aboutsummaryrefslogtreecommitdiffstats
path: root/OpenKeychain
diff options
context:
space:
mode:
authorDominik Schürmann <dominik@dominikschuermann.de>2014-04-06 12:57:42 +0200
committerDominik Schürmann <dominik@dominikschuermann.de>2014-04-06 12:57:42 +0200
commit6d1137190529dc7add74926cea52c377883319be (patch)
treefd88b29a048f3aec1daa2a84bbaf22c0efa3663f /OpenKeychain
parent17997dd362fe62d72113a0536069d0fdb9c3211b (diff)
downloadopen-keychain-6d1137190529dc7add74926cea52c377883319be.tar.gz
open-keychain-6d1137190529dc7add74926cea52c377883319be.tar.bz2
open-keychain-6d1137190529dc7add74926cea52c377883319be.zip
Rename folder structure from OpenPGP Keychain to OpenKeychain
Diffstat (limited to 'OpenKeychain')
-rw-r--r--OpenKeychain/.gitignore29
-rw-r--r--OpenKeychain/build.gradle105
-rw-r--r--OpenKeychain/src/main/AndroidManifest.xml450
-rw-r--r--OpenKeychain/src/main/assets/fontawesome-webfont.ttfbin0 -> 80776 bytes
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java82
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Id.java191
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java75
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/ClipboardReflection.java95
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/DialogFragmentWorkaround.java68
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/ListFragmentWorkaround.java37
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/ActionBarHelper.java120
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/ContactHelper.java41
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/ExportHelper.java177
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/FileHelper.java131
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/OtherHelper.java70
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/Preferences.java171
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpConversionHelper.java200
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java836
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyResult.java93
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java218
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java294
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java644
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java769
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java607
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpToX509.java313
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/exception/NoAsymmetricEncryptionException.java26
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/exception/PgpGeneralException.java29
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/exception/PgpGeneralMsgIdException.java35
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java297
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java301
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java752
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainServiceBlobContract.java40
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainServiceBlobDatabase.java47
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainServiceBlobProvider.java162
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java673
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AccountSettings.java85
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AppSettings.java50
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java484
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/RemoteService.java261
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/WrongPackageSignatureException.java27
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsActivity.java109
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsFragment.java201
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountsListFragment.java198
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java134
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsFragment.java108
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppsListActivity.java35
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppsListFragment.java174
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java303
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java905
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentServiceHandler.java130
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java380
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java107
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java388
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java182
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFileFragment.java261
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java180
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptMessageFragment.java189
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DrawerActivity.java295
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java760
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java256
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivityInterface.java30
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java268
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFileFragment.java380
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptMessageFragment.java259
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptSymmetricFragment.java98
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpAboutFragment.java75
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpActivity.java79
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpHtmlFragment.java75
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java489
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysClipboardFragment.java87
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java98
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java305
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysNFCFragment.java68
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysQrCodeFragment.java205
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysServerFragment.java155
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java95
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java700
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesActivity.java387
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesKeyServerActivity.java130
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyActivity.java143
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyFragment.java350
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectSecretKeyActivity.java83
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectSecretKeyFragment.java176
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectSecretKeyLayoutFragment.java211
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java127
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewCertActivity.java253
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java277
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivityJB.java124
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyCertsFragment.java311
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java347
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/AsyncTaskResultWrapper.java46
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/HighlightQueryCursorAdapter.java65
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java186
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListEntry.java274
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java167
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListServerLoader.java130
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyValueSpinnerAdapter.java101
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/PagerTabStripAdapter.java70
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java165
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/TabsAdapter.java101
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyKeysAdapter.java175
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyUserIdsAdapter.java181
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/BadImportKeyDialogFragment.java67
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/CreateKeyDialogFragment.java159
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteFileDialogFragment.java124
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java174
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java220
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/PassphraseDialogFragment.java328
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ProgressDialogFragment.java154
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SetPassphraseDialogFragment.java186
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ShareNfcDialogFragment.java99
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ShareQrCodeDialogFragment.java212
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/Editor.java27
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/FixedListView.java55
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/FoldableLinearLayout.java203
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/IntegerListPreference.java94
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyEditor.java377
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyServerEditor.java82
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SectionView.java429
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/UnderlineTextView.java69
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/UserIdEditor.java265
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/AlgorithmNames.java93
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Choice.java45
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/HkpKeyServer.java353
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/InputData.java41
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/IntentIntegratorSupportV4.java46
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/IterableIterator.java39
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/KeyServer.java52
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/KeychainServiceListener.java22
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Log.java83
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/PRNGFixes.java352
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/PausableThreadPoolExecutor.java94
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/PositionAwareInputStream.java83
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Primes.java188
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ProgressDialogUpdater.java25
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ProgressScaler.java50
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/QrCodeUtils.java72
-rw-r--r--OpenKeychain/src/main/res/anim/push_left_in.xml20
-rw-r--r--OpenKeychain/src/main/res/anim/push_left_out.xml20
-rw-r--r--OpenKeychain/src/main/res/anim/push_right_in.xml20
-rw-r--r--OpenKeychain/src/main/res/anim/push_right_out.xml20
-rw-r--r--OpenKeychain/src/main/res/drawable-hdpi/certify_small.pngbin0 -> 2091 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-hdpi/drawer_shadow.9.pngbin0 -> 161 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-hdpi/encrypted_small.pngbin0 -> 2187 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-hdpi/ic_action_add_person.pngbin0 -> 679 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-hdpi/ic_action_cancel.pngbin0 -> 1358 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-hdpi/ic_action_cloud.pngbin0 -> 450 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-hdpi/ic_action_discard.pngbin0 -> 454 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-hdpi/ic_action_done.pngbin0 -> 1320 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-hdpi/ic_action_import_export.pngbin0 -> 497 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-hdpi/ic_action_person.pngbin0 -> 573 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-hdpi/ic_action_save.pngbin0 -> 398 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-hdpi/ic_action_search.pngbin0 -> 702 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-hdpi/ic_action_secure.pngbin0 -> 394 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-hdpi/ic_action_select_all.pngbin0 -> 507 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-hdpi/ic_action_share.pngbin0 -> 647 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-hdpi/ic_dialog_alert_holo_light.pngbin0 -> 1018 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-hdpi/ic_drawer.pngbin0 -> 2829 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-hdpi/ic_menu_search.pngbin0 -> 1218 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-hdpi/ic_menu_search_list.pngbin0 -> 1190 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-hdpi/ic_next.pngbin0 -> 1722 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-hdpi/ic_previous.pngbin0 -> 1712 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-hdpi/icon.pngbin0 -> 5093 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-hdpi/key_small.pngbin0 -> 2088 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-hdpi/overlay_error.pngbin0 -> 1986 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-hdpi/overlay_ok.pngbin0 -> 1702 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-hdpi/popup_center_bright.9.pngbin0 -> 1110 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-hdpi/popup_full_bright.9.pngbin0 -> 2039 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-hdpi/revoked_key_small.pngbin0 -> 2509 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-hdpi/signed_large.pngbin0 -> 5928 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-hdpi/signed_small.pngbin0 -> 2219 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-ldpi/encrypted_small.pngbin0 -> 1176 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-ldpi/ic_next.pngbin0 -> 916 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-ldpi/ic_previous.pngbin0 -> 922 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-ldpi/icon.pngbin0 -> 1967 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-ldpi/key_small.pngbin0 -> 1074 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-ldpi/overlay_error.pngbin0 -> 1192 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-ldpi/overlay_ok.pngbin0 -> 1038 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-ldpi/signed_large.pngbin0 -> 2611 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-ldpi/signed_small.pngbin0 -> 1149 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-mdpi/certify_small.pngbin0 -> 1401 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-mdpi/drawer_shadow.9.pngbin0 -> 142 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-mdpi/encrypted_small.pngbin0 -> 1513 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-mdpi/ic_action_add_person.pngbin0 -> 513 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-mdpi/ic_action_cancel.pngbin0 -> 1202 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-mdpi/ic_action_cloud.pngbin0 -> 335 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-mdpi/ic_action_discard.pngbin0 -> 333 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-mdpi/ic_action_done.pngbin0 -> 1197 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-mdpi/ic_action_import_export.pngbin0 -> 410 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-mdpi/ic_action_person.pngbin0 -> 468 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-mdpi/ic_action_save.pngbin0 -> 359 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-mdpi/ic_action_search.pngbin0 -> 2349 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-mdpi/ic_action_secure.pngbin0 -> 317 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-mdpi/ic_action_select_all.pngbin0 -> 292 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-mdpi/ic_action_share.pngbin0 -> 472 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-mdpi/ic_dialog_alert_holo_light.pngbin0 -> 770 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-mdpi/ic_drawer.pngbin0 -> 2820 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-mdpi/ic_menu_search.pngbin0 -> 858 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-mdpi/ic_menu_search_list.pngbin0 -> 863 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-mdpi/ic_next.pngbin0 -> 1360 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-mdpi/ic_previous.pngbin0 -> 1352 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-mdpi/icon.pngbin0 -> 2896 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-mdpi/key_small.pngbin0 -> 1484 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-mdpi/overlay_error.pngbin0 -> 1539 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-mdpi/overlay_ok.pngbin0 -> 1305 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-mdpi/signed_large.pngbin0 -> 3858 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-mdpi/signed_small.pngbin0 -> 1576 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-xhdpi/drawer_shadow.9.pngbin0 -> 174 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-xhdpi/ic_action_add_person.pngbin0 -> 884 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-xhdpi/ic_action_cancel.pngbin0 -> 1488 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-xhdpi/ic_action_cloud.pngbin0 -> 538 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-xhdpi/ic_action_discard.pngbin0 -> 552 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-xhdpi/ic_action_done.pngbin0 -> 1546 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-xhdpi/ic_action_import_export.pngbin0 -> 633 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-xhdpi/ic_action_person.pngbin0 -> 781 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-xhdpi/ic_action_save.pngbin0 -> 451 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-xhdpi/ic_action_search.pngbin0 -> 900 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-xhdpi/ic_action_secure.pngbin0 -> 510 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-xhdpi/ic_action_select_all.pngbin0 -> 351 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-xhdpi/ic_action_share.pngbin0 -> 785 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-xhdpi/ic_dialog_alert_holo_light.pngbin0 -> 1359 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-xhdpi/ic_drawer.pngbin0 -> 2836 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-xhdpi/ic_menu_search.pngbin0 -> 1629 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-xhdpi/ic_menu_search_list.pngbin0 -> 1571 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-xhdpi/icon.pngbin0 -> 7870 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-xxhdpi/drawer_shadow.9.pngbin0 -> 208 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_add_person.pngbin0 -> 1171 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_cloud.pngbin0 -> 760 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_discard.pngbin0 -> 781 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_import_export.pngbin0 -> 896 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_person.pngbin0 -> 1004 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_save.pngbin0 -> 500 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_search.pngbin0 -> 1153 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_secure.pngbin0 -> 624 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_select_all.pngbin0 -> 563 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_share.pngbin0 -> 1094 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-xxhdpi/ic_dialog_alert_holo_light.pngbin0 -> 1991 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-xxhdpi/ic_drawer.pngbin0 -> 202 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-xxhdpi/icon.pngbin0 -> 14153 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-xxxhdpi/icon.pngbin0 -> 20825 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable/certify_small.pngbin0 -> 1401 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable/encrypted_small.pngbin0 -> 1513 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable/ic_next.pngbin0 -> 1360 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable/ic_previous.pngbin0 -> 1352 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable/key_small.pngbin0 -> 1484 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable/overlay_error.pngbin0 -> 1539 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable/overlay_ok.pngbin0 -> 1305 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable/revoked_key_small.pngbin0 -> 1793 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable/section_header.xml11
-rw-r--r--OpenKeychain/src/main/res/drawable/selector_transparent_button.xml7
-rw-r--r--OpenKeychain/src/main/res/drawable/signed_large.pngbin0 -> 3858 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable/signed_small.pngbin0 -> 1576 bytes
-rw-r--r--OpenKeychain/src/main/res/layout-large/api_apps_list_activity.xml20
-rw-r--r--OpenKeychain/src/main/res/layout-large/decrypt_activity.xml19
-rw-r--r--OpenKeychain/src/main/res/layout-large/encrypt_activity.xml19
-rw-r--r--OpenKeychain/src/main/res/layout-large/import_keys_activity.xml21
-rw-r--r--OpenKeychain/src/main/res/layout-large/key_list_activity.xml18
-rw-r--r--OpenKeychain/src/main/res/layout/actionbar_custom_view_done.xml27
-rw-r--r--OpenKeychain/src/main/res/layout/actionbar_custom_view_done_cancel.xml29
-rw-r--r--OpenKeychain/src/main/res/layout/actionbar_include_cancel_button.xml36
-rw-r--r--OpenKeychain/src/main/res/layout/actionbar_include_done_button.xml36
-rw-r--r--OpenKeychain/src/main/res/layout/api_account_settings_activity.xml20
-rw-r--r--OpenKeychain/src/main/res/layout/api_account_settings_fragment.xml102
-rw-r--r--OpenKeychain/src/main/res/layout/api_accounts_adapter_list_item.xml27
-rw-r--r--OpenKeychain/src/main/res/layout/api_app_settings_activity.xml33
-rw-r--r--OpenKeychain/src/main/res/layout/api_app_settings_fragment.xml75
-rw-r--r--OpenKeychain/src/main/res/layout/api_apps_adapter_list_item.xml26
-rw-r--r--OpenKeychain/src/main/res/layout/api_apps_list_activity.xml11
-rw-r--r--OpenKeychain/src/main/res/layout/api_apps_list_content.xml14
-rw-r--r--OpenKeychain/src/main/res/layout/api_remote_create_account.xml29
-rw-r--r--OpenKeychain/src/main/res/layout/api_remote_error_message.xml16
-rw-r--r--OpenKeychain/src/main/res/layout/api_remote_register_app.xml29
-rw-r--r--OpenKeychain/src/main/res/layout/api_remote_select_pub_keys.xml21
-rw-r--r--OpenKeychain/src/main/res/layout/certify_key_activity.xml156
-rw-r--r--OpenKeychain/src/main/res/layout/create_key_dialog.xml61
-rw-r--r--OpenKeychain/src/main/res/layout/decrypt_activity.xml12
-rw-r--r--OpenKeychain/src/main/res/layout/decrypt_content.xml23
-rw-r--r--OpenKeychain/src/main/res/layout/decrypt_file_fragment.xml81
-rw-r--r--OpenKeychain/src/main/res/layout/decrypt_message_fragment.xml66
-rw-r--r--OpenKeychain/src/main/res/layout/decrypt_signature_include.xml62
-rw-r--r--OpenKeychain/src/main/res/layout/drawer_list.xml18
-rw-r--r--OpenKeychain/src/main/res/layout/drawer_list_item.xml33
-rw-r--r--OpenKeychain/src/main/res/layout/edit_key_activity.xml46
-rw-r--r--OpenKeychain/src/main/res/layout/edit_key_key_item.xml176
-rw-r--r--OpenKeychain/src/main/res/layout/edit_key_section.xml44
-rw-r--r--OpenKeychain/src/main/res/layout/edit_key_user_id_item.xml99
-rw-r--r--OpenKeychain/src/main/res/layout/encrypt_activity.xml13
-rw-r--r--OpenKeychain/src/main/res/layout/encrypt_asymmetric_fragment.xml79
-rw-r--r--OpenKeychain/src/main/res/layout/encrypt_content.xml37
-rw-r--r--OpenKeychain/src/main/res/layout/encrypt_content_adv_settings.xml63
-rw-r--r--OpenKeychain/src/main/res/layout/encrypt_file_fragment.xml86
-rw-r--r--OpenKeychain/src/main/res/layout/encrypt_message_fragment.xml62
-rw-r--r--OpenKeychain/src/main/res/layout/encrypt_symmetric_fragment.xml52
-rw-r--r--OpenKeychain/src/main/res/layout/file_dialog.xml52
-rw-r--r--OpenKeychain/src/main/res/layout/foldable_linearlayout.xml41
-rw-r--r--OpenKeychain/src/main/res/layout/help_about_fragment.xml62
-rw-r--r--OpenKeychain/src/main/res/layout/help_activity.xml12
-rw-r--r--OpenKeychain/src/main/res/layout/import_keys_activity.xml11
-rw-r--r--OpenKeychain/src/main/res/layout/import_keys_clipboard_fragment.xml18
-rw-r--r--OpenKeychain/src/main/res/layout/import_keys_content.xml50
-rw-r--r--OpenKeychain/src/main/res/layout/import_keys_file_fragment.xml19
-rw-r--r--OpenKeychain/src/main/res/layout/import_keys_list_entry.xml110
-rw-r--r--OpenKeychain/src/main/res/layout/import_keys_list_entry_user_id.xml26
-rw-r--r--OpenKeychain/src/main/res/layout/import_keys_nfc_fragment.xml28
-rw-r--r--OpenKeychain/src/main/res/layout/import_keys_qr_code_fragment.xml36
-rw-r--r--OpenKeychain/src/main/res/layout/import_keys_server_fragment.xml45
-rw-r--r--OpenKeychain/src/main/res/layout/key_list_activity.xml11
-rw-r--r--OpenKeychain/src/main/res/layout/key_list_content.xml14
-rw-r--r--OpenKeychain/src/main/res/layout/key_list_fragment.xml109
-rw-r--r--OpenKeychain/src/main/res/layout/key_list_header.xml30
-rw-r--r--OpenKeychain/src/main/res/layout/key_list_item.xml86
-rw-r--r--OpenKeychain/src/main/res/layout/key_server_editor.xml40
-rw-r--r--OpenKeychain/src/main/res/layout/key_server_export.xml40
-rw-r--r--OpenKeychain/src/main/res/layout/key_server_preference.xml78
-rw-r--r--OpenKeychain/src/main/res/layout/passphrase_dialog.xml24
-rw-r--r--OpenKeychain/src/main/res/layout/passphrase_repeat_dialog.xml50
-rw-r--r--OpenKeychain/src/main/res/layout/select_key_item.xml64
-rw-r--r--OpenKeychain/src/main/res/layout/select_public_key_activity.xml12
-rw-r--r--OpenKeychain/src/main/res/layout/select_secret_key_activity.xml12
-rw-r--r--OpenKeychain/src/main/res/layout/select_secret_key_layout_fragment.xml78
-rw-r--r--OpenKeychain/src/main/res/layout/share_qr_code_dialog.xml19
-rw-r--r--OpenKeychain/src/main/res/layout/view_cert_activity.xml210
-rw-r--r--OpenKeychain/src/main/res/layout/view_key_activity.xml12
-rw-r--r--OpenKeychain/src/main/res/layout/view_key_certs_fragment.xml34
-rw-r--r--OpenKeychain/src/main/res/layout/view_key_certs_header.xml30
-rw-r--r--OpenKeychain/src/main/res/layout/view_key_certs_item.xml46
-rw-r--r--OpenKeychain/src/main/res/layout/view_key_delete_fragment.xml38
-rw-r--r--OpenKeychain/src/main/res/layout/view_key_keys_item.xml84
-rw-r--r--OpenKeychain/src/main/res/layout/view_key_main_fragment.xml265
-rw-r--r--OpenKeychain/src/main/res/layout/view_key_userids_item.xml58
-rw-r--r--OpenKeychain/src/main/res/menu/api_account_settings.xml14
-rw-r--r--OpenKeychain/src/main/res/menu/api_app_settings.xml10
-rw-r--r--OpenKeychain/src/main/res/menu/key_list.xml34
-rw-r--r--OpenKeychain/src/main/res/menu/key_list_multi.xml24
-rw-r--r--OpenKeychain/src/main/res/menu/key_view.xml81
-rw-r--r--OpenKeychain/src/main/res/menu/view_cert.xml9
-rw-r--r--OpenKeychain/src/main/res/raw-cs-rCZ/help_about.html49
-rw-r--r--OpenKeychain/src/main/res/raw-cs-rCZ/help_changelog.html136
-rw-r--r--OpenKeychain/src/main/res/raw-cs-rCZ/help_nfc_beam.html12
-rw-r--r--OpenKeychain/src/main/res/raw-cs-rCZ/help_start.html19
-rw-r--r--OpenKeychain/src/main/res/raw-cs-rCZ/nfc_beam_share.html11
-rw-r--r--OpenKeychain/src/main/res/raw-de/help_about.html49
-rw-r--r--OpenKeychain/src/main/res/raw-de/help_changelog.html136
-rw-r--r--OpenKeychain/src/main/res/raw-de/help_nfc_beam.html12
-rw-r--r--OpenKeychain/src/main/res/raw-de/help_start.html19
-rw-r--r--OpenKeychain/src/main/res/raw-de/nfc_beam_share.html11
-rw-r--r--OpenKeychain/src/main/res/raw-el/help_about.html49
-rw-r--r--OpenKeychain/src/main/res/raw-el/help_changelog.html136
-rw-r--r--OpenKeychain/src/main/res/raw-el/help_nfc_beam.html12
-rw-r--r--OpenKeychain/src/main/res/raw-el/help_start.html19
-rw-r--r--OpenKeychain/src/main/res/raw-el/nfc_beam_share.html11
-rw-r--r--OpenKeychain/src/main/res/raw-es-rCO/help_about.html49
-rw-r--r--OpenKeychain/src/main/res/raw-es-rCO/help_changelog.html136
-rw-r--r--OpenKeychain/src/main/res/raw-es-rCO/help_nfc_beam.html12
-rw-r--r--OpenKeychain/src/main/res/raw-es-rCO/help_start.html19
-rw-r--r--OpenKeychain/src/main/res/raw-es-rCO/nfc_beam_share.html11
-rw-r--r--OpenKeychain/src/main/res/raw-es/help_about.html49
-rw-r--r--OpenKeychain/src/main/res/raw-es/help_changelog.html136
-rw-r--r--OpenKeychain/src/main/res/raw-es/help_nfc_beam.html12
-rw-r--r--OpenKeychain/src/main/res/raw-es/help_start.html19
-rw-r--r--OpenKeychain/src/main/res/raw-es/nfc_beam_share.html11
-rw-r--r--OpenKeychain/src/main/res/raw-et/help_about.html49
-rw-r--r--OpenKeychain/src/main/res/raw-et/help_changelog.html136
-rw-r--r--OpenKeychain/src/main/res/raw-et/help_nfc_beam.html12
-rw-r--r--OpenKeychain/src/main/res/raw-et/help_start.html19
-rw-r--r--OpenKeychain/src/main/res/raw-et/nfc_beam_share.html11
-rw-r--r--OpenKeychain/src/main/res/raw-fa-rIR/help_about.html49
-rw-r--r--OpenKeychain/src/main/res/raw-fa-rIR/help_changelog.html136
-rw-r--r--OpenKeychain/src/main/res/raw-fa-rIR/help_nfc_beam.html12
-rw-r--r--OpenKeychain/src/main/res/raw-fa-rIR/help_start.html19
-rw-r--r--OpenKeychain/src/main/res/raw-fa-rIR/nfc_beam_share.html11
-rw-r--r--OpenKeychain/src/main/res/raw-fr/help_about.html49
-rw-r--r--OpenKeychain/src/main/res/raw-fr/help_changelog.html136
-rw-r--r--OpenKeychain/src/main/res/raw-fr/help_nfc_beam.html12
-rw-r--r--OpenKeychain/src/main/res/raw-fr/help_start.html19
-rw-r--r--OpenKeychain/src/main/res/raw-fr/nfc_beam_share.html11
-rw-r--r--OpenKeychain/src/main/res/raw-it-rIT/help_about.html49
-rw-r--r--OpenKeychain/src/main/res/raw-it-rIT/help_changelog.html136
-rw-r--r--OpenKeychain/src/main/res/raw-it-rIT/help_nfc_beam.html12
-rw-r--r--OpenKeychain/src/main/res/raw-it-rIT/help_start.html19
-rw-r--r--OpenKeychain/src/main/res/raw-it-rIT/nfc_beam_share.html11
-rw-r--r--OpenKeychain/src/main/res/raw-ja/help_about.html49
-rw-r--r--OpenKeychain/src/main/res/raw-ja/help_changelog.html136
-rw-r--r--OpenKeychain/src/main/res/raw-ja/help_nfc_beam.html12
-rw-r--r--OpenKeychain/src/main/res/raw-ja/help_start.html19
-rw-r--r--OpenKeychain/src/main/res/raw-ja/nfc_beam_share.html11
-rw-r--r--OpenKeychain/src/main/res/raw-nl-rNL/help_about.html49
-rw-r--r--OpenKeychain/src/main/res/raw-nl-rNL/help_changelog.html136
-rw-r--r--OpenKeychain/src/main/res/raw-nl-rNL/help_nfc_beam.html12
-rw-r--r--OpenKeychain/src/main/res/raw-nl-rNL/help_start.html19
-rw-r--r--OpenKeychain/src/main/res/raw-nl-rNL/nfc_beam_share.html11
-rw-r--r--OpenKeychain/src/main/res/raw-pl/help_about.html49
-rw-r--r--OpenKeychain/src/main/res/raw-pl/help_changelog.html136
-rw-r--r--OpenKeychain/src/main/res/raw-pl/help_nfc_beam.html12
-rw-r--r--OpenKeychain/src/main/res/raw-pl/help_start.html19
-rw-r--r--OpenKeychain/src/main/res/raw-pl/nfc_beam_share.html11
-rw-r--r--OpenKeychain/src/main/res/raw-pt-rBR/help_about.html49
-rw-r--r--OpenKeychain/src/main/res/raw-pt-rBR/help_changelog.html136
-rw-r--r--OpenKeychain/src/main/res/raw-pt-rBR/help_nfc_beam.html12
-rw-r--r--OpenKeychain/src/main/res/raw-pt-rBR/help_start.html19
-rw-r--r--OpenKeychain/src/main/res/raw-pt-rBR/nfc_beam_share.html11
-rw-r--r--OpenKeychain/src/main/res/raw-ru/help_about.html49
-rw-r--r--OpenKeychain/src/main/res/raw-ru/help_changelog.html136
-rw-r--r--OpenKeychain/src/main/res/raw-ru/help_nfc_beam.html12
-rw-r--r--OpenKeychain/src/main/res/raw-ru/help_start.html19
-rw-r--r--OpenKeychain/src/main/res/raw-ru/nfc_beam_share.html11
-rw-r--r--OpenKeychain/src/main/res/raw-sl-rSI/help_about.html49
-rw-r--r--OpenKeychain/src/main/res/raw-sl-rSI/help_changelog.html136
-rw-r--r--OpenKeychain/src/main/res/raw-sl-rSI/help_nfc_beam.html12
-rw-r--r--OpenKeychain/src/main/res/raw-sl-rSI/help_start.html19
-rw-r--r--OpenKeychain/src/main/res/raw-sl-rSI/nfc_beam_share.html11
-rw-r--r--OpenKeychain/src/main/res/raw-tr/help_about.html49
-rw-r--r--OpenKeychain/src/main/res/raw-tr/help_changelog.html136
-rw-r--r--OpenKeychain/src/main/res/raw-tr/help_nfc_beam.html12
-rw-r--r--OpenKeychain/src/main/res/raw-tr/help_start.html19
-rw-r--r--OpenKeychain/src/main/res/raw-tr/nfc_beam_share.html11
-rw-r--r--OpenKeychain/src/main/res/raw-uk/help_about.html49
-rw-r--r--OpenKeychain/src/main/res/raw-uk/help_changelog.html136
-rw-r--r--OpenKeychain/src/main/res/raw-uk/help_nfc_beam.html12
-rw-r--r--OpenKeychain/src/main/res/raw-uk/help_start.html19
-rw-r--r--OpenKeychain/src/main/res/raw-uk/nfc_beam_share.html11
-rw-r--r--OpenKeychain/src/main/res/raw-zh-rTW/help_about.html49
-rw-r--r--OpenKeychain/src/main/res/raw-zh-rTW/help_changelog.html136
-rw-r--r--OpenKeychain/src/main/res/raw-zh-rTW/help_nfc_beam.html12
-rw-r--r--OpenKeychain/src/main/res/raw-zh-rTW/help_start.html19
-rw-r--r--OpenKeychain/src/main/res/raw-zh-rTW/nfc_beam_share.html11
-rw-r--r--OpenKeychain/src/main/res/raw-zh/help_about.html49
-rw-r--r--OpenKeychain/src/main/res/raw-zh/help_changelog.html136
-rw-r--r--OpenKeychain/src/main/res/raw-zh/help_nfc_beam.html12
-rw-r--r--OpenKeychain/src/main/res/raw-zh/help_start.html19
-rw-r--r--OpenKeychain/src/main/res/raw-zh/nfc_beam_share.html11
-rw-r--r--OpenKeychain/src/main/res/raw/help_about.html47
-rw-r--r--OpenKeychain/src/main/res/raw/help_changelog.html156
-rw-r--r--OpenKeychain/src/main/res/raw/help_faq.html13
-rw-r--r--OpenKeychain/src/main/res/raw/help_nfc_beam.html16
-rw-r--r--OpenKeychain/src/main/res/raw/help_start.html23
-rw-r--r--OpenKeychain/src/main/res/raw/nfc_beam_share.html15
-rw-r--r--OpenKeychain/src/main/res/values-cs-rCZ/strings.xml51
-rw-r--r--OpenKeychain/src/main/res/values-de/strings.xml436
-rw-r--r--OpenKeychain/src/main/res/values-el/strings.xml52
-rw-r--r--OpenKeychain/src/main/res/values-es-rCO/strings.xml98
-rw-r--r--OpenKeychain/src/main/res/values-es/strings.xml451
-rw-r--r--OpenKeychain/src/main/res/values-et/strings.xml118
-rw-r--r--OpenKeychain/src/main/res/values-fa-rIR/strings.xml29
-rw-r--r--OpenKeychain/src/main/res/values-fr/strings.xml451
-rw-r--r--OpenKeychain/src/main/res/values-it-rIT/strings.xml451
-rw-r--r--OpenKeychain/src/main/res/values-ja/strings.xml436
-rw-r--r--OpenKeychain/src/main/res/values-large/dimens.xml4
-rw-r--r--OpenKeychain/src/main/res/values-nl-rNL/strings.xml229
-rw-r--r--OpenKeychain/src/main/res/values-pl/strings.xml466
-rw-r--r--OpenKeychain/src/main/res/values-pt-rBR/strings.xml29
-rw-r--r--OpenKeychain/src/main/res/values-ru/strings.xml456
-rw-r--r--OpenKeychain/src/main/res/values-sl-rSI/strings.xml29
-rw-r--r--OpenKeychain/src/main/res/values-tr/strings.xml138
-rw-r--r--OpenKeychain/src/main/res/values-uk/strings.xml467
-rw-r--r--OpenKeychain/src/main/res/values-v14/styles.xml18
-rw-r--r--OpenKeychain/src/main/res/values-zh-rTW/strings.xml29
-rw-r--r--OpenKeychain/src/main/res/values-zh/strings.xml181
-rw-r--r--OpenKeychain/src/main/res/values/arrays.xml46
-rw-r--r--OpenKeychain/src/main/res/values/attr.xml11
-rw-r--r--OpenKeychain/src/main/res/values/colors.xml7
-rw-r--r--OpenKeychain/src/main/res/values/dimens.xml5
-rw-r--r--OpenKeychain/src/main/res/values/ids.xml6
-rw-r--r--OpenKeychain/src/main/res/values/static_strings.xml6
-rw-r--r--OpenKeychain/src/main/res/values/strings.xml511
-rw-r--r--OpenKeychain/src/main/res/values/styles.xml34
-rw-r--r--OpenKeychain/src/main/res/xml/adv_preferences.xml48
-rw-r--r--OpenKeychain/src/main/res/xml/gen_preferences.xml32
-rw-r--r--OpenKeychain/src/main/res/xml/preference_headers.xml25
-rw-r--r--OpenKeychain/src/main/res/xml/preference_headers_legacy.xml29
-rw-r--r--OpenKeychain/src/main/res/xml/searchable_public_keys.xml22
-rw-r--r--OpenKeychain/src/main/res/xml/searchable_secret_keys.xml22
-rw-r--r--OpenKeychain/src/test/java/org/sufficientlysecure/keychain/PgpKeyOperationTest.java46
-rw-r--r--OpenKeychain/src/test/java/org/sufficientlysecure/keychain/RobolectricGradleTestRunner.java23
474 files changed, 41584 insertions, 0 deletions
diff --git a/OpenKeychain/.gitignore b/OpenKeychain/.gitignore
new file mode 100644
index 000000000..aa8bb5760
--- /dev/null
+++ b/OpenKeychain/.gitignore
@@ -0,0 +1,29 @@
+#Android specific
+bin
+gen
+obj
+lint.xml
+local.properties
+release.properties
+ant.properties
+*.class
+*.apk
+
+#Gradle
+.gradle
+build
+gradle.properties
+
+#Maven
+target
+pom.xml.*
+
+#Eclipse
+.project
+.classpath
+.settings
+.metadata
+
+#IntelliJ IDEA
+.idea
+*.iml
diff --git a/OpenKeychain/build.gradle b/OpenKeychain/build.gradle
new file mode 100644
index 000000000..3fcdf790b
--- /dev/null
+++ b/OpenKeychain/build.gradle
@@ -0,0 +1,105 @@
+apply plugin: 'android'
+
+sourceSets {
+ testLocal {
+ java.srcDir file('src/test/java')
+ resources.srcDir file('src/test/resources')
+ }
+}
+
+dependencies {
+ compile 'com.android.support:support-v4:19.0.1'
+ compile 'com.android.support:appcompat-v7:19.0.1'
+ compile project(':OpenKeychain-API:libraries:openpgp-api-library')
+ compile project(':OpenKeychain-API:libraries:openkeychain-api-library')
+ compile project(':libraries:HtmlTextView')
+ compile project(':libraries:StickyListHeaders:library')
+ compile project(':libraries:AndroidBootstrap')
+ compile project(':libraries:zxing')
+ compile project(':libraries:zxing-android-integration')
+ compile project(':libraries:spongycastle:core')
+ compile project(':libraries:spongycastle:pg')
+ compile project(':libraries:spongycastle:pkix')
+ compile project(':libraries:spongycastle:prov')
+ compile project(':libraries:Android-AppMsg:library')
+
+ // Dependencies for the `testLocal` task, make sure to list all your global dependencies here as well
+ testLocalCompile 'junit:junit:4.11'
+ testLocalCompile 'org.robolectric:robolectric:2.1.+'
+ testLocalCompile 'com.google.android:android:4.1.1.4'
+ testLocalCompile 'com.android.support:support-v4:19.0.1'
+ testLocalCompile 'com.android.support:appcompat-v7:19.0.1'
+ testLocalCompile project(':OpenKeychain-API:libraries:openpgp-api-library')
+ testLocalCompile project(':OpenKeychain-API:libraries:openkeychain-api-library')
+ testLocalCompile project(':libraries:HtmlTextView')
+ testLocalCompile project(':libraries:StickyListHeaders:library')
+ testLocalCompile project(':libraries:AndroidBootstrap')
+ testLocalCompile project(':libraries:zxing')
+ testLocalCompile project(':libraries:zxing-android-integration')
+ testLocalCompile project(':libraries:spongycastle:core')
+ testLocalCompile project(':libraries:spongycastle:pg')
+ testLocalCompile project(':libraries:spongycastle:pkix')
+ testLocalCompile project(':libraries:spongycastle:prov')
+ testLocalCompile project(':libraries:Android-AppMsg:library')
+}
+
+android {
+ compileSdkVersion 19
+ buildToolsVersion "19.0.3"
+
+ defaultConfig {
+ minSdkVersion 9
+ targetSdkVersion 19
+ }
+
+ /*
+ * To sign release build, create file gradle.properties in ~/.gradle/ with this content:
+ *
+ * signingStoreLocation=/home/key.store
+ * signingStorePassword=xxx
+ * signingKeyAlias=alias
+ * signingKeyPassword=xxx
+ */
+ if (project.hasProperty('signingStoreLocation') &&
+ project.hasProperty('signingStorePassword') &&
+ project.hasProperty('signingKeyAlias') &&
+ project.hasProperty('signingKeyPassword')) {
+ println "Found sign properties in gradle.properties! Signing build…"
+
+ signingConfigs {
+ release {
+ storeFile file(signingStoreLocation)
+ storePassword signingStorePassword
+ keyAlias signingKeyAlias
+ keyPassword signingKeyPassword
+ }
+ }
+
+ buildTypes.release.signingConfig = signingConfigs.release
+ } else {
+ buildTypes.release.signingConfig = null
+ }
+
+ // Do not abort build if lint finds errors
+ lintOptions {
+ abortOnError false
+ htmlReport true
+ htmlOutput file("lint-report.html")
+ }
+}
+
+task localTest(type: Test, dependsOn: assemble) {
+ testClassesDir = sourceSets.testLocal.output.classesDir
+
+ android.sourceSets.main.java.srcDirs.each { dir ->
+ def buildDir = dir.getAbsolutePath().split('/')
+ buildDir = (buildDir[0..(buildDir.length - 4)] + ['build', 'classes', 'debug']).join('/')
+
+ sourceSets.testLocal.compileClasspath += files(buildDir)
+ sourceSets.testLocal.runtimeClasspath += files(buildDir)
+ }
+
+ classpath = sourceSets.testLocal.runtimeClasspath
+}
+
+//check.dependsOn localTest
diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..de17f9b20
--- /dev/null
+++ b/OpenKeychain/src/main/AndroidManifest.xml
@@ -0,0 +1,450 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="org.sufficientlysecure.keychain"
+ android:installLocation="auto"
+ android:versionCode="25000"
+ android:versionName="2.5">
+
+ <!--
+ General remarks
+ ===============
+ - Last APG 1 version was 10900 (1.0.9 beta 00)
+ - Keychain starting with versionCode 20000!
+
+ Association of file types to Keychain
+ =====================================
+ General remarks about file ending conventions:
+ - *.gpg for binary files
+ - *.asc for ascii armored files The actual content can be anything.
+
+ The file ending only shows if it is binary or ascii encoded.
+
+ Remarks about the ugly android:pathPattern:
+ - We are matching all files with a specific file ending.
+ This is done in an ugly way because of Android limitations.
+ Read http://stackoverflow.com/questions/1733195/android-intent-filter-for-a-particular-file-extension
+ and http://stackoverflow.com/questions/3400072/pathpattern-to-match-file-extension-does-not-work-if-a-period-exists-elsewhere-i/8599921
+ for more information.
+ - Do _not_ set mimeType for gpg!
+ Cyanogenmod's file manager will only show Keychain for gpg files if no mimeType is set!
+ For OI Filemanager it makes no difference, gpg files can't be associated
+ -->
+
+ <uses-sdk
+ android:minSdkVersion="9"
+ android:targetSdkVersion="19" />
+
+ <uses-feature
+ android:name="android.hardware.wifi"
+ android:required="false" />
+ <uses-feature
+ android:name="android.hardware.telephony"
+ android:required="false" />
+ <uses-feature
+ android:name="android.hardware.microphone"
+ android:required="false" />
+ <uses-feature
+ android:name="android.hardware.touchscreen"
+ android:required="false" />
+
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.NFC" />
+ <uses-permission android:name="android.permission.GET_ACCOUNTS" />
+
+ <!-- android:allowBackup="false": Don't allow backup over adb backup or other apps! -->
+ <application
+ android:name=".KeychainApplication"
+ android:allowBackup="false"
+ android:hardwareAccelerated="true"
+ android:icon="@drawable/icon"
+ android:theme="@style/KeychainTheme"
+ android:label="@string/app_name">
+ <activity
+ android:name=".ui.KeyListActivity"
+ android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
+ android:label="@string/app_name"
+ android:launchMode="singleTop">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ <activity
+ android:name=".ui.EditKeyActivity"
+ android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
+ android:label="@string/title_edit_key"
+ android:windowSoftInputMode="stateHidden" />
+ <activity
+ android:name=".ui.ViewKeyActivity"
+ android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
+ android:label="@string/title_key_details"
+ android:parentActivityName=".ui.KeyListActivity">
+ <meta-data
+ android:name="android.support.PARENT_ACTIVITY"
+ android:value=".ui.KeyListActivity" />
+ </activity>
+ <activity
+ android:name=".ui.ViewKeyActivityJB"
+ android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
+ android:label="@string/title_key_details"
+ android:parentActivityName=".ui.KeyListActivity">
+ <meta-data
+ android:name="android.support.PARENT_ACTIVITY"
+ android:value=".ui.KeyListActivity" />
+ </activity>
+ <activity
+ android:name=".ui.ViewCertActivity"
+ android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
+ android:label="View Certificate Details"
+ android:parentActivityName=".ui.ViewKeyActivity">
+ <meta-data
+ android:name="android.support.PARENT_ACTIVITY"
+ android:value=".ui.ViewKeyActivity" />
+ </activity>
+ <activity
+ android:name=".ui.SelectPublicKeyActivity"
+ android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
+ android:label="@string/title_select_recipients"
+ android:launchMode="singleTop" />
+ <activity
+ android:name=".ui.SelectSecretKeyActivity"
+ android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
+ android:label="@string/title_select_secret_key"
+ android:launchMode="singleTop" />
+ <activity
+ android:name=".ui.EncryptActivity"
+ android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
+ android:label="@string/title_encrypt"
+ android:windowSoftInputMode="stateHidden">
+
+ <!-- Keychain's own Actions -->
+ <!-- ENCRYPT with text as extra -->
+ <intent-filter>
+ <action android:name="org.sufficientlysecure.keychain.action.ENCRYPT" />
+
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <!-- ENCRYPT with data Uri -->
+ <intent-filter>
+ <action android:name="org.sufficientlysecure.keychain.action.ENCRYPT" />
+
+ <category android:name="android.intent.category.DEFAULT" />
+ <!-- TODO: accept other schemes! -->
+ <data android:scheme="file" />
+ </intent-filter>
+ <!-- Android's Send Action -->
+ <intent-filter android:label="@string/intent_send_encrypt">
+ <action android:name="android.intent.action.SEND" />
+
+ <category android:name="android.intent.category.DEFAULT" />
+
+ <data android:mimeType="*/*" />
+ </intent-filter>
+ </activity>
+ <activity
+ android:name=".ui.DecryptActivity"
+ android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
+ android:label="@string/title_decrypt"
+ android:windowSoftInputMode="stateHidden">
+
+ <!--&lt;!&ndash; VIEW with mimeType: TODO (from email app) &ndash;&gt;-->
+ <!--<intent-filter android:label="@string/intent_import_key">-->
+ <!--<action android:name="android.intent.action.VIEW" />-->
+
+ <!--<category android:name="android.intent.category.BROWSABLE" />-->
+ <!--<category android:name="android.intent.category.DEFAULT" />-->
+
+ <!--&lt;!&ndash; mime type as defined in http://tools.ietf.org/html/rfc3156 &ndash;&gt;-->
+ <!--<data android:mimeType="application/pgp-signature" />-->
+ <!--</intent-filter>-->
+ <!--&lt;!&ndash; VIEW with mimeType: TODO (from email app) &ndash;&gt;-->
+ <!--<intent-filter android:label="@string/intent_import_key">-->
+ <!--<action android:name="android.intent.action.VIEW" />-->
+
+ <!--<category android:name="android.intent.category.BROWSABLE" />-->
+ <!--<category android:name="android.intent.category.DEFAULT" />-->
+
+ <!--&lt;!&ndash; mime type as defined in http://tools.ietf.org/html/rfc3156 &ndash;&gt;-->
+ <!--<data android:mimeType="application/pgp-encrypted" />-->
+ <!--</intent-filter>-->
+ <!-- Keychain's own Actions -->
+ <!-- DECRYPT with text as extra -->
+ <intent-filter>
+ <action android:name="org.sufficientlysecure.keychain.action.DECRYPT" />
+
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <!-- DECRYPT with data Uri -->
+ <intent-filter>
+ <action android:name="org.sufficientlysecure.keychain.action.DECRYPT" />
+
+ <category android:name="android.intent.category.DEFAULT" />
+ <!-- TODO: accept other schemes! -->
+ <data android:scheme="file" />
+ </intent-filter>
+ <!-- Android's Send Action -->
+ <intent-filter android:label="@string/intent_send_decrypt">
+ <action android:name="android.intent.action.SEND" />
+
+ <category android:name="android.intent.category.DEFAULT" />
+
+ <data android:mimeType="*/*" />
+ </intent-filter>
+ <!-- Linking "Decrypt" to file types -->
+ <intent-filter android:label="@string/intent_decrypt_file">
+ <action android:name="android.intent.action.VIEW" />
+
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+
+ <data android:host="*" />
+ <data android:scheme="file" />
+ <data android:scheme="content" />
+ <!-- Workaround to match files in pathes with dots in them, like /cdcard/my.folder/test.gpg -->
+ <data android:pathPattern=".*\\.gpg" />
+ <data android:pathPattern=".*\\..*\\.gpg" />
+ <data android:pathPattern=".*\\..*\\..*\\.gpg" />
+ <data android:pathPattern=".*\\..*\\..*\\..*\\.gpg" />
+ <data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.gpg" />
+ <data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
+ <data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
+ <data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
+ <data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
+ <data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
+ </intent-filter>
+ <intent-filter android:label="@string/intent_decrypt_file">
+ <action android:name="android.intent.action.VIEW" />
+
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+
+ <data android:host="*" />
+ <data android:scheme="file" />
+ <data android:scheme="content" />
+ <data android:mimeType="*/*" />
+ <data android:pathPattern=".*\\.asc" />
+ <data android:pathPattern=".*\\..*\\.asc" />
+ <data android:pathPattern=".*\\..*\\..*\\.asc" />
+ <data android:pathPattern=".*\\..*\\..*\\..*\\.asc" />
+ <data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.asc" />
+ <data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.asc" />
+ <data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\.asc" />
+ <data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.asc" />
+ <data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.asc" />
+ <data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.asc" />
+ </intent-filter>
+ </activity>
+ <activity
+ android:name=".ui.UploadKeyActivity"
+ android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
+ android:label="@string/title_send_key" />
+ <activity
+ android:name=".ui.PreferencesActivity"
+ android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
+ android:label="@string/title_preferences">
+ <intent-filter>
+ <action android:name="org.sufficientlysecure.keychain.ui.PREFS_GEN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="org.sufficientlysecure.keychain.ui.PREFS_ADV" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+ <activity
+ android:name=".ui.PreferencesKeyServerActivity"
+ android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
+ android:label="@string/title_key_server_preference"
+ android:windowSoftInputMode="stateHidden" />
+ <activity
+ android:name=".ui.CertifyKeyActivity"
+ android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
+ android:label="@string/title_certify_key" />
+ <activity
+ android:name=".ui.ImportKeysActivity"
+ android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
+ android:label="@string/title_import_keys"
+ android:launchMode="singleTop"
+ android:windowSoftInputMode="stateHidden">
+
+ <!-- VIEW with fingerprint scheme:
+ Handle URIs with fingerprints when scanning directly from Barcode Scanner -->
+ <intent-filter android:label="@string/intent_import_key">
+ <action android:name="android.intent.action.VIEW" />
+
+ <category android:name="android.intent.category.BROWSABLE" />
+ <category android:name="android.intent.category.DEFAULT" />
+
+ <!-- Android's scheme matcher is case-sensitive, so include most likely variations -->
+ <data android:scheme="openpgp4fpr" />
+ <data android:scheme="OPENPGP4FPR" />
+ <data android:scheme="OpenPGP4FPR" />
+ <data android:scheme="OpenPGP4Fpr" />
+ <data android:scheme="OpenPGP4fpr" />
+ </intent-filter>
+ <!-- VIEW with mimeType: Allows to import keys (attached to emails) from email apps -->
+ <intent-filter android:label="@string/intent_import_key">
+ <action android:name="android.intent.action.VIEW" />
+
+ <category android:name="android.intent.category.BROWSABLE" />
+ <category android:name="android.intent.category.DEFAULT" />
+
+ <!-- mime type as defined in http://tools.ietf.org/html/rfc3156 -->
+ <data android:mimeType="application/pgp-keys" />
+ </intent-filter>
+ <!-- NFC: Handle NFC tags detected from outside our application -->
+ <intent-filter>
+ <action android:name="android.nfc.action.NDEF_DISCOVERED" />
+
+ <category android:name="android.intent.category.DEFAULT" />
+ <!-- mime type as defined in http://tools.ietf.org/html/rfc3156 -->
+ <data android:mimeType="application/pgp-keys" />
+ </intent-filter>
+ <!-- VIEW with file endings: *.gpg (e.g. to import from OI File Manager) -->
+ <intent-filter android:label="@string/intent_import_key">
+ <action android:name="android.intent.action.VIEW" />
+
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+
+ <data android:host="*" />
+ <data android:scheme="file" />
+ <data android:scheme="content" />
+ <data android:pathPattern=".*\\.gpg" />
+ <data android:pathPattern=".*\\..*\\.gpg" />
+ <data android:pathPattern=".*\\..*\\..*\\.gpg" />
+ <data android:pathPattern=".*\\..*\\..*\\..*\\.gpg" />
+ <data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.gpg" />
+ <data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
+ <data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
+ <data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
+ <data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
+ <data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
+ </intent-filter>
+ <!-- VIEW with file endings: *.asc -->
+ <intent-filter android:label="@string/intent_import_key">
+ <action android:name="android.intent.action.VIEW" />
+
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+
+ <data android:host="*" />
+ <data android:scheme="file" />
+ <data android:scheme="content" />
+ <data android:mimeType="*/*" />
+ <data android:pathPattern=".*\\.asc" />
+ <data android:pathPattern=".*\\..*\\.asc" />
+ <data android:pathPattern=".*\\..*\\..*\\.asc" />
+ <data android:pathPattern=".*\\..*\\..*\\..*\\.asc" />
+ <data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.asc" />
+ <data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.asc" />
+ <data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\.asc" />
+ <data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.asc" />
+ <data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.asc" />
+ <data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.asc" />
+ </intent-filter>
+ <!-- Keychain's own Actions -->
+ <!-- IMPORT_KEY with files TODO: does this work? -->
+ <intent-filter android:label="@string/intent_import_key">
+ <action android:name="org.sufficientlysecure.keychain.action.IMPORT_KEY" />
+
+ <category android:name="android.intent.category.DEFAULT" />
+
+ <data android:mimeType="*/*" />
+ </intent-filter>
+ <!-- IMPORT_KEY with mimeType 'application/pgp-keys' -->
+ <intent-filter>
+ <action android:name="org.sufficientlysecure.keychain.action.IMPORT_KEY" />
+
+ <category android:name="android.intent.category.DEFAULT" />
+ <!-- mime type as defined in http://tools.ietf.org/html/rfc3156, section 7 -->
+ <data android:mimeType="application/pgp-keys" />
+ </intent-filter>
+ <!-- IMPORT_KEY without mimeType to allow import with extras Bundle -->
+ <intent-filter android:label="@string/intent_import_key">
+ <action android:name="org.sufficientlysecure.keychain.action.IMPORT_KEY" />
+ <action android:name="org.sufficientlysecure.keychain.action.IMPORT_KEY_FROM_QR_CODE" />
+ <action android:name="org.sufficientlysecure.keychain.action.IMPORT_KEY_FROM_KEYSERVER" />
+
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <meta-data
+ android:name="android.support.PARENT_ACTIVITY"
+ android:value=".ui.KeyListActivity" />
+ </activity>
+ <activity
+ android:name=".ui.HelpActivity"
+ android:label="@string/title_help" />
+
+ <!-- Internal services/content providers (not exported) -->
+ <service
+ android:name=".service.PassphraseCacheService"
+ android:exported="false"
+ android:process=":passphrase_cache" />
+ <service
+ android:name=".service.KeychainIntentService"
+ android:exported="false" />
+
+ <provider
+ android:name=".provider.KeychainProvider"
+ android:authorities="org.sufficientlysecure.keychain.provider"
+ android:exported="false" />
+
+ <!-- Internal classes of the remote APIs (not exported) -->
+ <activity
+ android:name=".remote.ui.RemoteServiceActivity"
+ android:exported="false"
+ android:label="@string/app_name"
+ android:launchMode="singleTop"
+ android:process=":remote_api" />
+ <activity
+ android:name=".remote.ui.AppsListActivity"
+ android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
+ android:exported="false"
+ android:label="@string/title_api_registered_apps" />
+ <activity
+ android:name=".remote.ui.AppSettingsActivity"
+ android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
+ android:exported="false">
+
+ <meta-data
+ android:name="android.support.PARENT_ACTIVITY"
+ android:value=".remote.ui.AppsListActivity" />
+ </activity>
+ <activity
+ android:name=".remote.ui.AccountSettingsActivity"
+ android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
+ android:exported="false" />
+
+ <!-- OpenPGP Remote API -->
+ <service
+ android:name=".remote.OpenPgpService"
+ android:enabled="true"
+ android:exported="true"
+ android:process=":remote_api">
+ <intent-filter>
+ <action android:name="org.openintents.openpgp.IOpenPgpService" />
+ </intent-filter>
+ </service>
+
+ <!-- Extended Remote API -->
+ <!--<service-->
+ <!--android:name="org.sufficientlysecure.keychain.service.remote.ExtendedApiService"-->
+ <!--android:enabled="true"-->
+ <!--android:exported="true"-->
+ <!--android:process=":remote_api">-->
+ <!--<intent-filter>-->
+ <!--<action android:name="org.sufficientlysecure.keychain.service.remote.IExtendedApiService" />-->
+ <!--</intent-filter>-->
+ <!--</service>-->
+
+ <!-- TODO: authority! Make this API with content provider uris -->
+ <!-- <provider -->
+ <!-- android:name="org.sufficientlysecure.keychain.provider.KeychainServiceBlobProvider" -->
+ <!-- android:authorities="org.sufficientlysecure.keychain.provider.KeychainServiceBlobProvider" -->
+ <!-- android:permission="org.sufficientlysecure.keychain.permission.ACCESS_API" /> -->
+ </application>
+
+</manifest>
diff --git a/OpenKeychain/src/main/assets/fontawesome-webfont.ttf b/OpenKeychain/src/main/assets/fontawesome-webfont.ttf
new file mode 100644
index 000000000..7ec2e1de8
--- /dev/null
+++ b/OpenKeychain/src/main/assets/fontawesome-webfont.ttf
Binary files differ
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java
new file mode 100644
index 000000000..b3bfaa229
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
+ *
+ * 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;
+
+import android.os.Environment;
+
+import org.spongycastle.jce.provider.BouncyCastleProvider;
+import org.sufficientlysecure.keychain.remote.ui.AppsListActivity;
+import org.sufficientlysecure.keychain.ui.DecryptActivity;
+import org.sufficientlysecure.keychain.ui.EncryptActivity;
+import org.sufficientlysecure.keychain.ui.ImportKeysActivity;
+import org.sufficientlysecure.keychain.ui.KeyListActivity;
+
+public final class Constants {
+
+ public static final boolean DEBUG = BuildConfig.DEBUG;
+
+ public static final String TAG = "Keychain";
+
+ public static final String PACKAGE_NAME = "org.sufficientlysecure.keychain";
+
+ // as defined in http://tools.ietf.org/html/rfc3156, section 7
+ public static final String NFC_MIME = "application/pgp-keys";
+
+ // used by QR Codes (Guardian Project, Monkeysphere compatiblity)
+ public static final String FINGERPRINT_SCHEME = "openpgp4fpr";
+
+ // Not BC due to the use of Spongy Castle for Android
+ public static final String SC = BouncyCastleProvider.PROVIDER_NAME;
+ public static final String BOUNCY_CASTLE_PROVIDER_NAME = SC;
+
+ public static final String INTENT_PREFIX = PACKAGE_NAME + ".action.";
+
+ public static final class Path {
+ public static final String APP_DIR = Environment.getExternalStorageDirectory()
+ + "/OpenKeychain";
+ public static final String APP_DIR_FILE = APP_DIR + "/export.asc";
+ }
+
+ public static final class Pref {
+ public static final String DEFAULT_ENCRYPTION_ALGORITHM = "defaultEncryptionAlgorithm";
+ public static final String DEFAULT_HASH_ALGORITHM = "defaultHashAlgorithm";
+ public static final String DEFAULT_ASCII_ARMOR = "defaultAsciiArmor";
+ public static final String DEFAULT_MESSAGE_COMPRESSION = "defaultMessageCompression";
+ public static final String DEFAULT_FILE_COMPRESSION = "defaultFileCompression";
+ public static final String PASSPHRASE_CACHE_TTL = "passphraseCacheTtl";
+ public static final String LANGUAGE = "language";
+ public static final String FORCE_V3_SIGNATURES = "forceV3Signatures";
+ public static final String KEY_SERVERS = "keyServers";
+ }
+
+ public static final class Defaults {
+ public static final String KEY_SERVERS = "pool.sks-keyservers.net, subkeys.pgp.net, pgp.mit.edu";
+ }
+
+ public static final class DrawerItems {
+ public static final Class KEY_LIST = KeyListActivity.class;
+ public static final Class ENCRYPT = EncryptActivity.class;
+ public static final Class DECRYPT = DecryptActivity.class;
+ public static final Class REGISTERED_APPS_LIST = AppsListActivity.class;
+ public static final Class[] ARRAY = new Class[]{
+ KEY_LIST,
+ ENCRYPT,
+ DECRYPT,
+ REGISTERED_APPS_LIST
+ };
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Id.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Id.java
new file mode 100644
index 000000000..784ec340e
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Id.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
+ *
+ * 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;
+
+import org.spongycastle.bcpg.CompressionAlgorithmTags;
+
+/**
+ *
+ * TODO:
+ *
+ * - refactor ids, some are not needed and can be done with xml
+ *
+ */
+public final class Id {
+
+ public static final class menu {
+
+ public static final class option {
+ public static final int new_passphrase = 0x21070001;
+ public static final int create = 0x21070002;
+ public static final int about = 0x21070003;
+ public static final int manage_public_keys = 0x21070004;
+ public static final int manage_secret_keys = 0x21070005;
+ public static final int export_keys = 0x21070007;
+ public static final int preferences = 0x21070008;
+ public static final int search = 0x21070009;
+ public static final int help = 0x21070010;
+ public static final int key_server = 0x21070011;
+ public static final int scanQRCode = 0x21070012;
+ public static final int encrypt = 0x21070013;
+ public static final int encrypt_to_clipboard = 0x21070014;
+ public static final int decrypt = 0x21070015;
+ public static final int reply = 0x21070016;
+ public static final int cancel = 0x21070017;
+ public static final int save = 0x21070018;
+ public static final int okay = 0x21070019;
+ public static final int import_from_file = 0x21070020;
+ public static final int import_from_qr_code = 0x21070021;
+ public static final int import_from_nfc = 0x21070022;
+ public static final int crypto_consumers = 0x21070023;
+ public static final int createExpert = 0x21070024;
+ }
+ }
+
+ // use only lower 16 bits due to compatibility lib
+ public static final class message {
+ public static final int progress_update = 0x00006001;
+ public static final int done = 0x00006002;
+ public static final int import_keys = 0x00006003;
+ public static final int export_keys = 0x00006004;
+ public static final int import_done = 0x00006005;
+ public static final int export_done = 0x00006006;
+ public static final int create_key = 0x00006007;
+ public static final int edit_key = 0x00006008;
+ public static final int delete_done = 0x00006009;
+ public static final int query_done = 0x00006010;
+ public static final int unknown_signature_key = 0x00006011;
+ }
+
+ // use only lower 16 bits due to compatibility lib
+ public static final class request {
+ public static final int public_keys = 0x00007001;
+ public static final int secret_keys = 0x00007002;
+ public static final int filename = 0x00007003;
+// public static final int output_filename = 0x00007004;
+ public static final int key_server_preference = 0x00007005;
+// public static final int look_up_key_id = 0x00007006;
+ public static final int export_to_server = 0x00007007;
+ public static final int import_from_qr_code = 0x00007008;
+ public static final int sign_key = 0x00007009;
+ }
+
+ public static final class dialog {
+ public static final int passphrase = 0x21070001;
+ public static final int encrypting = 0x21070002;
+ public static final int decrypting = 0x21070003;
+ public static final int new_passphrase = 0x21070004;
+ public static final int passphrases_do_not_match = 0x21070005;
+ public static final int no_passphrase = 0x21070006;
+ public static final int saving = 0x21070007;
+ public static final int delete_key = 0x21070008;
+ public static final int import_keys = 0x21070009;
+ public static final int importing = 0x2107000a;
+ public static final int export_key = 0x2107000b;
+ public static final int export_keys = 0x2107000c;
+ public static final int exporting = 0x2107000d;
+ public static final int new_account = 0x2107000e;
+ public static final int change_log = 0x21070010;
+ public static final int output_filename = 0x21070011;
+ public static final int delete_file = 0x21070012;
+ public static final int deleting = 0x21070013;
+ public static final int help = 0x21070014;
+ public static final int querying = 0x21070015;
+ public static final int lookup_unknown_key = 0x21070016;
+ public static final int signing = 0x21070017;
+ }
+
+ public static final class task {
+ public static final int import_keys = 0x21070001;
+ public static final int export_keys = 0x21070002;
+ }
+
+ public static final class type {
+ public static final int public_key = 0x21070001;
+ public static final int secret_key = 0x21070002;
+ public static final int user_id = 0x21070003;
+ public static final int key = 0x21070004;
+ public static final int public_secret_key = 0x21070005;
+ }
+
+ public static final class choice {
+ public static final class algorithm {
+ public static final int dsa = 0x21070001;
+ public static final int elgamal = 0x21070002;
+ public static final int rsa = 0x21070003;
+ }
+
+ public static final class compression {
+ public static final int none = 0x21070001;
+ public static final int zlib = CompressionAlgorithmTags.ZLIB;
+ public static final int bzip2 = CompressionAlgorithmTags.BZIP2;
+ public static final int zip = CompressionAlgorithmTags.ZIP;
+ }
+
+ public static final class usage {
+ public static final int sign_only = 0x21070001;
+ public static final int encrypt_only = 0x21070002;
+ public static final int sign_and_encrypt = 0x21070003;
+ }
+
+ public static final class action {
+ public static final int encrypt = 0x21070001;
+ public static final int decrypt = 0x21070002;
+ public static final int import_public = 0x21070003;
+ public static final int import_secret = 0x21070004;
+ }
+ }
+
+ public static final class return_value {
+ public static final int ok = 0;
+ public static final int error = -1;
+ public static final int no_master_key = -2;
+ public static final int updated = 1;
+ public static final int bad = -3;
+ }
+
+ public static final class target {
+ public static final int clipboard = 0x21070001;
+ public static final int email = 0x21070002;
+ public static final int file = 0x21070003;
+ public static final int message = 0x21070004;
+ }
+
+ public static final class mode {
+ public static final int undefined = 0x21070001;
+ public static final int byte_array = 0x21070002;
+ public static final int file = 0x21070003;
+ public static final int stream = 0x21070004;
+ }
+
+ public static final class key {
+ public static final int none = 0;
+ public static final int symmetric = -1;
+ }
+
+ public static final class content {
+ public static final int unknown = 0;
+ public static final int encrypted_data = 1;
+ public static final int keys = 2;
+ }
+
+ public static final class keyserver {
+ public static final int search = 0x21070001;
+ public static final int get = 0x21070002;
+ public static final int add = 0x21070003;
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java
new file mode 100644
index 000000000..9b80e76f3
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java
@@ -0,0 +1,75 @@
+/*
+ * 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;
+
+import android.app.Application;
+import android.os.Environment;
+
+import org.spongycastle.jce.provider.BouncyCastleProvider;
+
+import org.sufficientlysecure.keychain.util.Log;
+import org.sufficientlysecure.keychain.util.PRNGFixes;
+
+import java.io.File;
+import java.security.Provider;
+import java.security.Security;
+
+public class KeychainApplication extends Application {
+
+ /**
+ * Called when the application is starting, before any activity, service, or receiver objects
+ * (excluding content providers) have been created.
+ */
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ /*
+ * Sets Bouncy (Spongy) Castle as preferred security provider
+ *
+ * insertProviderAt() position starts from 1
+ */
+ Security.insertProviderAt(new BouncyCastleProvider(), 1);
+
+ /*
+ * apply RNG fixes
+ *
+ * among other things, executes Security.insertProviderAt(new
+ * LinuxPRNGSecureRandomProvider(), 1) for Android <= SDK 17
+ */
+ PRNGFixes.apply();
+ Log.d(Constants.TAG, "Bouncy Castle set and PRNG Fixes applied!");
+
+ if (Constants.DEBUG) {
+ Provider[] providers = Security.getProviders();
+ Log.d(Constants.TAG, "Installed Security Providers:");
+ for (Provider p : providers) {
+ Log.d(Constants.TAG, "provider class: " + p.getClass().getName());
+ }
+ }
+
+ // Create APG directory on sdcard if not existing
+ if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+ File dir = new File(Constants.Path.APP_DIR);
+ if (!dir.exists() && !dir.mkdirs()) {
+ // ignore this for now, it's not crucial
+ // that the directory doesn't exist at this point
+ }
+ }
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/ClipboardReflection.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/ClipboardReflection.java
new file mode 100644
index 000000000..1cac5762d
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/ClipboardReflection.java
@@ -0,0 +1,95 @@
+/*
+ * 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.compatibility;
+
+import android.content.Context;
+
+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);
+ }
+ }
+
+ /**
+ * 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);
+
+ 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);
+ 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
new file mode 100644
index 000000000..36f7fd23b
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/DialogFragmentWorkaround.java
@@ -0,0 +1,68 @@
+/*
+ * 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.compatibility;
+
+import android.os.Build;
+import android.os.Handler;
+
+/**
+ * Bug on Android >= 4.2
+ *
+ * http://code.google.com/p/android/issues/detail?id=41901
+ *
+ * DialogFragment disappears on pressing home and comming back. This also happens especially in
+ * FileDialogFragment after launching a file manager and coming back.
+ *
+ * Usage: <code>
+ * DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() {
+ * public void run() {
+ * // show dialog...
+ * }
+ * });
+ * </code>
+ */
+public class DialogFragmentWorkaround {
+ public static final SDKLevel17Interface INTERFACE =
+ ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) ? new SDKLevel17Impl()
+ : new SDKLevelPriorLevel17Impl());
+
+ private static final int RUNNABLE_DELAY = 300;
+
+ public interface SDKLevel17Interface {
+ // Workaround for http://code.google.com/p/android/issues/detail?id=41901
+ void runnableRunDelayed(Runnable runnable);
+ }
+
+ private static class SDKLevelPriorLevel17Impl implements SDKLevel17Interface {
+ @Override
+ public void runnableRunDelayed(Runnable runnable) {
+ runnable.run();
+ }
+ }
+
+ private static class SDKLevel17Impl implements SDKLevel17Interface {
+ @Override
+ public void runnableRunDelayed(Runnable runnable) {
+ new Handler().postDelayed(runnable, RUNNABLE_DELAY);
+ }
+ }
+
+ // Can't instantiate this class
+ private DialogFragmentWorkaround() {
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/ListFragmentWorkaround.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/ListFragmentWorkaround.java
new file mode 100644
index 000000000..1cb91c688
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/ListFragmentWorkaround.java
@@ -0,0 +1,37 @@
+/*
+ * 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.compatibility;
+
+import android.support.v4.app.ListFragment;
+import android.view.View;
+import android.widget.ListView;
+
+/**
+ * Bug on Android >= 4.1
+ * <p/>
+ * http://code.google.com/p/android/issues/detail?id=35885
+ * <p/>
+ * Items are not checked in layout
+ */
+public class ListFragmentWorkaround extends ListFragment {
+
+ @Override
+ public void onListItemClick(ListView l, View v, int position, long id) {
+ l.setItemChecked(position, l.isItemChecked(position));
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/ActionBarHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/ActionBarHelper.java
new file mode 100644
index 000000000..a26df556d
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/ActionBarHelper.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.helper;
+
+import android.app.Activity;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.ActionBarActivity;
+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.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.util.Log;
+
+public class ActionBarHelper {
+
+ /**
+ * Set actionbar without home button if called from another app
+ *
+ * @param activity
+ */
+ public static void setBackButton(ActionBarActivity activity) {
+ final ActionBar actionBar = activity.getSupportActionBar();
+ Log.d(Constants.TAG, "calling package (only set when using startActivityForResult)="
+ + activity.getCallingPackage());
+ if (activity.getCallingPackage() != null
+ && activity.getCallingPackage().equals(Constants.PACKAGE_NAME)) {
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ actionBar.setHomeButtonEnabled(true);
+ } else {
+ actionBar.setDisplayHomeAsUpEnabled(false);
+ actionBar.setHomeButtonEnabled(false);
+ }
+ }
+
+ /**
+ * Sets custom view on ActionBar for Done/Cancel activities
+ *
+ * @param actionBar
+ * @param firstText
+ * @param firstDrawableId
+ * @param firstOnClickListener
+ * @param secondText
+ * @param secondDrawableId
+ * @param secondOnClickListener
+ */
+ public static void setTwoButtonView(ActionBar actionBar,
+ int firstText, int firstDrawableId, OnClickListener firstOnClickListener,
+ int secondText, int secondDrawableId, OnClickListener secondOnClickListener) {
+
+ // Inflate the custom action bar view
+ final LayoutInflater inflater = (LayoutInflater) actionBar.getThemedContext()
+ .getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
+ final View customActionBarView = inflater.inflate(
+ R.layout.actionbar_custom_view_done_cancel, null);
+
+ TextView firstTextView = ((TextView) customActionBarView.findViewById(R.id.actionbar_done_text));
+ firstTextView.setText(firstText);
+ firstTextView.setCompoundDrawablesWithIntrinsicBounds(firstDrawableId, 0, 0, 0);
+ customActionBarView.findViewById(R.id.actionbar_done).setOnClickListener(
+ firstOnClickListener);
+ TextView secondTextView = ((TextView) customActionBarView.findViewById(R.id.actionbar_cancel_text));
+ secondTextView.setText(secondText);
+ secondTextView.setCompoundDrawablesWithIntrinsicBounds(secondDrawableId, 0, 0, 0);
+ customActionBarView.findViewById(R.id.actionbar_cancel).setOnClickListener(
+ secondOnClickListener);
+
+ // Show the custom action bar view and hide the normal Home icon and title.
+ actionBar.setDisplayShowTitleEnabled(false);
+ actionBar.setDisplayShowHomeEnabled(false);
+ actionBar.setDisplayShowCustomEnabled(true);
+ actionBar.setCustomView(customActionBarView, new ActionBar.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
+ }
+
+ /**
+ * Sets custom view on ActionBar for Done activities
+ *
+ * @param actionBar
+ * @param firstText
+ * @param firstOnClickListener
+ */
+ public static void setOneButtonView(ActionBar actionBar, int firstText, int firstDrawableId,
+ OnClickListener firstOnClickListener) {
+ // Inflate a "Done" custom action bar view to serve as the "Up" affordance.
+ final LayoutInflater inflater = (LayoutInflater) actionBar.getThemedContext()
+ .getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
+ final View customActionBarView = inflater
+ .inflate(R.layout.actionbar_custom_view_done, null);
+
+ TextView firstTextView = ((TextView) customActionBarView.findViewById(R.id.actionbar_done_text));
+ firstTextView.setText(firstText);
+ firstTextView.setCompoundDrawablesWithIntrinsicBounds(firstDrawableId, 0, 0, 0);
+ customActionBarView.findViewById(R.id.actionbar_done).setOnClickListener(
+ firstOnClickListener);
+
+ // Show the custom action bar view and hide the normal Home icon and title.
+ actionBar.setDisplayShowTitleEnabled(false);
+ actionBar.setDisplayShowHomeEnabled(false);
+ actionBar.setDisplayShowCustomEnabled(true);
+ actionBar.setCustomView(customActionBarView);
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/ContactHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/ContactHelper.java
new file mode 100644
index 000000000..f8ed21816
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/ContactHelper.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * 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.helper;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.content.Context;
+import android.util.Patterns;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class ContactHelper {
+
+ public static final List<String> getMailAccounts(Context context) {
+ final Account[] accounts = AccountManager.get(context).getAccounts();
+ final Set<String> emailSet = new HashSet<String>();
+ for (Account account : accounts) {
+ if (Patterns.EMAIL_ADDRESS.matcher(account.name).matches()) {
+ emailSet.add(account.name);
+ }
+ }
+ return new ArrayList<String>(emailSet);
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/ExportHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/ExportHelper.java
new file mode 100644
index 000000000..810f22d8e
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/ExportHelper.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * 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.helper;
+
+import android.app.ProgressDialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Messenger;
+import android.support.v7.app.ActionBarActivity;
+import android.widget.Toast;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.Id;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.service.KeychainIntentService;
+import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
+import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment;
+import org.sufficientlysecure.keychain.ui.dialog.FileDialogFragment;
+import org.sufficientlysecure.keychain.util.Log;
+
+public class ExportHelper {
+ protected FileDialogFragment mFileDialog;
+ protected String mExportFilename;
+
+ ActionBarActivity mActivity;
+
+ public ExportHelper(ActionBarActivity activity) {
+ super();
+ this.mActivity = activity;
+ }
+
+ public void deleteKey(Uri dataUri, Handler deleteHandler) {
+ // Create a new Messenger for the communication back
+ Messenger messenger = new Messenger(deleteHandler);
+ long masterKeyId = ProviderHelper.getMasterKeyId(mActivity, dataUri);
+
+ DeleteKeyDialogFragment deleteKeyDialog = DeleteKeyDialogFragment.newInstance(messenger,
+ new long[]{ masterKeyId });
+ deleteKeyDialog.show(mActivity.getSupportFragmentManager(), "deleteKeyDialog");
+ }
+
+ /**
+ * Show dialog where to export keys
+ */
+ public void showExportKeysDialog(final long[] masterKeyIds, final String exportFilename,
+ final boolean showSecretCheckbox) {
+ mExportFilename = exportFilename;
+
+ // Message is received after file is selected
+ Handler returnHandler = new Handler() {
+ @Override
+ public void handleMessage(Message message) {
+ if (message.what == FileDialogFragment.MESSAGE_OKAY) {
+ Bundle data = message.getData();
+ mExportFilename = data.getString(FileDialogFragment.MESSAGE_DATA_FILENAME);
+
+ exportKeys(masterKeyIds, data.getBoolean(FileDialogFragment.MESSAGE_DATA_CHECKED));
+ }
+ }
+ };
+
+ // Create a new Messenger for the communication back
+ final Messenger messenger = new Messenger(returnHandler);
+
+ DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() {
+ public void run() {
+ String title = null;
+ if (masterKeyIds == null) {
+ // export all keys
+ title = mActivity.getString(R.string.title_export_keys);
+ } else {
+ // export only key specified at data uri
+ 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;
+
+ mFileDialog = FileDialogFragment.newInstance(messenger, title, message,
+ exportFilename, checkMsg);
+
+ mFileDialog.show(mActivity.getSupportFragmentManager(), "fileDialog");
+ }
+ });
+ }
+
+ /**
+ * Export keys
+ */
+ public void exportKeys(long[] masterKeyIds, boolean exportSecret) {
+ Log.d(Constants.TAG, "exportKeys started");
+
+ // 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, mExportFilename);
+ 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
+ KeychainIntentServiceHandler exportHandler = new KeychainIntentServiceHandler(mActivity,
+ mActivity.getString(R.string.progress_exporting),
+ ProgressDialog.STYLE_HORIZONTAL,
+ true,
+ new DialogInterface.OnCancelListener() {
+ @Override
+ public void onCancel(DialogInterface dialogInterface) {
+ mActivity.stopService(intent);
+ }
+ }) {
+ public void handleMessage(Message message) {
+ // handle messages by standard KeychainIntentServiceHandler first
+ super.handleMessage(message);
+
+ if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
+ // get returned data bundle
+ Bundle returnData = message.getData();
+
+ int exported = returnData.getInt(KeychainIntentService.RESULT_EXPORT);
+ String toastMessage;
+ if (exported == 1) {
+ toastMessage = mActivity.getString(R.string.key_exported);
+ } else if (exported > 0) {
+ toastMessage = mActivity.getString(R.string.keys_exported, exported);
+ } else {
+ toastMessage = mActivity.getString(R.string.no_keys_exported);
+ }
+ Toast.makeText(mActivity, toastMessage, Toast.LENGTH_SHORT).show();
+
+ }
+ }
+ };
+
+ // Create a new Messenger for the communication back
+ Messenger messenger = new Messenger(exportHandler);
+ intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
+
+ // show progress dialog
+ exportHandler.showProgressDialog(mActivity);
+
+ // start service with intent
+ mActivity.startService(intent);
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/FileHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/FileHelper.java
new file mode 100644
index 000000000..d24aeca52
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/FileHelper.java
@@ -0,0 +1,131 @@
+/*
+ * 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.helper;
+
+import android.app.Activity;
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Environment;
+import android.support.v4.app.Fragment;
+import android.widget.Toast;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.util.Log;
+
+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;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Opens the preferred installed file manager on Android and shows a toast if no manager is
+ * installed.
+ *
+ * @param activity
+ * @param filename default selected file, 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(Activity activity, String filename, String mimeType, int requestCode) {
+ Intent intent = buildFileIntent(filename, mimeType);
+
+ try {
+ activity.startActivityForResult(intent, requestCode);
+ } catch (ActivityNotFoundException e) {
+ // No compatible file manager was found.
+ Toast.makeText(activity, R.string.no_filemanager_installed, Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ public static void openFile(Fragment fragment, String filename, String mimeType, int requestCode) {
+ Intent intent = buildFileIntent(filename, mimeType);
+
+ try {
+ fragment.startActivityForResult(intent, requestCode);
+ } catch (ActivityNotFoundException e) {
+ // No compatible file manager was found.
+ Toast.makeText(fragment.getActivity(), R.string.no_filemanager_installed,
+ Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ private static Intent buildFileIntent(String filename, String mimeType) {
+ Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+ intent.addCategory(Intent.CATEGORY_OPENABLE);
+
+ intent.setData(Uri.parse("file://" + filename));
+ intent.setType(mimeType);
+
+ return intent;
+ }
+
+ /**
+ * Get a file path from a Uri.
+ * <p/>
+ * from https://github.com/iPaulPro/aFileChooser/blob/master/aFileChooser/src/com/ipaulpro/
+ * afilechooser/utils/FileUtils.java
+ *
+ * @param context
+ * @param uri
+ * @return
+ * @author paulburke
+ */
+ public static String getPath(Context context, Uri uri) {
+ Log.d(Constants.TAG + " File -",
+ "Authority: " + uri.getAuthority() + ", Fragment: " + uri.getFragment()
+ + ", Port: " + uri.getPort() + ", Query: " + uri.getQuery() + ", Scheme: "
+ + uri.getScheme() + ", Host: " + uri.getHost() + ", Segments: "
+ + uri.getPathSegments().toString());
+
+ if ("content".equalsIgnoreCase(uri.getScheme())) {
+ String[] projection = {"_data"};
+ Cursor cursor = null;
+
+ try {
+ cursor = context.getContentResolver().query(uri, projection, null, null, null);
+ int columnIndex = cursor.getColumnIndexOrThrow("_data");
+ if (cursor.moveToFirst()) {
+ return cursor.getString(columnIndex);
+ }
+ } catch (Exception e) {
+ // Eat it
+ }
+ } else if ("file".equalsIgnoreCase(uri.getScheme())) {
+ return uri.getPath();
+ }
+
+ return null;
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/OtherHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/OtherHelper.java
new file mode 100644
index 000000000..b31a889f0
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/OtherHelper.java
@@ -0,0 +1,70 @@
+/*
+ * 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.helper;
+
+import android.os.Bundle;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+
+import android.text.style.StrikethroughSpan;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.util.Log;
+
+import java.util.Iterator;
+import java.util.Set;
+
+public class OtherHelper {
+
+ /**
+ * Logs bundle content to debug for inspecting the content
+ *
+ * @param bundle
+ * @param bundleName
+ */
+ public static void logDebugBundle(Bundle bundle, String bundleName) {
+ if (Constants.DEBUG) {
+ if (bundle != null) {
+ Set<String> ks = bundle.keySet();
+ Iterator<String> iterator = ks.iterator();
+
+ Log.d(Constants.TAG, "Bundle " + bundleName + ":");
+ Log.d(Constants.TAG, "------------------------------");
+ while (iterator.hasNext()) {
+ String key = iterator.next();
+ Object value = bundle.get(key);
+
+ if (value != null) {
+ Log.d(Constants.TAG, key + " : " + value.toString());
+ } else {
+ Log.d(Constants.TAG, key + " : null");
+ }
+ }
+ Log.d(Constants.TAG, "------------------------------");
+ } else {
+ Log.d(Constants.TAG, "Bundle " + bundleName + ": null");
+ }
+ }
+ }
+
+ public static SpannableStringBuilder strikeOutText(CharSequence text) {
+ SpannableStringBuilder sb = new SpannableStringBuilder(text);
+ sb.setSpan(new StrikethroughSpan(), 0, text.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ return sb;
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/Preferences.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/Preferences.java
new file mode 100644
index 000000000..ca5555fea
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/Preferences.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2012 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
+ *
+ * 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.helper;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import org.spongycastle.bcpg.HashAlgorithmTags;
+import org.spongycastle.openpgp.PGPEncryptedData;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.Id;
+
+import java.util.Vector;
+
+/**
+ * Singleton Implementation of a Preference Helper
+ */
+public class Preferences {
+ private static Preferences sPreferences;
+ private SharedPreferences mSharedPreferences;
+
+ public static synchronized Preferences getPreferences(Context context) {
+ return getPreferences(context, false);
+ }
+
+ public static synchronized Preferences getPreferences(Context context, boolean forceNew) {
+ if (sPreferences == null || forceNew) {
+ sPreferences = new Preferences(context);
+ }
+ return sPreferences;
+ }
+
+ private Preferences(Context context) {
+ mSharedPreferences = context.getSharedPreferences("APG.main", Context.MODE_PRIVATE);
+ }
+
+ public String getLanguage() {
+ return mSharedPreferences.getString(Constants.Pref.LANGUAGE, "");
+ }
+
+ public void setLanguage(String value) {
+ SharedPreferences.Editor editor = mSharedPreferences.edit();
+ editor.putString(Constants.Pref.LANGUAGE, value);
+ editor.commit();
+ }
+
+ public long getPassphraseCacheTtl() {
+ int ttl = mSharedPreferences.getInt(Constants.Pref.PASSPHRASE_CACHE_TTL, 180);
+ // fix the value if it was set to "never" in previous versions, which currently is not
+ // supported
+ if (ttl == 0) {
+ ttl = 180;
+ }
+ return (long) ttl;
+ }
+
+ public void setPassphraseCacheTtl(int value) {
+ SharedPreferences.Editor editor = mSharedPreferences.edit();
+ editor.putInt(Constants.Pref.PASSPHRASE_CACHE_TTL, value);
+ editor.commit();
+ }
+
+ public int getDefaultEncryptionAlgorithm() {
+ return mSharedPreferences.getInt(Constants.Pref.DEFAULT_ENCRYPTION_ALGORITHM,
+ PGPEncryptedData.AES_256);
+ }
+
+ public void setDefaultEncryptionAlgorithm(int value) {
+ SharedPreferences.Editor editor = mSharedPreferences.edit();
+ editor.putInt(Constants.Pref.DEFAULT_ENCRYPTION_ALGORITHM, value);
+ editor.commit();
+ }
+
+ public int getDefaultHashAlgorithm() {
+ return mSharedPreferences.getInt(Constants.Pref.DEFAULT_HASH_ALGORITHM,
+ HashAlgorithmTags.SHA512);
+ }
+
+ public void setDefaultHashAlgorithm(int value) {
+ SharedPreferences.Editor editor = mSharedPreferences.edit();
+ editor.putInt(Constants.Pref.DEFAULT_HASH_ALGORITHM, value);
+ editor.commit();
+ }
+
+ public int getDefaultMessageCompression() {
+ return mSharedPreferences.getInt(Constants.Pref.DEFAULT_MESSAGE_COMPRESSION,
+ Id.choice.compression.zlib);
+ }
+
+ public void setDefaultMessageCompression(int value) {
+ SharedPreferences.Editor editor = mSharedPreferences.edit();
+ editor.putInt(Constants.Pref.DEFAULT_MESSAGE_COMPRESSION, value);
+ editor.commit();
+ }
+
+ public int getDefaultFileCompression() {
+ return mSharedPreferences.getInt(Constants.Pref.DEFAULT_FILE_COMPRESSION,
+ Id.choice.compression.none);
+ }
+
+ public void setDefaultFileCompression(int value) {
+ SharedPreferences.Editor editor = mSharedPreferences.edit();
+ editor.putInt(Constants.Pref.DEFAULT_FILE_COMPRESSION, value);
+ editor.commit();
+ }
+
+ public boolean getDefaultAsciiArmor() {
+ return mSharedPreferences.getBoolean(Constants.Pref.DEFAULT_ASCII_ARMOR, false);
+ }
+
+ public void setDefaultAsciiArmor(boolean value) {
+ SharedPreferences.Editor editor = mSharedPreferences.edit();
+ editor.putBoolean(Constants.Pref.DEFAULT_ASCII_ARMOR, value);
+ editor.commit();
+ }
+
+ public boolean getForceV3Signatures() {
+ return mSharedPreferences.getBoolean(Constants.Pref.FORCE_V3_SIGNATURES, false);
+ }
+
+ public void setForceV3Signatures(boolean value) {
+ SharedPreferences.Editor editor = mSharedPreferences.edit();
+ editor.putBoolean(Constants.Pref.FORCE_V3_SIGNATURES, value);
+ editor.commit();
+ }
+
+ public String[] getKeyServers() {
+ String rawData = mSharedPreferences.getString(Constants.Pref.KEY_SERVERS,
+ Constants.Defaults.KEY_SERVERS);
+ Vector<String> servers = new Vector<String>();
+ String chunks[] = rawData.split(",");
+ for (String c : chunks) {
+ String tmp = c.trim();
+ if (tmp.length() > 0) {
+ servers.add(tmp);
+ }
+ }
+ return servers.toArray(chunks);
+ }
+
+ public void setKeyServers(String[] value) {
+ SharedPreferences.Editor editor = mSharedPreferences.edit();
+ String rawData = "";
+ for (String v : value) {
+ String tmp = v.trim();
+ if (tmp.length() == 0) {
+ continue;
+ }
+ if (!"".equals(rawData)) {
+ rawData += ",";
+ }
+ rawData += tmp;
+ }
+ editor.putString(Constants.Pref.KEY_SERVERS, rawData);
+ editor.commit();
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpConversionHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpConversionHelper.java
new file mode 100644
index 000000000..c6c62d649
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpConversionHelper.java
@@ -0,0 +1,200 @@
+/*
+ * 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.pgp;
+
+import org.spongycastle.openpgp.PGPKeyRing;
+import org.spongycastle.openpgp.PGPObjectFactory;
+import org.spongycastle.openpgp.PGPSecretKey;
+import org.spongycastle.openpgp.PGPSecretKeyRing;
+import org.spongycastle.openpgp.PGPSignature;
+import org.spongycastle.openpgp.PGPSignatureList;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.util.Log;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+
+public class PgpConversionHelper {
+
+ /**
+ * Convert from byte[] to PGPKeyRing
+ *
+ * @param keysBytes
+ * @return
+ */
+ public static PGPKeyRing BytesToPGPKeyRing(byte[] keysBytes) {
+ PGPObjectFactory factory = new PGPObjectFactory(keysBytes);
+ PGPKeyRing keyRing = null;
+ try {
+ if ((keyRing = (PGPKeyRing) factory.nextObject()) == null) {
+ Log.e(Constants.TAG, "No keys given!");
+ }
+ } catch (IOException e) {
+ Log.e(Constants.TAG, "Error while converting to PGPKeyRing!", e);
+ }
+
+ return keyRing;
+ }
+
+ /**
+ * Convert from byte[] to ArrayList<PGPSecretKey>
+ *
+ * @param keysBytes
+ * @return
+ */
+ public static ArrayList<PGPSecretKey> BytesToPGPSecretKeyList(byte[] keysBytes) {
+ PGPObjectFactory factory = new PGPObjectFactory(keysBytes);
+ Object obj = null;
+ ArrayList<PGPSecretKey> keys = new ArrayList<PGPSecretKey>();
+ try {
+ while ((obj = factory.nextObject()) != null) {
+ PGPSecretKey secKey = null;
+ if (obj instanceof PGPSecretKey) {
+ secKey = (PGPSecretKey) obj;
+ if (secKey == null) {
+ Log.e(Constants.TAG, "No keys given!");
+ }
+ keys.add(secKey);
+ } else if (obj instanceof PGPSecretKeyRing) { //master keys are sent as keyrings
+ PGPSecretKeyRing keyRing = null;
+ keyRing = (PGPSecretKeyRing) obj;
+ if (keyRing == null) {
+ Log.e(Constants.TAG, "No keys given!");
+ }
+ @SuppressWarnings("unchecked")
+ Iterator<PGPSecretKey> itr = keyRing.getSecretKeys();
+ while (itr.hasNext()) {
+ keys.add(itr.next());
+ }
+ }
+ }
+ } catch (IOException e) {
+ }
+
+ return keys;
+ }
+
+ /**
+ * Convert from byte[] to PGPSecretKey
+ * <p/>
+ * Singles keys are encoded as keyRings with one single key in it by Bouncy Castle
+ *
+ * @param keyBytes
+ * @return
+ */
+ public static PGPSecretKey BytesToPGPSecretKey(byte[] keyBytes) {
+ PGPObjectFactory factory = new PGPObjectFactory(keyBytes);
+ Object obj = null;
+ try {
+ obj = factory.nextObject();
+ } catch (IOException e) {
+ Log.e(Constants.TAG, "Error while converting to PGPSecretKey!", e);
+ }
+ PGPSecretKey secKey = null;
+ if (obj instanceof PGPSecretKey) {
+ if ((secKey = (PGPSecretKey) obj) == null) {
+ Log.e(Constants.TAG, "No keys given!");
+ }
+ } else if (obj instanceof PGPSecretKeyRing) { //master keys are sent as keyrings
+ PGPSecretKeyRing keyRing = null;
+ if ((keyRing = (PGPSecretKeyRing) obj) == null) {
+ Log.e(Constants.TAG, "No keys given!");
+ }
+ secKey = keyRing.getSecretKey();
+ }
+
+ return secKey;
+ }
+
+ /**
+ * Convert from byte[] to PGPSignature
+ *
+ * @param sigBytes
+ * @return
+ */
+ public static PGPSignature BytesToPGPSignature(byte[] sigBytes) {
+ PGPObjectFactory factory = new PGPObjectFactory(sigBytes);
+ PGPSignatureList signatures = null;
+ try {
+ if ((signatures = (PGPSignatureList) factory.nextObject()) == null || signatures.isEmpty()) {
+ Log.e(Constants.TAG, "No signatures given!");
+ return null;
+ }
+ } catch (IOException e) {
+ Log.e(Constants.TAG, "Error while converting to PGPSignature!", e);
+ return null;
+ }
+
+ return signatures.get(0);
+ }
+
+ /**
+ * Convert from ArrayList<PGPSecretKey> to byte[]
+ *
+ * @param keys
+ * @return
+ */
+ public static byte[] PGPSecretKeyArrayListToBytes(ArrayList<PGPSecretKey> keys) {
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ for (PGPSecretKey key : keys) {
+ try {
+ key.encode(os);
+ } catch (IOException e) {
+ Log.e(Constants.TAG, "Error while converting ArrayList<PGPSecretKey> to byte[]!", e);
+ }
+ }
+
+ return os.toByteArray();
+ }
+
+ /**
+ * Convert from PGPSecretKey to byte[]
+ *
+ * @param key
+ * @return
+ */
+ public static byte[] PGPSecretKeyToBytes(PGPSecretKey key) {
+ try {
+ return key.getEncoded();
+ } catch (IOException e) {
+ Log.e(Constants.TAG, "Encoding failed", e);
+
+ return null;
+ }
+ }
+
+ /**
+ * Convert from PGPSecretKeyRing to byte[]
+ *
+ * @param keyRing
+ * @return
+ */
+ public static byte[] PGPSecretKeyRingToBytes(PGPSecretKeyRing keyRing) {
+ try {
+ return keyRing.getEncoded();
+ } catch (IOException e) {
+ Log.e(Constants.TAG, "Encoding failed", e);
+
+ return null;
+ }
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java
new file mode 100644
index 000000000..8a0bf99d7
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java
@@ -0,0 +1,836 @@
+/*
+ * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
+ *
+ * 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.pgp;
+
+import android.content.Context;
+
+import org.openintents.openpgp.OpenPgpSignatureResult;
+import org.spongycastle.bcpg.ArmoredInputStream;
+import org.spongycastle.bcpg.SignatureSubpacketTags;
+import org.spongycastle.openpgp.PGPCompressedData;
+import org.spongycastle.openpgp.PGPEncryptedData;
+import org.spongycastle.openpgp.PGPEncryptedDataList;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPLiteralData;
+import org.spongycastle.openpgp.PGPObjectFactory;
+import org.spongycastle.openpgp.PGPOnePassSignature;
+import org.spongycastle.openpgp.PGPOnePassSignatureList;
+import org.spongycastle.openpgp.PGPPBEEncryptedData;
+import org.spongycastle.openpgp.PGPPrivateKey;
+import org.spongycastle.openpgp.PGPPublicKey;
+import org.spongycastle.openpgp.PGPPublicKeyEncryptedData;
+import org.spongycastle.openpgp.PGPPublicKeyRing;
+import org.spongycastle.openpgp.PGPSecretKey;
+import org.spongycastle.openpgp.PGPSecretKeyRing;
+import org.spongycastle.openpgp.PGPSignature;
+import org.spongycastle.openpgp.PGPSignatureList;
+import org.spongycastle.openpgp.PGPSignatureSubpacketVector;
+import org.spongycastle.openpgp.PGPUtil;
+import org.spongycastle.openpgp.operator.PBEDataDecryptorFactory;
+import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
+import org.spongycastle.openpgp.operator.PGPDigestCalculatorProvider;
+import org.spongycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
+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.JcePBESecretKeyDecryptorBuilder;
+import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
+import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.service.PassphraseCacheService;
+import org.sufficientlysecure.keychain.util.InputData;
+import org.sufficientlysecure.keychain.util.Log;
+import org.sufficientlysecure.keychain.util.ProgressDialogUpdater;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.SignatureException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Set;
+
+/**
+ * This class uses a Builder pattern!
+ */
+public class PgpDecryptVerify {
+ private Context mContext;
+ private InputData mData;
+ private OutputStream mOutStream;
+
+ private ProgressDialogUpdater mProgressDialogUpdater;
+ private boolean mAllowSymmetricDecryption;
+ private String mPassphrase;
+ private Set<Long> mAllowedKeyIds;
+
+ private PgpDecryptVerify(Builder builder) {
+ // private Constructor can only be called from Builder
+ this.mContext = builder.mContext;
+ this.mData = builder.mData;
+ this.mOutStream = builder.mOutStream;
+
+ this.mProgressDialogUpdater = builder.mProgressDialogUpdater;
+ this.mAllowSymmetricDecryption = builder.mAllowSymmetricDecryption;
+ this.mPassphrase = builder.mPassphrase;
+ this.mAllowedKeyIds = builder.mAllowedKeyIds;
+ }
+
+ public static class Builder {
+ // mandatory parameter
+ private Context mContext;
+ private InputData mData;
+ private OutputStream mOutStream;
+
+ // optional
+ private ProgressDialogUpdater mProgressDialogUpdater = null;
+ private boolean mAllowSymmetricDecryption = true;
+ private String mPassphrase = null;
+ private Set<Long> mAllowedKeyIds = null;
+
+ public Builder(Context context, InputData data, OutputStream outStream) {
+ this.mContext = context;
+ this.mData = data;
+ this.mOutStream = outStream;
+ }
+
+ public Builder progressDialogUpdater(ProgressDialogUpdater progressDialogUpdater) {
+ this.mProgressDialogUpdater = progressDialogUpdater;
+ return this;
+ }
+
+ public Builder allowSymmetricDecryption(boolean allowSymmetricDecryption) {
+ this.mAllowSymmetricDecryption = allowSymmetricDecryption;
+ return this;
+ }
+
+ public Builder passphrase(String passphrase) {
+ this.mPassphrase = passphrase;
+ return this;
+ }
+
+ /**
+ * Allow these key ids alone for decryption.
+ * This means only ciphertexts encrypted for one of these private key can be decrypted.
+ *
+ * @param allowedKeyIds
+ * @return
+ */
+ public Builder allowedKeyIds(Set<Long> allowedKeyIds) {
+ this.mAllowedKeyIds = allowedKeyIds;
+ return this;
+ }
+
+ public PgpDecryptVerify build() {
+ return new PgpDecryptVerify(this);
+ }
+ }
+
+ public void updateProgress(int message, int current, int total) {
+ if (mProgressDialogUpdater != null) {
+ mProgressDialogUpdater.setProgress(message, current, total);
+ }
+ }
+
+ public void updateProgress(int current, int total) {
+ if (mProgressDialogUpdater != null) {
+ mProgressDialogUpdater.setProgress(current, total);
+ }
+ }
+
+ /**
+ * Decrypts and/or verifies data based on parameters of class
+ *
+ * @return
+ * @throws IOException
+ * @throws PgpGeneralException
+ * @throws PGPException
+ * @throws SignatureException
+ */
+ public PgpDecryptVerifyResult execute()
+ throws IOException, PgpGeneralException, PGPException, SignatureException {
+ // automatically works with ascii armor input and binary
+ InputStream in = PGPUtil.getDecoderStream(mData.getInputStream());
+ if (in instanceof ArmoredInputStream) {
+ ArmoredInputStream aIn = (ArmoredInputStream) in;
+ // it is ascii armored
+ Log.d(Constants.TAG, "ASCII Armor Header Line: " + aIn.getArmorHeaderLine());
+
+ if (aIn.isClearText()) {
+ // a cleartext signature, verify it with the other method
+ return verifyCleartextSignature(aIn);
+ }
+ // else: ascii armored encryption! go on...
+ }
+
+ return decryptVerify(in);
+ }
+
+ /**
+ * Decrypt and/or verifies binary or ascii armored pgp
+ *
+ * @param in
+ * @return
+ * @throws IOException
+ * @throws PgpGeneralException
+ * @throws PGPException
+ * @throws SignatureException
+ */
+ private PgpDecryptVerifyResult decryptVerify(InputStream in)
+ throws IOException, PgpGeneralException, PGPException, SignatureException {
+ PgpDecryptVerifyResult returnData = new PgpDecryptVerifyResult();
+
+ PGPObjectFactory pgpF = new PGPObjectFactory(in);
+ PGPEncryptedDataList enc;
+ Object o = pgpF.nextObject();
+
+ int currentProgress = 0;
+ updateProgress(R.string.progress_reading_data, currentProgress, 100);
+
+ if (o instanceof PGPEncryptedDataList) {
+ enc = (PGPEncryptedDataList) o;
+ } else {
+ enc = (PGPEncryptedDataList) pgpF.nextObject();
+ }
+
+ if (enc == null) {
+ throw new PgpGeneralException(mContext.getString(R.string.error_invalid_data));
+ }
+
+ InputStream clear;
+ PGPEncryptedData encryptedData;
+
+ currentProgress += 5;
+
+ PGPPublicKeyEncryptedData encryptedDataAsymmetric = null;
+ PGPPBEEncryptedData encryptedDataSymmetric = null;
+ PGPSecretKey secretKey = null;
+ Iterator<?> it = enc.getEncryptedDataObjects();
+ boolean symmetricPacketFound = false;
+ // find secret key
+ while (it.hasNext()) {
+ Object obj = it.next();
+ if (obj instanceof PGPPublicKeyEncryptedData) {
+ updateProgress(R.string.progress_finding_key, currentProgress, 100);
+
+ PGPPublicKeyEncryptedData encData = (PGPPublicKeyEncryptedData) obj;
+ long masterKeyId = ProviderHelper.getMasterKeyId(mContext,
+ KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(Long.toString(encData.getKeyID()))
+ );
+ PGPSecretKeyRing secretKeyRing = ProviderHelper.getPGPSecretKeyRing(mContext, masterKeyId);
+ if (secretKeyRing == null) {
+ throw new PgpGeneralException(mContext.getString(R.string.error_no_secret_key_found));
+ }
+ secretKey = secretKeyRing.getSecretKey(encData.getKeyID());
+ if (secretKey == null) {
+ throw new PgpGeneralException(mContext.getString(R.string.error_no_secret_key_found));
+ }
+ // secret key exists in database
+
+ // allow only a specific key for decryption?
+ if (mAllowedKeyIds != null) {
+ Log.d(Constants.TAG, "encData.getKeyID():" + encData.getKeyID());
+ Log.d(Constants.TAG, "allowedKeyIds: " + mAllowedKeyIds);
+ Log.d(Constants.TAG, "masterKeyId: " + masterKeyId);
+
+ if (!mAllowedKeyIds.contains(masterKeyId)) {
+ throw new PgpGeneralException(
+ mContext.getString(R.string.error_no_secret_key_found));
+ }
+ }
+
+ encryptedDataAsymmetric = encData;
+
+ // if no passphrase was explicitly set try to get it from the cache service
+ if (mPassphrase == null) {
+ // returns "" if key has no passphrase
+ mPassphrase =
+ PassphraseCacheService.getCachedPassphrase(mContext, masterKeyId);
+
+ // if passphrase was not cached, return here
+ // indicating that a passphrase is missing!
+ if (mPassphrase == null) {
+ returnData.setKeyIdPassphraseNeeded(masterKeyId);
+ returnData.setStatus(PgpDecryptVerifyResult.KEY_PASSHRASE_NEEDED);
+ return returnData;
+ }
+ }
+
+ // break out of while, only get first object here
+ // TODO???: There could be more pgp objects, which are not decrypted!
+ break;
+ } else if (mAllowSymmetricDecryption && obj instanceof PGPPBEEncryptedData) {
+ symmetricPacketFound = true;
+
+ encryptedDataSymmetric = (PGPPBEEncryptedData) obj;
+
+ // if no passphrase is given, return here
+ // indicating that a passphrase is missing!
+ if (mPassphrase == null) {
+ returnData.setStatus(PgpDecryptVerifyResult.SYMMETRIC_PASSHRASE_NEEDED);
+ return returnData;
+ }
+
+ // break out of while, only get first object here
+ // TODO???: There could be more pgp objects, which are not decrypted!
+ break;
+ }
+ }
+
+ if (symmetricPacketFound) {
+ updateProgress(R.string.progress_preparing_streams, currentProgress, 100);
+
+ PGPDigestCalculatorProvider digestCalcProvider = new JcaPGPDigestCalculatorProviderBuilder()
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build();
+ PBEDataDecryptorFactory decryptorFactory = new JcePBEDataDecryptorFactoryBuilder(
+ digestCalcProvider).setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
+ mPassphrase.toCharArray());
+
+ clear = encryptedDataSymmetric.getDataStream(decryptorFactory);
+
+ encryptedData = encryptedDataSymmetric;
+ currentProgress += 5;
+ } else {
+ if (secretKey == null) {
+ throw new PgpGeneralException(mContext.getString(R.string.error_no_secret_key_found));
+ }
+
+ currentProgress += 5;
+ updateProgress(R.string.progress_extracting_key, currentProgress, 100);
+ PGPPrivateKey privateKey;
+ try {
+ PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder()
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
+ mPassphrase.toCharArray());
+ privateKey = secretKey.extractPrivateKey(keyDecryptor);
+ } catch (PGPException e) {
+ throw new PGPException(mContext.getString(R.string.error_wrong_passphrase));
+ }
+ if (privateKey == null) {
+ throw new PgpGeneralException(
+ mContext.getString(R.string.error_could_not_extract_private_key));
+ }
+ currentProgress += 5;
+ updateProgress(R.string.progress_preparing_streams, currentProgress, 100);
+
+ PublicKeyDataDecryptorFactory decryptorFactory = new JcePublicKeyDataDecryptorFactoryBuilder()
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(privateKey);
+
+ clear = encryptedDataAsymmetric.getDataStream(decryptorFactory);
+
+ encryptedData = encryptedDataAsymmetric;
+ currentProgress += 5;
+ }
+
+ PGPObjectFactory plainFact = new PGPObjectFactory(clear);
+ Object dataChunk = plainFact.nextObject();
+ PGPOnePassSignature signature = null;
+ OpenPgpSignatureResult signatureResult = null;
+ PGPPublicKey signatureKey = null;
+ int signatureIndex = -1;
+
+ if (dataChunk instanceof PGPCompressedData) {
+ updateProgress(R.string.progress_decompressing_data, currentProgress, 100);
+
+ PGPObjectFactory fact = new PGPObjectFactory(
+ ((PGPCompressedData) dataChunk).getDataStream());
+ dataChunk = fact.nextObject();
+ plainFact = fact;
+ currentProgress += 10;
+ }
+
+ long signatureKeyId = 0;
+ if (dataChunk instanceof PGPOnePassSignatureList) {
+ updateProgress(R.string.progress_processing_signature, currentProgress, 100);
+
+ signatureResult = new OpenPgpSignatureResult();
+ PGPOnePassSignatureList sigList = (PGPOnePassSignatureList) dataChunk;
+ for (int i = 0; i < sigList.size(); ++i) {
+ signature = sigList.get(i);
+ signatureKey = ProviderHelper
+ .getPGPPublicKeyRing(mContext, signature.getKeyID()).getPublicKey();
+ if (signatureKeyId == 0) {
+ signatureKeyId = signature.getKeyID();
+ }
+ if (signatureKey == null) {
+ signature = null;
+ } else {
+ signatureIndex = i;
+ signatureKeyId = signature.getKeyID();
+ String userId = null;
+ PGPPublicKeyRing signKeyRing = ProviderHelper.getPGPPublicKeyRingWithKeyId(
+ mContext, signatureKeyId);
+ if (signKeyRing != null) {
+ userId = PgpKeyHelper.getMainUserId(signKeyRing.getPublicKey());
+ }
+ signatureResult.setUserId(userId);
+ break;
+ }
+ }
+
+ signatureResult.setKeyId(signatureKeyId);
+
+ if (signature != null) {
+ JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider =
+ new JcaPGPContentVerifierBuilderProvider()
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
+
+ signature.init(contentVerifierBuilderProvider, signatureKey);
+ } else {
+ signatureResult.setStatus(OpenPgpSignatureResult.SIGNATURE_UNKNOWN_PUB_KEY);
+ }
+
+ dataChunk = plainFact.nextObject();
+ currentProgress += 10;
+ }
+
+ if (dataChunk instanceof PGPSignatureList) {
+ dataChunk = plainFact.nextObject();
+ }
+
+ if (dataChunk instanceof PGPLiteralData) {
+ updateProgress(R.string.progress_decrypting, currentProgress, 100);
+
+ PGPLiteralData literalData = (PGPLiteralData) dataChunk;
+
+ byte[] buffer = new byte[1 << 16];
+ InputStream dataIn = literalData.getInputStream();
+
+ int startProgress = currentProgress;
+ int endProgress = 100;
+ if (signature != null) {
+ endProgress = 90;
+ } else if (encryptedData.isIntegrityProtected()) {
+ endProgress = 95;
+ }
+
+ int n;
+ // TODO: progress calculation is broken here! Try to rework it based on commented code!
+// int progress = 0;
+ long startPos = mData.getStreamPosition();
+ while ((n = dataIn.read(buffer)) > 0) {
+ mOutStream.write(buffer, 0, n);
+// progress += n;
+ if (signature != null) {
+ try {
+ signature.update(buffer, 0, n);
+ } catch (SignatureException e) {
+ signatureResult.setStatus(OpenPgpSignatureResult.SIGNATURE_ERROR);
+ signature = null;
+ }
+ }
+ // TODO: dead code?!
+ // unknown size, but try to at least have a moving, slowing down progress bar
+// currentProgress = startProgress + (endProgress - startProgress) * progress
+// / (progress + 100000);
+ if (mData.getSize() - startPos == 0) {
+ currentProgress = endProgress;
+ } else {
+ currentProgress = (int) (startProgress + (endProgress - startProgress)
+ * (mData.getStreamPosition() - startPos) / (mData.getSize() - startPos));
+ }
+ updateProgress(currentProgress, 100);
+ }
+
+ if (signature != null) {
+ updateProgress(R.string.progress_verifying_signature, 90, 100);
+
+ PGPSignatureList signatureList = (PGPSignatureList) plainFact.nextObject();
+ PGPSignature messageSignature = signatureList.get(signatureIndex);
+
+ // these are not cleartext signatures!
+ // TODO: what about binary signatures?
+ signatureResult.setSignatureOnly(false);
+
+ //Now check binding signatures
+ boolean validKeyBinding = verifyKeyBinding(mContext, messageSignature, signatureKey);
+ boolean validSignature = signature.verify(messageSignature);
+
+ // TODO: implement CERTIFIED!
+ if (validKeyBinding & validSignature) {
+ signatureResult.setStatus(OpenPgpSignatureResult.SIGNATURE_SUCCESS_UNCERTIFIED);
+ }
+ }
+ }
+
+ if (encryptedData.isIntegrityProtected()) {
+ updateProgress(R.string.progress_verifying_integrity, 95, 100);
+
+ if (encryptedData.verify()) {
+ // passed
+ Log.d(Constants.TAG, "Integrity verification: success!");
+ } else {
+ // failed
+ Log.d(Constants.TAG, "Integrity verification: failed!");
+ throw new PgpGeneralException(mContext.getString(R.string.error_integrity_check_failed));
+ }
+ } else {
+ // no integrity check
+ Log.e(Constants.TAG, "Encrypted data was not integrity protected!");
+ // TODO: inform user?
+ }
+
+ updateProgress(R.string.progress_done, 100, 100);
+
+ returnData.setSignatureResult(signatureResult);
+ return returnData;
+ }
+
+ /**
+ * This method verifies cleartext signatures
+ * as defined in http://tools.ietf.org/html/rfc4880#section-7
+ * <p/>
+ * The method is heavily based on
+ * pg/src/main/java/org/spongycastle/openpgp/examples/ClearSignedFileProcessor.java
+ *
+ * @return
+ * @throws IOException
+ * @throws PgpGeneralException
+ * @throws PGPException
+ * @throws SignatureException
+ */
+ private PgpDecryptVerifyResult verifyCleartextSignature(ArmoredInputStream aIn)
+ throws IOException, PgpGeneralException, PGPException, SignatureException {
+ PgpDecryptVerifyResult returnData = new PgpDecryptVerifyResult();
+ OpenPgpSignatureResult signatureResult = new OpenPgpSignatureResult();
+ // cleartext signatures are never encrypted ;)
+ signatureResult.setSignatureOnly(true);
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ updateProgress(R.string.progress_done, 0, 100);
+
+ ByteArrayOutputStream lineOut = new ByteArrayOutputStream();
+ int lookAhead = readInputLine(lineOut, aIn);
+ byte[] lineSep = getLineSeparator();
+
+ byte[] line = lineOut.toByteArray();
+ out.write(line, 0, getLengthWithoutSeparator(line));
+ out.write(lineSep);
+
+ while (lookAhead != -1 && aIn.isClearText()) {
+ lookAhead = readInputLine(lineOut, lookAhead, aIn);
+ line = lineOut.toByteArray();
+ out.write(line, 0, getLengthWithoutSeparator(line));
+ out.write(lineSep);
+ }
+
+ out.close();
+
+ byte[] clearText = out.toByteArray();
+ mOutStream.write(clearText);
+
+ updateProgress(R.string.progress_processing_signature, 60, 100);
+ PGPObjectFactory pgpFact = new PGPObjectFactory(aIn);
+
+ PGPSignatureList sigList = (PGPSignatureList) pgpFact.nextObject();
+ if (sigList == null) {
+ throw new PgpGeneralException(mContext.getString(R.string.error_corrupt_data));
+ }
+ PGPSignature signature = null;
+ long signatureKeyId = 0;
+ PGPPublicKey signatureKey = null;
+ for (int i = 0; i < sigList.size(); ++i) {
+
+ signature = sigList.get(i);
+ signatureKeyId = signature.getKeyID();
+
+ // find data about this subkey
+ HashMap<String, Object> data = ProviderHelper.getGenericData(mContext,
+ KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(Long.toString(signature.getKeyID())),
+ new String[] { KeyRings.MASTER_KEY_ID, KeyRings.USER_ID },
+ new int[] { ProviderHelper.FIELD_TYPE_INTEGER, ProviderHelper.FIELD_TYPE_STRING });
+ // any luck? otherwise, try next.
+ if(data.get(KeyRings.MASTER_KEY_ID) == null) {
+ signature = null;
+ // do NOT reset signatureKeyId, that one is shown when no known one is found!
+ continue;
+ }
+
+ // this one can't fail now (yay database constraints)
+ signatureKey = ProviderHelper.getPGPPublicKeyRing(mContext, (Long) data.get(KeyRings.MASTER_KEY_ID)).getPublicKey();
+ signatureResult.setUserId((String) data.get(KeyRings.USER_ID));
+
+ break;
+ }
+
+ signatureResult.setKeyId(signatureKeyId);
+
+ if (signature == null) {
+ signatureResult.setStatus(OpenPgpSignatureResult.SIGNATURE_UNKNOWN_PUB_KEY);
+ returnData.setSignatureResult(signatureResult);
+
+ updateProgress(R.string.progress_done, 100, 100);
+ return returnData;
+ }
+
+ JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider =
+ new JcaPGPContentVerifierBuilderProvider()
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
+
+ signature.init(contentVerifierBuilderProvider, signatureKey);
+
+ InputStream sigIn = new BufferedInputStream(new ByteArrayInputStream(clearText));
+
+ lookAhead = readInputLine(lineOut, sigIn);
+
+ processLine(signature, lineOut.toByteArray());
+
+ if (lookAhead != -1) {
+ do {
+ lookAhead = readInputLine(lineOut, lookAhead, sigIn);
+
+ signature.update((byte) '\r');
+ signature.update((byte) '\n');
+
+ processLine(signature, lineOut.toByteArray());
+ } while (lookAhead != -1);
+ }
+
+ //Now check binding signatures
+ boolean validKeyBinding = verifyKeyBinding(mContext, signature, signatureKey);
+ boolean validSignature = signature.verify();
+
+ if (validSignature & validKeyBinding) {
+ signatureResult.setStatus(OpenPgpSignatureResult.SIGNATURE_SUCCESS_UNCERTIFIED);
+ }
+
+ // TODO: what about SIGNATURE_SUCCESS_CERTIFIED and SIGNATURE_ERROR????
+
+ returnData.setSignatureResult(signatureResult);
+
+ updateProgress(R.string.progress_done, 100, 100);
+ return returnData;
+ }
+
+ private static boolean verifyKeyBinding(Context context,
+ PGPSignature signature, PGPPublicKey signatureKey) {
+ long signatureKeyId = signature.getKeyID();
+ boolean validKeyBinding = false;
+
+ PGPPublicKeyRing signKeyRing = ProviderHelper.getPGPPublicKeyRingWithKeyId(context,
+ signatureKeyId);
+ PGPPublicKey mKey = null;
+ if (signKeyRing != null) {
+ mKey = signKeyRing.getPublicKey();
+ }
+
+ if (signature.getKeyID() != mKey.getKeyID()) {
+ validKeyBinding = verifyKeyBinding(mKey, signatureKey);
+ } else { //if the key used to make the signature was the master key, no need to check binding sigs
+ validKeyBinding = true;
+ }
+ return validKeyBinding;
+ }
+
+ private static boolean verifyKeyBinding(PGPPublicKey masterPublicKey, PGPPublicKey signingPublicKey) {
+ boolean validSubkeyBinding = false;
+ boolean validTempSubkeyBinding = false;
+ boolean validPrimaryKeyBinding = false;
+
+ JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider =
+ new JcaPGPContentVerifierBuilderProvider()
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
+
+ Iterator<PGPSignature> itr = signingPublicKey.getSignatures();
+
+ while (itr.hasNext()) { //what does gpg do if the subkey binding is wrong?
+ //gpg has an invalid subkey binding error on key import I think, but doesn't shout
+ //about keys without subkey signing. Can't get it to import a slightly broken one
+ //either, so we will err on bad subkey binding here.
+ PGPSignature sig = itr.next();
+ if (sig.getKeyID() == masterPublicKey.getKeyID() &&
+ sig.getSignatureType() == PGPSignature.SUBKEY_BINDING) {
+ //check and if ok, check primary key binding.
+ try {
+ sig.init(contentVerifierBuilderProvider, masterPublicKey);
+ validTempSubkeyBinding = sig.verifyCertification(masterPublicKey, signingPublicKey);
+ } catch (PGPException e) {
+ continue;
+ } catch (SignatureException e) {
+ continue;
+ }
+
+ if (validTempSubkeyBinding) {
+ validSubkeyBinding = true;
+ }
+ if (validTempSubkeyBinding) {
+ validPrimaryKeyBinding = verifyPrimaryKeyBinding(sig.getUnhashedSubPackets(),
+ masterPublicKey, signingPublicKey);
+ if (validPrimaryKeyBinding) {
+ break;
+ }
+ validPrimaryKeyBinding = verifyPrimaryKeyBinding(sig.getHashedSubPackets(),
+ masterPublicKey, signingPublicKey);
+ if (validPrimaryKeyBinding) {
+ break;
+ }
+ }
+ }
+ }
+ return (validSubkeyBinding & validPrimaryKeyBinding);
+ }
+
+ private static boolean verifyPrimaryKeyBinding(PGPSignatureSubpacketVector pkts,
+ PGPPublicKey masterPublicKey,
+ PGPPublicKey signingPublicKey) {
+ boolean validPrimaryKeyBinding = false;
+ JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider =
+ new JcaPGPContentVerifierBuilderProvider()
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
+ PGPSignatureList eSigList;
+
+ if (pkts.hasSubpacket(SignatureSubpacketTags.EMBEDDED_SIGNATURE)) {
+ try {
+ eSigList = pkts.getEmbeddedSignatures();
+ } catch (IOException e) {
+ return false;
+ } catch (PGPException e) {
+ return false;
+ }
+ for (int j = 0; j < eSigList.size(); ++j) {
+ PGPSignature emSig = eSigList.get(j);
+ if (emSig.getSignatureType() == PGPSignature.PRIMARYKEY_BINDING) {
+ try {
+ emSig.init(contentVerifierBuilderProvider, signingPublicKey);
+ validPrimaryKeyBinding = emSig.verifyCertification(masterPublicKey, signingPublicKey);
+ if (validPrimaryKeyBinding) {
+ break;
+ }
+ } catch (PGPException e) {
+ continue;
+ } catch (SignatureException e) {
+ continue;
+ }
+ }
+ }
+ }
+
+ return validPrimaryKeyBinding;
+ }
+
+ /**
+ * Mostly taken from ClearSignedFileProcessor in Bouncy Castle
+ *
+ * @param sig
+ * @param line
+ * @throws SignatureException
+ */
+ private static void processLine(PGPSignature sig, byte[] line)
+ throws SignatureException {
+ int length = getLengthWithoutWhiteSpace(line);
+ if (length > 0) {
+ sig.update(line, 0, length);
+ }
+ }
+
+ private static int readInputLine(ByteArrayOutputStream bOut, InputStream fIn)
+ throws IOException {
+ bOut.reset();
+
+ int lookAhead = -1;
+ int ch;
+
+ while ((ch = fIn.read()) >= 0) {
+ bOut.write(ch);
+ if (ch == '\r' || ch == '\n') {
+ lookAhead = readPassedEOL(bOut, ch, fIn);
+ break;
+ }
+ }
+
+ return lookAhead;
+ }
+
+ private static int readInputLine(ByteArrayOutputStream bOut, int lookAhead, InputStream fIn)
+ throws IOException {
+ bOut.reset();
+
+ int ch = lookAhead;
+
+ do {
+ bOut.write(ch);
+ if (ch == '\r' || ch == '\n') {
+ lookAhead = readPassedEOL(bOut, ch, fIn);
+ break;
+ }
+ } while ((ch = fIn.read()) >= 0);
+
+ if (ch < 0) {
+ lookAhead = -1;
+ }
+
+ return lookAhead;
+ }
+
+ private static int readPassedEOL(ByteArrayOutputStream bOut, int lastCh, InputStream fIn)
+ throws IOException {
+ int lookAhead = fIn.read();
+
+ if (lastCh == '\r' && lookAhead == '\n') {
+ bOut.write(lookAhead);
+ lookAhead = fIn.read();
+ }
+
+ return lookAhead;
+ }
+
+ private static int getLengthWithoutSeparator(byte[] line) {
+ int end = line.length - 1;
+
+ while (end >= 0 && isLineEnding(line[end])) {
+ end--;
+ }
+
+ return end + 1;
+ }
+
+ private static boolean isLineEnding(byte b) {
+ return b == '\r' || b == '\n';
+ }
+
+ private static int getLengthWithoutWhiteSpace(byte[] line) {
+ int end = line.length - 1;
+
+ while (end >= 0 && isWhiteSpace(line[end])) {
+ end--;
+ }
+
+ return end + 1;
+ }
+
+ private static boolean isWhiteSpace(byte b) {
+ return b == '\r' || b == '\n' || b == '\t' || b == ' ';
+ }
+
+ 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;
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyResult.java
new file mode 100644
index 000000000..ad240e834
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyResult.java
@@ -0,0 +1,93 @@
+/*
+ * 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.pgp;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import org.openintents.openpgp.OpenPgpSignatureResult;
+
+public class PgpDecryptVerifyResult implements Parcelable {
+ public static final int SUCCESS = 1;
+ public static final int KEY_PASSHRASE_NEEDED = 2;
+ public static final int SYMMETRIC_PASSHRASE_NEEDED = 3;
+
+ int mStatus;
+ long mKeyIdPassphraseNeeded;
+
+ OpenPgpSignatureResult mSignatureResult;
+
+ public int getStatus() {
+ return mStatus;
+ }
+
+ public void setStatus(int mStatus) {
+ this.mStatus = mStatus;
+ }
+
+ public long getKeyIdPassphraseNeeded() {
+ return mKeyIdPassphraseNeeded;
+ }
+
+ public void setKeyIdPassphraseNeeded(long mKeyIdPassphraseNeeded) {
+ this.mKeyIdPassphraseNeeded = mKeyIdPassphraseNeeded;
+ }
+
+ public OpenPgpSignatureResult getSignatureResult() {
+ return mSignatureResult;
+ }
+
+ public void setSignatureResult(OpenPgpSignatureResult signatureResult) {
+ this.mSignatureResult = signatureResult;
+ }
+
+ public PgpDecryptVerifyResult() {
+
+ }
+
+ public PgpDecryptVerifyResult(PgpDecryptVerifyResult b) {
+ this.mStatus = b.mStatus;
+ this.mKeyIdPassphraseNeeded = b.mKeyIdPassphraseNeeded;
+ this.mSignatureResult = b.mSignatureResult;
+ }
+
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mStatus);
+ dest.writeLong(mKeyIdPassphraseNeeded);
+ dest.writeParcelable(mSignatureResult, 0);
+ }
+
+ public static final Creator<PgpDecryptVerifyResult> CREATOR = new Creator<PgpDecryptVerifyResult>() {
+ public PgpDecryptVerifyResult createFromParcel(final Parcel source) {
+ PgpDecryptVerifyResult vr = new PgpDecryptVerifyResult();
+ vr.mStatus = source.readInt();
+ vr.mKeyIdPassphraseNeeded = source.readLong();
+ vr.mSignatureResult = source.readParcelable(OpenPgpSignatureResult.class.getClassLoader());
+ return vr;
+ }
+
+ public PgpDecryptVerifyResult[] newArray(final int size) {
+ return new PgpDecryptVerifyResult[size];
+ }
+ };
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java
new file mode 100644
index 000000000..f884b1776
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2012-2013 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
+ *
+ * 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.pgp;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+
+import org.spongycastle.openpgp.PGPEncryptedDataList;
+import org.spongycastle.openpgp.PGPObjectFactory;
+import org.spongycastle.openpgp.PGPPublicKeyEncryptedData;
+import org.spongycastle.openpgp.PGPPublicKeyRing;
+import org.spongycastle.openpgp.PGPSecretKey;
+import org.spongycastle.openpgp.PGPSecretKeyRing;
+import org.spongycastle.openpgp.PGPUtil;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.Id;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.pgp.exception.NoAsymmetricEncryptionException;
+import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.util.Log;
+import org.sufficientlysecure.keychain.util.ProgressDialogUpdater;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.RandomAccessFile;
+import java.security.SecureRandom;
+import java.util.Iterator;
+import java.util.regex.Pattern;
+
+public class PgpHelper {
+
+ public static final Pattern PGP_MESSAGE = Pattern.compile(
+ ".*?(-----BEGIN PGP MESSAGE-----.*?-----END PGP MESSAGE-----).*", Pattern.DOTALL);
+
+ public static final Pattern PGP_CLEARTEXT_SIGNATURE = Pattern
+ .compile(".*?(-----BEGIN PGP SIGNED MESSAGE-----.*?-----" +
+ "BEGIN PGP SIGNATURE-----.*?-----END PGP SIGNATURE-----).*",
+ Pattern.DOTALL);
+
+ public static final Pattern PGP_PUBLIC_KEY = Pattern.compile(
+ ".*?(-----BEGIN PGP PUBLIC KEY BLOCK-----.*?-----END PGP PUBLIC KEY BLOCK-----).*",
+ Pattern.DOTALL);
+
+ public static String getVersion(Context context) {
+ String version = null;
+ try {
+ PackageInfo pi = context.getPackageManager().getPackageInfo(Constants.PACKAGE_NAME, 0);
+ version = pi.versionName;
+ return version;
+ } catch (NameNotFoundException e) {
+ Log.e(Constants.TAG, "Version could not be retrieved!", e);
+ return "0.0.0";
+ }
+ }
+
+ public static String getFullVersion(Context context) {
+ return "OpenPGP Keychain v" + getVersion(context);
+ }
+
+ public static long getDecryptionKeyId(Context context, InputStream inputStream)
+ throws PgpGeneralException, NoAsymmetricEncryptionException, IOException {
+ InputStream in = PGPUtil.getDecoderStream(inputStream);
+ PGPObjectFactory pgpF = new PGPObjectFactory(in);
+ PGPEncryptedDataList enc;
+ Object o = pgpF.nextObject();
+
+ // the first object might be a PGP marker packet.
+ if (o instanceof PGPEncryptedDataList) {
+ enc = (PGPEncryptedDataList) o;
+ } else {
+ enc = (PGPEncryptedDataList) pgpF.nextObject();
+ }
+
+ if (enc == null) {
+ throw new PgpGeneralException(context.getString(R.string.error_invalid_data));
+ }
+
+ // TODO: currently we always only look at the first known key
+ // find the secret key
+ PGPSecretKey secretKey = null;
+ Iterator<?> it = enc.getEncryptedDataObjects();
+ boolean gotAsymmetricEncryption = false;
+ while (it.hasNext()) {
+ Object obj = it.next();
+ if (obj instanceof PGPPublicKeyEncryptedData) {
+ gotAsymmetricEncryption = true;
+ PGPPublicKeyEncryptedData pbe = (PGPPublicKeyEncryptedData) obj;
+ secretKey = ProviderHelper.getPGPSecretKeyRing(context, pbe.getKeyID()).getSecretKey();
+ if (secretKey != null) {
+ break;
+ }
+ }
+ }
+
+ if (!gotAsymmetricEncryption) {
+ throw new NoAsymmetricEncryptionException();
+ }
+
+ if (secretKey == null) {
+ return Id.key.none;
+ }
+
+ return secretKey.getKeyID();
+ }
+
+ public static int getStreamContent(Context context, InputStream inStream) throws IOException {
+ InputStream in = PGPUtil.getDecoderStream(inStream);
+ PGPObjectFactory pgpF = new PGPObjectFactory(in);
+ Object object = pgpF.nextObject();
+ while (object != null) {
+ if (object instanceof PGPPublicKeyRing || object instanceof PGPSecretKeyRing) {
+ return Id.content.keys;
+ } else if (object instanceof PGPEncryptedDataList) {
+ return Id.content.encrypted_data;
+ }
+ object = pgpF.nextObject();
+ }
+
+ return Id.content.unknown;
+ }
+
+ /**
+ * Generate a random filename
+ *
+ * @param length
+ * @return
+ */
+ public static String generateRandomFilename(int length) {
+ SecureRandom random = new SecureRandom();
+
+ byte bytes[] = new byte[length];
+ random.nextBytes(bytes);
+ String result = "";
+ for (int i = 0; i < length; ++i) {
+ int v = (bytes[i] + 256) % 64;
+ if (v < 10) {
+ result += (char) ('0' + v);
+ } else if (v < 36) {
+ result += (char) ('A' + v - 10);
+ } else if (v < 62) {
+ result += (char) ('a' + v - 36);
+ } else if (v == 62) {
+ result += '_';
+ } else if (v == 63) {
+ result += '.';
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Go once through stream to get length of stream. The length is later used to display progress
+ * when encrypting/decrypting
+ *
+ * @param in
+ * @return
+ * @throws IOException
+ */
+ public static long getLengthOfStream(InputStream in) throws IOException {
+ long size = 0;
+ long n = 0;
+ byte dummy[] = new byte[0x10000];
+ while ((n = in.read(dummy)) > 0) {
+ size += n;
+ }
+ return size;
+ }
+
+ /**
+ * Deletes file securely by overwriting it with random data before deleting it.
+ * <p/>
+ * TODO: Does this really help on flash storage?
+ *
+ * @param context
+ * @param progress
+ * @param file
+ * @throws IOException
+ */
+ public static void deleteFileSecurely(Context context, ProgressDialogUpdater progress, File file)
+ throws IOException {
+ long length = file.length();
+ SecureRandom random = new SecureRandom();
+ RandomAccessFile raf = new RandomAccessFile(file, "rws");
+ raf.seek(0);
+ raf.getFilePointer();
+ byte[] data = new byte[1 << 16];
+ int pos = 0;
+ String msg = context.getString(R.string.progress_deleting_securely, file.getName());
+ while (pos < length) {
+ if (progress != null) {
+ progress.setProgress(msg, (int) (100 * pos / length), 100);
+ }
+ random.nextBytes(data);
+ raf.write(data);
+ pos += data.length;
+ }
+ raf.close();
+ file.delete();
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java
new file mode 100644
index 000000000..d03f3ccc2
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2012-2013 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
+ *
+ * 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.pgp;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Environment;
+
+import org.spongycastle.bcpg.ArmoredOutputStream;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPKeyRing;
+import org.spongycastle.openpgp.PGPPublicKey;
+import org.spongycastle.openpgp.PGPPublicKeyRing;
+import org.spongycastle.openpgp.PGPSecretKey;
+import org.spongycastle.openpgp.PGPSecretKeyRing;
+import org.spongycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.Id;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.service.KeychainIntentService;
+import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry;
+import org.sufficientlysecure.keychain.util.HkpKeyServer;
+import org.sufficientlysecure.keychain.util.IterableIterator;
+import org.sufficientlysecure.keychain.util.KeyServer.AddKeyException;
+import org.sufficientlysecure.keychain.util.KeychainServiceListener;
+import org.sufficientlysecure.keychain.util.Log;
+import org.sufficientlysecure.keychain.util.ProgressDialogUpdater;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+public class PgpImportExport {
+
+ private Context mContext;
+ private ProgressDialogUpdater mProgress;
+
+ private KeychainServiceListener mKeychainServiceListener;
+
+ public PgpImportExport(Context context, ProgressDialogUpdater progress) {
+ super();
+ this.mContext = context;
+ this.mProgress = progress;
+ }
+
+ public PgpImportExport(Context context,
+ ProgressDialogUpdater progress, KeychainServiceListener keychainListener) {
+ super();
+ this.mContext = context;
+ this.mProgress = progress;
+ this.mKeychainServiceListener = keychainListener;
+ }
+
+ public void updateProgress(int message, int current, int total) {
+ if (mProgress != null) {
+ mProgress.setProgress(message, current, total);
+ }
+ }
+
+ public void updateProgress(String message, int current, int total) {
+ if (mProgress != null) {
+ mProgress.setProgress(message, current, total);
+ }
+ }
+
+ public void updateProgress(int current, int total) {
+ if (mProgress != null) {
+ mProgress.setProgress(current, total);
+ }
+ }
+
+ public boolean uploadKeyRingToServer(HkpKeyServer server, PGPPublicKeyRing keyring) {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ ArmoredOutputStream aos = null;
+ try {
+ aos = new ArmoredOutputStream(bos);
+ aos.write(keyring.getEncoded());
+ aos.close();
+
+ String armoredKey = bos.toString("UTF-8");
+ server.add(armoredKey);
+
+ return true;
+ } catch (IOException e) {
+ return false;
+ } catch (AddKeyException e) {
+ // TODO: tell the user?
+ return false;
+ } finally {
+ try {
+ if (aos != null) { aos.close(); }
+ if (bos != null) { bos.close(); }
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ /**
+ * Imports keys from given data. If keyIds is given only those are imported
+ */
+ public Bundle importKeyRings(List<ImportKeysListEntry> entries)
+ throws PgpGeneralException, PGPException, IOException {
+ Bundle returnData = new Bundle();
+
+ updateProgress(R.string.progress_importing, 0, 100);
+
+ int newKeys = 0;
+ int oldKeys = 0;
+ int badKeys = 0;
+
+ int position = 0;
+ try {
+ for (ImportKeysListEntry entry : entries) {
+ Object obj = PgpConversionHelper.BytesToPGPKeyRing(entry.getBytes());
+
+ if (obj instanceof PGPKeyRing) {
+ PGPKeyRing keyring = (PGPKeyRing) obj;
+
+ int status = storeKeyRingInCache(keyring);
+
+ if (status == Id.return_value.error) {
+ throw new PgpGeneralException(
+ mContext.getString(R.string.error_saving_keys));
+ }
+
+ // update the counts to display to the user at the end
+ if (status == Id.return_value.updated) {
+ ++oldKeys;
+ } else if (status == Id.return_value.ok) {
+ ++newKeys;
+ } else if (status == Id.return_value.bad) {
+ ++badKeys;
+ }
+ } else {
+ Log.e(Constants.TAG, "Object not recognized as PGPKeyRing!");
+ }
+
+ position++;
+ updateProgress(position / entries.size() * 100, 100);
+ }
+ } catch (Exception e) {
+ Log.e(Constants.TAG, "Exception on parsing key file!", e);
+ }
+
+ returnData.putInt(KeychainIntentService.RESULT_IMPORT_ADDED, newKeys);
+ returnData.putInt(KeychainIntentService.RESULT_IMPORT_UPDATED, oldKeys);
+ returnData.putInt(KeychainIntentService.RESULT_IMPORT_BAD, badKeys);
+
+ return returnData;
+ }
+
+ public Bundle exportKeyRings(ArrayList<Long> publicKeyRingMasterIds,
+ ArrayList<Long> secretKeyRingMasterIds,
+ OutputStream outStream) throws PgpGeneralException,
+ PGPException, IOException {
+ Bundle returnData = new Bundle();
+
+ int masterKeyIdsSize = publicKeyRingMasterIds.size() + secretKeyRingMasterIds.size();
+ int progress = 0;
+
+ updateProgress(
+ mContext.getResources().getQuantityString(R.plurals.progress_exporting_key,
+ masterKeyIdsSize), 0, 100);
+
+ if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+ throw new PgpGeneralException(
+ mContext.getString(R.string.error_external_storage_not_ready));
+ }
+ // For each public masterKey id
+ for (long pubKeyMasterId : publicKeyRingMasterIds) {
+ progress++;
+ // Create an output stream
+ ArmoredOutputStream arOutStream = new ArmoredOutputStream(outStream);
+ arOutStream.setHeader("Version", PgpHelper.getFullVersion(mContext));
+
+ updateProgress(progress * 100 / masterKeyIdsSize, 100);
+ PGPPublicKeyRing publicKeyRing =
+ ProviderHelper.getPGPPublicKeyRing(mContext, pubKeyMasterId);
+
+ if (publicKeyRing != null) {
+ publicKeyRing.encode(arOutStream);
+ }
+
+ if (mKeychainServiceListener.hasServiceStopped()) {
+ arOutStream.close();
+ return null;
+ }
+
+ arOutStream.close();
+ }
+
+ // For each secret masterKey id
+ for (long secretKeyMasterId : secretKeyRingMasterIds) {
+ progress++;
+ // Create an output stream
+ ArmoredOutputStream arOutStream = new ArmoredOutputStream(outStream);
+ arOutStream.setHeader("Version", PgpHelper.getFullVersion(mContext));
+
+ updateProgress(progress * 100 / masterKeyIdsSize, 100);
+ PGPSecretKeyRing secretKeyRing =
+ ProviderHelper.getPGPSecretKeyRing(mContext, secretKeyMasterId);
+
+ if (secretKeyRing != null) {
+ secretKeyRing.encode(arOutStream);
+ }
+ if (mKeychainServiceListener.hasServiceStopped()) {
+ arOutStream.close();
+ return null;
+ }
+
+ arOutStream.close();
+ }
+
+ returnData.putInt(KeychainIntentService.RESULT_EXPORT, masterKeyIdsSize);
+
+ updateProgress(R.string.progress_done, 100, 100);
+
+ return returnData;
+ }
+
+ /**
+ * TODO: implement Id.return_value.updated as status when key already existed
+ */
+ @SuppressWarnings("unchecked")
+ public int storeKeyRingInCache(PGPKeyRing keyring) {
+ int status = Integer.MIN_VALUE; // out of bounds value (Id.return_value.*)
+ try {
+ if (keyring instanceof PGPSecretKeyRing) {
+ PGPSecretKeyRing secretKeyRing = (PGPSecretKeyRing) keyring;
+ boolean save = true;
+
+ for (PGPSecretKey testSecretKey : new IterableIterator<PGPSecretKey>(
+ secretKeyRing.getSecretKeys())) {
+ if (!testSecretKey.isMasterKey()) {
+ if (testSecretKey.isPrivateKeyEmpty()) {
+ // this is bad, something is very wrong...
+ save = false;
+ status = Id.return_value.bad;
+ }
+ }
+ }
+
+ if (save) {
+ // TODO: preserve certifications
+ // (http://osdir.com/ml/encryption.bouncy-castle.devel/2007-01/msg00054.html ?)
+ PGPPublicKeyRing newPubRing = null;
+ for (PGPPublicKey key : new IterableIterator<PGPPublicKey>(
+ secretKeyRing.getPublicKeys())) {
+ if (newPubRing == null) {
+ newPubRing = new PGPPublicKeyRing(key.getEncoded(),
+ new JcaKeyFingerprintCalculator());
+ }
+ newPubRing = PGPPublicKeyRing.insertPublicKey(newPubRing, key);
+ }
+ if (newPubRing != null) {
+ ProviderHelper.saveKeyRing(mContext, newPubRing);
+ }
+ ProviderHelper.saveKeyRing(mContext, secretKeyRing);
+ // TODO: remove status returns, use exceptions!
+ status = Id.return_value.ok;
+ }
+ } else if (keyring instanceof PGPPublicKeyRing) {
+ PGPPublicKeyRing publicKeyRing = (PGPPublicKeyRing) keyring;
+ ProviderHelper.saveKeyRing(mContext, publicKeyRing);
+ // TODO: remove status returns, use exceptions!
+ status = Id.return_value.ok;
+ }
+ } catch (IOException e) {
+ status = Id.return_value.error;
+ }
+
+ return status;
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java
new file mode 100644
index 000000000..4c786f555
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java
@@ -0,0 +1,644 @@
+/*
+ * Copyright (C) 2012-2013 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
+ *
+ * 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.pgp;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.text.Spannable;
+import android.text.SpannableStringBuilder;
+import android.text.style.ForegroundColorSpan;
+
+import org.spongycastle.bcpg.sig.KeyFlags;
+import org.spongycastle.openpgp.PGPPublicKey;
+import org.spongycastle.openpgp.PGPPublicKeyRing;
+import org.spongycastle.openpgp.PGPSecretKey;
+import org.spongycastle.openpgp.PGPSecretKeyRing;
+import org.spongycastle.openpgp.PGPSignature;
+import org.spongycastle.openpgp.PGPSignatureSubpacketVector;
+import org.spongycastle.util.encoders.Hex;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.util.IterableIterator;
+import org.sufficientlysecure.keychain.util.Log;
+
+import java.security.DigestException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Locale;
+import java.util.Vector;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class PgpKeyHelper {
+
+ private static final Pattern USER_ID_PATTERN = Pattern.compile("^(.*?)(?: \\((.*)\\))?(?: <(.*)>)?$");
+
+ public static Date getCreationDate(PGPPublicKey key) {
+ return key.getCreationTime();
+ }
+
+ public static Date getCreationDate(PGPSecretKey key) {
+ return key.getPublicKey().getCreationTime();
+ }
+
+ @SuppressWarnings("unchecked")
+ public static PGPSecretKey getKeyNum(PGPSecretKeyRing keyRing, long num) {
+ long cnt = 0;
+ if (keyRing == null) {
+ return null;
+ }
+ for (PGPSecretKey key : new IterableIterator<PGPSecretKey>(keyRing.getSecretKeys())) {
+ if (cnt == num) {
+ return key;
+ }
+ cnt++;
+ }
+
+ return null;
+ }
+
+ @SuppressWarnings("unchecked")
+ public static Vector<PGPPublicKey> getEncryptKeys(PGPPublicKeyRing keyRing) {
+ Vector<PGPPublicKey> encryptKeys = new Vector<PGPPublicKey>();
+
+ for (PGPPublicKey key : new IterableIterator<PGPPublicKey>(keyRing.getPublicKeys())) {
+ if (isEncryptionKey(key)) {
+ encryptKeys.add(key);
+ }
+ }
+
+ return encryptKeys;
+ }
+
+ @SuppressWarnings("unchecked")
+ public static Vector<PGPSecretKey> getSigningKeys(PGPSecretKeyRing keyRing) {
+ Vector<PGPSecretKey> signingKeys = new Vector<PGPSecretKey>();
+
+ for (PGPSecretKey key : new IterableIterator<PGPSecretKey>(keyRing.getSecretKeys())) {
+ if (isSigningKey(key)) {
+ signingKeys.add(key);
+ }
+ }
+
+ return signingKeys;
+ }
+
+ @SuppressWarnings("unchecked")
+ public static Vector<PGPSecretKey> getCertificationKeys(PGPSecretKeyRing keyRing) {
+ Vector<PGPSecretKey> signingKeys = new Vector<PGPSecretKey>();
+
+ for (PGPSecretKey key : new IterableIterator<PGPSecretKey>(keyRing.getSecretKeys())) {
+ if (isCertificationKey(key)) {
+ signingKeys.add(key);
+ }
+ }
+
+ return signingKeys;
+ }
+
+ public static Vector<PGPPublicKey> getUsableEncryptKeys(PGPPublicKeyRing keyRing) {
+ Vector<PGPPublicKey> usableKeys = new Vector<PGPPublicKey>();
+ Vector<PGPPublicKey> encryptKeys = getEncryptKeys(keyRing);
+ PGPPublicKey masterKey = null;
+ for (int i = 0; i < encryptKeys.size(); ++i) {
+ PGPPublicKey key = encryptKeys.get(i);
+ if (!isExpired(key) && !key.isRevoked()) {
+ if (key.isMasterKey()) {
+ masterKey = key;
+ } else {
+ usableKeys.add(key);
+ }
+ }
+ }
+ if (masterKey != null) {
+ usableKeys.add(masterKey);
+ }
+ return usableKeys;
+ }
+
+ public static boolean isExpired(PGPPublicKey key) {
+ Date creationDate = getCreationDate(key);
+ Date expiryDate = getExpiryDate(key);
+ Date now = new Date();
+ if (now.compareTo(creationDate) >= 0
+ && (expiryDate == null || now.compareTo(expiryDate) <= 0)) {
+ return false;
+ }
+ return true;
+ }
+
+ public static Vector<PGPSecretKey> getUsableCertificationKeys(PGPSecretKeyRing keyRing) {
+ Vector<PGPSecretKey> usableKeys = new Vector<PGPSecretKey>();
+ Vector<PGPSecretKey> signingKeys = getCertificationKeys(keyRing);
+ PGPSecretKey masterKey = null;
+ for (int i = 0; i < signingKeys.size(); ++i) {
+ PGPSecretKey key = signingKeys.get(i);
+ if (key.isMasterKey()) {
+ masterKey = key;
+ } else {
+ usableKeys.add(key);
+ }
+ }
+ if (masterKey != null) {
+ usableKeys.add(masterKey);
+ }
+ return usableKeys;
+ }
+
+ public static Vector<PGPSecretKey> getUsableSigningKeys(PGPSecretKeyRing keyRing) {
+ Vector<PGPSecretKey> usableKeys = new Vector<PGPSecretKey>();
+ Vector<PGPSecretKey> signingKeys = getSigningKeys(keyRing);
+ PGPSecretKey masterKey = null;
+ for (int i = 0; i < signingKeys.size(); ++i) {
+ PGPSecretKey key = signingKeys.get(i);
+ if (key.isMasterKey()) {
+ masterKey = key;
+ } else {
+ usableKeys.add(key);
+ }
+ }
+ if (masterKey != null) {
+ usableKeys.add(masterKey);
+ }
+ return usableKeys;
+ }
+
+ public static Date getExpiryDate(PGPPublicKey key) {
+ Date creationDate = getCreationDate(key);
+ if (key.getValidDays() == 0) {
+ // no expiry
+ return null;
+ }
+ Calendar calendar = GregorianCalendar.getInstance();
+ calendar.setTime(creationDate);
+ calendar.add(Calendar.DATE, key.getValidDays());
+
+ return calendar.getTime();
+ }
+
+ public static Date getExpiryDate(PGPSecretKey key) {
+ return getExpiryDate(key.getPublicKey());
+ }
+
+ public static PGPPublicKey getEncryptPublicKey(Context context, long masterKeyId) {
+ PGPPublicKeyRing keyRing = ProviderHelper.getPGPPublicKeyRing(context, masterKeyId);
+ if (keyRing == null) {
+ Log.e(Constants.TAG, "keyRing is null!");
+ return null;
+ }
+ Vector<PGPPublicKey> encryptKeys = getUsableEncryptKeys(keyRing);
+ if (encryptKeys.size() == 0) {
+ Log.e(Constants.TAG, "encryptKeys is null!");
+ return null;
+ }
+ return encryptKeys.get(0);
+ }
+
+ public static PGPSecretKey getCertificationKey(Context context, long masterKeyId) {
+ PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRing(context, masterKeyId);
+ if (keyRing == null) {
+ return null;
+ }
+ Vector<PGPSecretKey> signingKeys = getUsableCertificationKeys(keyRing);
+ if (signingKeys.size() == 0) {
+ return null;
+ }
+ return signingKeys.get(0);
+ }
+
+ public static PGPSecretKey getSigningKey(Context context, long masterKeyId) {
+ PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRing(context, masterKeyId);
+ if (keyRing == null) {
+ return null;
+ }
+ Vector<PGPSecretKey> signingKeys = getUsableSigningKeys(keyRing);
+ if (signingKeys.size() == 0) {
+ return null;
+ }
+ return signingKeys.get(0);
+ }
+
+ @SuppressWarnings("unchecked")
+ public static String getMainUserId(PGPPublicKey key) {
+ for (String userId : new IterableIterator<String>(key.getUserIDs())) {
+ return userId;
+ }
+ return null;
+ }
+
+ @SuppressWarnings("unchecked")
+ public static String getMainUserId(PGPSecretKey key) {
+ for (String userId : new IterableIterator<String>(key.getUserIDs())) {
+ return userId;
+ }
+ return null;
+ }
+
+ public static String getMainUserIdSafe(Context context, PGPPublicKey key) {
+ String userId = getMainUserId(key);
+ if (userId == null || userId.equals("")) {
+ userId = context.getString(R.string.user_id_no_name);
+ }
+ return userId;
+ }
+
+ public static String getMainUserIdSafe(Context context, PGPSecretKey key) {
+ String userId = getMainUserId(key);
+ if (userId == null || userId.equals("")) {
+ userId = context.getString(R.string.user_id_no_name);
+ }
+ return userId;
+ }
+
+ public static int getKeyUsage(PGPSecretKey key) {
+ return getKeyUsage(key.getPublicKey());
+ }
+
+ @SuppressWarnings("unchecked")
+ private static int getKeyUsage(PGPPublicKey key) {
+ int usage = 0;
+ if (key.getVersion() >= 4) {
+ for (PGPSignature sig : new IterableIterator<PGPSignature>(key.getSignatures())) {
+ if (key.isMasterKey() && sig.getKeyID() != key.getKeyID()) {
+ continue;
+ }
+
+ PGPSignatureSubpacketVector hashed = sig.getHashedSubPackets();
+ if (hashed != null) {
+ usage |= hashed.getKeyFlags();
+ }
+
+ PGPSignatureSubpacketVector unhashed = sig.getUnhashedSubPackets();
+ if (unhashed != null) {
+ usage |= unhashed.getKeyFlags();
+ }
+ }
+ }
+ return usage;
+ }
+
+ @SuppressWarnings("unchecked")
+ public static boolean isEncryptionKey(PGPPublicKey key) {
+ if (!key.isEncryptionKey()) {
+ return false;
+ }
+
+ if (key.getVersion() <= 3) {
+ // this must be true now
+ return key.isEncryptionKey();
+ }
+
+ // special cases
+ if (key.getAlgorithm() == PGPPublicKey.ELGAMAL_ENCRYPT) {
+ return true;
+ }
+
+ if (key.getAlgorithm() == PGPPublicKey.RSA_ENCRYPT) {
+ return true;
+ }
+
+ for (PGPSignature sig : new IterableIterator<PGPSignature>(key.getSignatures())) {
+ if (key.isMasterKey() && sig.getKeyID() != key.getKeyID()) {
+ continue;
+ }
+ PGPSignatureSubpacketVector hashed = sig.getHashedSubPackets();
+
+ if (hashed != null
+ && (hashed.getKeyFlags() & (KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE)) != 0) {
+ return true;
+ }
+
+ PGPSignatureSubpacketVector unhashed = sig.getUnhashedSubPackets();
+
+ if (unhashed != null
+ && (unhashed.getKeyFlags() & (KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE)) != 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static boolean isEncryptionKey(PGPSecretKey key) {
+ return isEncryptionKey(key.getPublicKey());
+ }
+
+ @SuppressWarnings("unchecked")
+ public static boolean isSigningKey(PGPPublicKey key) {
+ if (key.getVersion() <= 3) {
+ return true;
+ }
+
+ // special case
+ if (key.getAlgorithm() == PGPPublicKey.RSA_SIGN) {
+ return true;
+ }
+
+ for (PGPSignature sig : new IterableIterator<PGPSignature>(key.getSignatures())) {
+ if (key.isMasterKey() && sig.getKeyID() != key.getKeyID()) {
+ continue;
+ }
+ PGPSignatureSubpacketVector hashed = sig.getHashedSubPackets();
+
+ if (hashed != null && (hashed.getKeyFlags() & KeyFlags.SIGN_DATA) != 0) {
+ return true;
+ }
+
+ PGPSignatureSubpacketVector unhashed = sig.getUnhashedSubPackets();
+
+ if (unhashed != null && (unhashed.getKeyFlags() & KeyFlags.SIGN_DATA) != 0) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public static boolean isSigningKey(PGPSecretKey key) {
+ return isSigningKey(key.getPublicKey());
+ }
+
+ @SuppressWarnings("unchecked")
+ public static boolean isCertificationKey(PGPPublicKey key) {
+ if (key.getVersion() <= 3) {
+ return true;
+ }
+
+ for (PGPSignature sig : new IterableIterator<PGPSignature>(key.getSignatures())) {
+ if (key.isMasterKey() && sig.getKeyID() != key.getKeyID()) {
+ continue;
+ }
+ PGPSignatureSubpacketVector hashed = sig.getHashedSubPackets();
+
+ if (hashed != null && (hashed.getKeyFlags() & KeyFlags.CERTIFY_OTHER) != 0) {
+ return true;
+ }
+
+ PGPSignatureSubpacketVector unhashed = sig.getUnhashedSubPackets();
+
+ if (unhashed != null && (unhashed.getKeyFlags() & KeyFlags.CERTIFY_OTHER) != 0) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public static boolean isAuthenticationKey(PGPSecretKey key) {
+ return isAuthenticationKey(key.getPublicKey());
+ }
+
+ @SuppressWarnings("unchecked")
+ public static boolean isAuthenticationKey(PGPPublicKey key) {
+ if (key.getVersion() <= 3) {
+ return true;
+ }
+
+ for (PGPSignature sig : new IterableIterator<PGPSignature>(key.getSignatures())) {
+ if (key.isMasterKey() && sig.getKeyID() != key.getKeyID()) {
+ continue;
+ }
+ PGPSignatureSubpacketVector hashed = sig.getHashedSubPackets();
+
+ if (hashed != null && (hashed.getKeyFlags() & KeyFlags.AUTHENTICATION) != 0) {
+ return true;
+ }
+
+ PGPSignatureSubpacketVector unhashed = sig.getUnhashedSubPackets();
+
+ if (unhashed != null && (unhashed.getKeyFlags() & KeyFlags.AUTHENTICATION) != 0) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public static boolean isCertificationKey(PGPSecretKey key) {
+ return isCertificationKey(key.getPublicKey());
+ }
+
+ public static String getAlgorithmInfo(PGPPublicKey key) {
+ return getAlgorithmInfo(key.getAlgorithm(), key.getBitStrength());
+ }
+
+ public static String getAlgorithmInfo(PGPSecretKey key) {
+ return getAlgorithmInfo(key.getPublicKey());
+ }
+
+ public static String getAlgorithmInfo(int algorithm, int keySize) {
+ String algorithmStr;
+
+ switch (algorithm) {
+ case PGPPublicKey.RSA_ENCRYPT:
+ case PGPPublicKey.RSA_GENERAL:
+ case PGPPublicKey.RSA_SIGN: {
+ algorithmStr = "RSA";
+ break;
+ }
+ case PGPPublicKey.DSA: {
+ algorithmStr = "DSA";
+ break;
+ }
+
+ case PGPPublicKey.ELGAMAL_ENCRYPT:
+ case PGPPublicKey.ELGAMAL_GENERAL: {
+ algorithmStr = "ElGamal";
+ break;
+ }
+
+ default: {
+ algorithmStr = "Unknown";
+ break;
+ }
+ }
+ if(keySize > 0)
+ return algorithmStr + ", " + keySize + " bit";
+ else
+ return algorithmStr;
+ }
+
+ /**
+ * Converts fingerprint to hex (optional: with whitespaces after 4 characters)
+ * <p/>
+ * Fingerprint is shown using lowercase characters. Studies have shown that humans can
+ * better differentiate between numbers and letters when letters are lowercase.
+ *
+ * @param fingerprint
+ * @return
+ */
+ public static String convertFingerprintToHex(byte[] fingerprint) {
+ String hexString = Hex.toHexString(fingerprint);
+
+ return hexString;
+ }
+
+ /**
+ * Convert key id from long to 64 bit hex string
+ * <p/>
+ * V4: "The Key ID is the low-order 64 bits of the fingerprint"
+ * <p/>
+ * see http://tools.ietf.org/html/rfc4880#section-12.2
+ *
+ * @param keyId
+ * @return
+ */
+ public static String convertKeyIdToHex(long keyId) {
+ long upper = keyId >> 32;
+ if (upper == 0) {
+ // this is a short key id
+ return convertKeyIdToHexShort(keyId);
+ }
+ return "0x" + convertKeyIdToHex32bit(keyId >> 32) + convertKeyIdToHex32bit(keyId);
+ }
+
+ public static String convertKeyIdToHexShort(long keyId) {
+ return "0x" + convertKeyIdToHex32bit(keyId);
+ }
+
+ private static String convertKeyIdToHex32bit(long keyId) {
+ String hexString = Long.toHexString(keyId & 0xffffffffL).toLowerCase(Locale.US);
+ while (hexString.length() < 8) {
+ hexString = "0" + hexString;
+ }
+ return hexString;
+ }
+
+
+ public static SpannableStringBuilder colorizeFingerprint(String fingerprint) {
+ // split by 4 characters
+ fingerprint = fingerprint.replaceAll("(.{4})(?!$)", "$1 ");
+
+ // add line breaks to have a consistent "image" that can be recognized
+ char[] chars = fingerprint.toCharArray();
+ chars[24] = '\n';
+ fingerprint = String.valueOf(chars);
+
+ SpannableStringBuilder sb = new SpannableStringBuilder(fingerprint);
+ try {
+ // for each 4 characters of the fingerprint + 1 space
+ for (int i = 0; i < fingerprint.length(); i += 5) {
+ int spanEnd = Math.min(i + 4, fingerprint.length());
+ String fourChars = fingerprint.substring(i, spanEnd);
+
+ int raw = Integer.parseInt(fourChars, 16);
+ byte[] bytes = {(byte) ((raw >> 8) & 0xff - 128), (byte) (raw & 0xff - 128)};
+ int[] color = getRgbForData(bytes);
+ int r = color[0];
+ int g = color[1];
+ int b = color[2];
+
+ // we cannot change black by multiplication, so adjust it to an almost-black grey,
+ // which will then be brightened to the minimal brightness level
+ if (r == 0 && g == 0 && b == 0) {
+ r = 1;
+ g = 1;
+ b = 1;
+ }
+
+ // Convert rgb to brightness
+ double brightness = 0.2126 * r + 0.7152 * g + 0.0722 * b;
+
+ // If a color is too dark to be seen on black,
+ // then brighten it up to a minimal brightness.
+ if (brightness < 80) {
+ double factor = 80.0 / brightness;
+ r = Math.min(255, (int) (r * factor));
+ g = Math.min(255, (int) (g * factor));
+ b = Math.min(255, (int) (b * factor));
+
+ // If it is too light, then darken it to a respective maximal brightness.
+ } else if (brightness > 180) {
+ double factor = 180.0 / brightness;
+ r = (int) (r * factor);
+ g = (int) (g * factor);
+ b = (int) (b * factor);
+ }
+
+ // Create a foreground color with the 3 digest integers as RGB
+ // and then converting that int to hex to use as a color
+ sb.setSpan(new ForegroundColorSpan(Color.rgb(r, g, b)),
+ i, spanEnd, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
+ }
+ } catch (Exception e) {
+ Log.e(Constants.TAG, "Colorization failed", e);
+ // if anything goes wrong, then just display the fingerprint without colour,
+ // instead of partially correct colour or wrong colours
+ return new SpannableStringBuilder(fingerprint);
+ }
+
+ return sb;
+ }
+
+ /**
+ * 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
+ */
+ private static int[] getRgbForData(byte[] bytes) throws NoSuchAlgorithmException, DigestException {
+ MessageDigest md = MessageDigest.getInstance("SHA1");
+
+ md.update(bytes);
+ byte[] digest = md.digest();
+
+ int[] result = {((int) digest[0] + 256) % 256,
+ ((int) digest[1] + 256) % 256,
+ ((int) digest[2] + 256) % 256};
+ return result;
+ }
+
+ /**
+ * Splits userId string into naming part, email part, and comment part
+ *
+ * @param userId
+ * @return array with naming (0), email (1), comment (2)
+ */
+ public static String[] splitUserId(String userId) {
+ String[] result = new String[]{null, null, null};
+
+ if (userId == null || userId.equals("")) {
+ return result;
+ }
+
+ /*
+ * User ID matching:
+ * http://fiddle.re/t4p6f
+ *
+ * test cases:
+ * "Max Mustermann (this is a comment) <max@example.com>"
+ * "Max Mustermann <max@example.com>"
+ * "Max Mustermann (this is a comment)"
+ * "Max Mustermann [this is nothing]"
+ */
+ Matcher matcher = USER_ID_PATTERN.matcher(userId);
+ if (matcher.matches()) {
+ result[0] = matcher.group(1);
+ result[1] = matcher.group(3);
+ result[2] = matcher.group(2);
+ }
+
+ return result;
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java
new file mode 100644
index 000000000..48b959738
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java
@@ -0,0 +1,769 @@
+/*
+ * Copyright (C) 2012-2013 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
+ *
+ * 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.pgp;
+
+import android.util.Pair;
+
+import org.spongycastle.bcpg.CompressionAlgorithmTags;
+import org.spongycastle.bcpg.HashAlgorithmTags;
+import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags;
+import org.spongycastle.bcpg.sig.KeyFlags;
+import org.spongycastle.jce.spec.ElGamalParameterSpec;
+import org.spongycastle.openpgp.PGPEncryptedData;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPKeyPair;
+import org.spongycastle.openpgp.PGPKeyRingGenerator;
+import org.spongycastle.openpgp.PGPPrivateKey;
+import org.spongycastle.openpgp.PGPPublicKey;
+import org.spongycastle.openpgp.PGPPublicKeyRing;
+import org.spongycastle.openpgp.PGPSecretKey;
+import org.spongycastle.openpgp.PGPSecretKeyRing;
+import org.spongycastle.openpgp.PGPSignature;
+import org.spongycastle.openpgp.PGPSignatureGenerator;
+import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator;
+import org.spongycastle.openpgp.PGPSignatureSubpacketVector;
+import org.spongycastle.openpgp.PGPUtil;
+import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
+import org.spongycastle.openpgp.operator.PBESecretKeyEncryptor;
+import org.spongycastle.openpgp.operator.PGPContentSignerBuilder;
+import org.spongycastle.openpgp.operator.PGPDigestCalculator;
+import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
+import org.spongycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
+import org.spongycastle.openpgp.operator.jcajce.JcaPGPKeyPair;
+import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
+import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.Id;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralMsgIdException;
+import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
+import org.sufficientlysecure.keychain.util.IterableIterator;
+import org.sufficientlysecure.keychain.util.Primes;
+import org.sufficientlysecure.keychain.util.ProgressDialogUpdater;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.SecureRandom;
+import java.security.SignatureException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Iterator;
+import java.util.List;
+import java.util.TimeZone;
+
+/** This class is the single place where ALL operations that actually modify a PGP public or secret
+ * key take place.
+ *
+ * Note that no android specific stuff should be done here, ie no imports from com.android.
+ *
+ * All operations support progress reporting to a ProgressDialogUpdater passed on initialization.
+ * This indicator may be null.
+ *
+ */
+public class PgpKeyOperation {
+ private ProgressDialogUpdater mProgress;
+
+ private static final int[] PREFERRED_SYMMETRIC_ALGORITHMS = new int[]{
+ SymmetricKeyAlgorithmTags.AES_256, SymmetricKeyAlgorithmTags.AES_192,
+ SymmetricKeyAlgorithmTags.AES_128, SymmetricKeyAlgorithmTags.CAST5,
+ SymmetricKeyAlgorithmTags.TRIPLE_DES};
+ private static final int[] PREFERRED_HASH_ALGORITHMS = new int[]{HashAlgorithmTags.SHA1,
+ HashAlgorithmTags.SHA256, HashAlgorithmTags.RIPEMD160};
+ private static final int[] PREFERRED_COMPRESSION_ALGORITHMS = new int[]{
+ CompressionAlgorithmTags.ZLIB, CompressionAlgorithmTags.BZIP2,
+ CompressionAlgorithmTags.ZIP};
+
+ public PgpKeyOperation(ProgressDialogUpdater progress) {
+ super();
+ this.mProgress = progress;
+ }
+
+ void updateProgress(int message, int current, int total) {
+ if (mProgress != null) {
+ mProgress.setProgress(message, current, total);
+ }
+ }
+
+ void updateProgress(int current, int total) {
+ if (mProgress != null) {
+ mProgress.setProgress(current, total);
+ }
+ }
+
+ /**
+ * Creates new secret key.
+ *
+ * @param algorithmChoice
+ * @param keySize
+ * @param passphrase
+ * @param isMasterKey
+ * @return A newly created PGPSecretKey
+ * @throws NoSuchAlgorithmException
+ * @throws PGPException
+ * @throws NoSuchProviderException
+ * @throws PgpGeneralMsgIdException
+ * @throws InvalidAlgorithmParameterException
+ */
+
+ // TODO: key flags?
+ public PGPSecretKey createKey(int algorithmChoice, int keySize, String passphrase,
+ boolean isMasterKey)
+ throws NoSuchAlgorithmException, PGPException, NoSuchProviderException,
+ PgpGeneralMsgIdException, InvalidAlgorithmParameterException {
+
+ if (keySize < 512) {
+ throw new PgpGeneralMsgIdException(R.string.error_key_size_minimum512bit);
+ }
+
+ if (passphrase == null) {
+ passphrase = "";
+ }
+
+ int algorithm;
+ KeyPairGenerator keyGen;
+
+ switch (algorithmChoice) {
+ case Id.choice.algorithm.dsa: {
+ keyGen = KeyPairGenerator.getInstance("DSA", Constants.BOUNCY_CASTLE_PROVIDER_NAME);
+ keyGen.initialize(keySize, new SecureRandom());
+ algorithm = PGPPublicKey.DSA;
+ break;
+ }
+
+ case Id.choice.algorithm.elgamal: {
+ if (isMasterKey) {
+ throw new PgpGeneralMsgIdException(R.string.error_master_key_must_not_be_el_gamal);
+ }
+ keyGen = KeyPairGenerator.getInstance("ElGamal", Constants.BOUNCY_CASTLE_PROVIDER_NAME);
+ BigInteger p = Primes.getBestPrime(keySize);
+ BigInteger g = new BigInteger("2");
+
+ ElGamalParameterSpec elParams = new ElGamalParameterSpec(p, g);
+
+ keyGen.initialize(elParams);
+ algorithm = PGPPublicKey.ELGAMAL_ENCRYPT;
+ break;
+ }
+
+ case Id.choice.algorithm.rsa: {
+ keyGen = KeyPairGenerator.getInstance("RSA", Constants.BOUNCY_CASTLE_PROVIDER_NAME);
+ keyGen.initialize(keySize, new SecureRandom());
+
+ algorithm = PGPPublicKey.RSA_GENERAL;
+ break;
+ }
+
+ default: {
+ throw new PgpGeneralMsgIdException(R.string.error_unknown_algorithm_choice);
+ }
+ }
+
+ // build new key pair
+ PGPKeyPair keyPair = new JcaPGPKeyPair(algorithm, keyGen.generateKeyPair(), new Date());
+
+ // define hashing and signing algos
+ PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder().build().get(
+ HashAlgorithmTags.SHA1);
+
+ // Build key encrypter and decrypter based on passphrase
+ PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder(
+ PGPEncryptedData.CAST5, sha1Calc)
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray());
+
+ return new PGPSecretKey(keyPair.getPrivateKey(), keyPair.getPublicKey(),
+ sha1Calc, isMasterKey, keyEncryptor);
+ }
+
+ public PGPSecretKeyRing changeSecretKeyPassphrase(PGPSecretKeyRing keyRing, String oldPassphrase,
+ String newPassphrase)
+ throws IOException, PGPException, NoSuchProviderException {
+
+ updateProgress(R.string.progress_building_key, 0, 100);
+ if (oldPassphrase == null) {
+ oldPassphrase = "";
+ }
+ if (newPassphrase == null) {
+ newPassphrase = "";
+ }
+
+ PGPSecretKeyRing newKeyRing = PGPSecretKeyRing.copyWithNewPassword(
+ keyRing,
+ new JcePBESecretKeyDecryptorBuilder(new JcaPGPDigestCalculatorProviderBuilder()
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build()).setProvider(
+ Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(oldPassphrase.toCharArray()),
+ new JcePBESecretKeyEncryptorBuilder(keyRing.getSecretKey()
+ .getKeyEncryptionAlgorithm()).build(newPassphrase.toCharArray()));
+
+ return newKeyRing;
+
+ }
+
+ private Pair<PGPSecretKeyRing, PGPPublicKeyRing> buildNewSecretKey(
+ ArrayList<String> userIds, ArrayList<PGPSecretKey> keys,
+ ArrayList<GregorianCalendar> keysExpiryDates,
+ ArrayList<Integer> keysUsages,
+ String newPassphrase, String oldPassphrase)
+ throws PgpGeneralMsgIdException, PGPException, SignatureException, IOException {
+
+ int usageId = keysUsages.get(0);
+ boolean canSign;
+ String mainUserId = userIds.get(0);
+
+ PGPSecretKey masterKey = keys.get(0);
+
+ // this removes all userIds and certifications previously attached to the masterPublicKey
+ PGPPublicKey masterPublicKey = masterKey.getPublicKey();
+
+ PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
+ Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(oldPassphrase.toCharArray());
+ PGPPrivateKey masterPrivateKey = masterKey.extractPrivateKey(keyDecryptor);
+
+ updateProgress(R.string.progress_certifying_master_key, 20, 100);
+
+ for (String userId : userIds) {
+ PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
+ masterPublicKey.getAlgorithm(), HashAlgorithmTags.SHA1)
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
+ PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
+
+ sGen.init(PGPSignature.POSITIVE_CERTIFICATION, masterPrivateKey);
+
+ PGPSignature certification = sGen.generateCertification(userId, masterPublicKey);
+ masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, userId, certification);
+ }
+
+ PGPKeyPair masterKeyPair = new PGPKeyPair(masterPublicKey, masterPrivateKey);
+
+ PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator();
+ PGPSignatureSubpacketGenerator unhashedPacketsGen = new PGPSignatureSubpacketGenerator();
+
+ hashedPacketsGen.setKeyFlags(true, usageId);
+
+ hashedPacketsGen.setPreferredSymmetricAlgorithms(true, PREFERRED_SYMMETRIC_ALGORITHMS);
+ hashedPacketsGen.setPreferredHashAlgorithms(true, PREFERRED_HASH_ALGORITHMS);
+ hashedPacketsGen.setPreferredCompressionAlgorithms(true, PREFERRED_COMPRESSION_ALGORITHMS);
+
+ if (keysExpiryDates.get(0) != null) {
+ GregorianCalendar creationDate = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
+ creationDate.setTime(masterPublicKey.getCreationTime());
+ GregorianCalendar expiryDate = keysExpiryDates.get(0);
+ //note that the below, (a/c) - (b/c) is *not* the same as (a - b) /c
+ //here we purposefully ignore partial days in each date - long type has no fractional part!
+ long numDays = (expiryDate.getTimeInMillis() / 86400000) -
+ (creationDate.getTimeInMillis() / 86400000);
+ if (numDays <= 0) {
+ throw new PgpGeneralMsgIdException(R.string.error_expiry_must_come_after_creation);
+ }
+ hashedPacketsGen.setKeyExpirationTime(false, numDays * 86400);
+ } else {
+ hashedPacketsGen.setKeyExpirationTime(false, 0);
+ // do this explicitly, although since we're rebuilding,
+ // this happens anyway
+ }
+
+ updateProgress(R.string.progress_building_master_key, 30, 100);
+
+ // define hashing and signing algos
+ PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder().build().get(
+ HashAlgorithmTags.SHA1);
+ PGPContentSignerBuilder certificationSignerBuilder = new JcaPGPContentSignerBuilder(
+ masterKeyPair.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA1);
+
+ // Build key encrypter based on passphrase
+ PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder(
+ PGPEncryptedData.CAST5, sha1Calc)
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
+ newPassphrase.toCharArray());
+
+ PGPKeyRingGenerator keyGen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION,
+ masterKeyPair, mainUserId, sha1Calc, hashedPacketsGen.generate(),
+ unhashedPacketsGen.generate(), certificationSignerBuilder, keyEncryptor);
+
+ updateProgress(R.string.progress_adding_sub_keys, 40, 100);
+
+ for (int i = 1; i < keys.size(); ++i) {
+ updateProgress(40 + 40 * (i - 1) / (keys.size() - 1), 100);
+
+ PGPSecretKey subKey = keys.get(i);
+ PGPPublicKey subPublicKey = subKey.getPublicKey();
+
+ PBESecretKeyDecryptor keyDecryptor2 = new JcePBESecretKeyDecryptorBuilder()
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
+ oldPassphrase.toCharArray());
+ PGPPrivateKey subPrivateKey = subKey.extractPrivateKey(keyDecryptor2);
+
+ // TODO: now used without algorithm and creation time?! (APG 1)
+ PGPKeyPair subKeyPair = new PGPKeyPair(subPublicKey, subPrivateKey);
+
+ hashedPacketsGen = new PGPSignatureSubpacketGenerator();
+ unhashedPacketsGen = new PGPSignatureSubpacketGenerator();
+
+ usageId = keysUsages.get(i);
+ canSign = (usageId & KeyFlags.SIGN_DATA) > 0; //todo - separate function for this
+ if (canSign) {
+ Date todayDate = new Date(); //both sig times the same
+ // cross-certify signing keys
+ hashedPacketsGen.setSignatureCreationTime(false, todayDate); //set outer creation time
+ PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator();
+ subHashedPacketsGen.setSignatureCreationTime(false, todayDate); //set inner creation time
+ PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
+ subPublicKey.getAlgorithm(), PGPUtil.SHA1)
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
+ PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
+ sGen.init(PGPSignature.PRIMARYKEY_BINDING, subPrivateKey);
+ sGen.setHashedSubpackets(subHashedPacketsGen.generate());
+ PGPSignature certification = sGen.generateCertification(masterPublicKey,
+ subPublicKey);
+ unhashedPacketsGen.setEmbeddedSignature(false, certification);
+ }
+ hashedPacketsGen.setKeyFlags(false, usageId);
+
+ if (keysExpiryDates.get(i) != null) {
+ GregorianCalendar creationDate = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
+ creationDate.setTime(subPublicKey.getCreationTime());
+ GregorianCalendar expiryDate = keysExpiryDates.get(i);
+ //note that the below, (a/c) - (b/c) is *not* the same as (a - b) /c
+ //here we purposefully ignore partial days in each date - long type has no fractional part!
+ long numDays = (expiryDate.getTimeInMillis() / 86400000) -
+ (creationDate.getTimeInMillis() / 86400000);
+ if (numDays <= 0) {
+ throw new PgpGeneralMsgIdException(R.string.error_expiry_must_come_after_creation);
+ }
+ hashedPacketsGen.setKeyExpirationTime(false, numDays * 86400);
+ } else {
+ hashedPacketsGen.setKeyExpirationTime(false, 0);
+ // do this explicitly, although since we're rebuilding,
+ // this happens anyway
+ }
+
+ keyGen.addSubKey(subKeyPair, hashedPacketsGen.generate(), unhashedPacketsGen.generate());
+ }
+
+ PGPSecretKeyRing secretKeyRing = keyGen.generateSecretKeyRing();
+ PGPPublicKeyRing publicKeyRing = keyGen.generatePublicKeyRing();
+
+ return new Pair<PGPSecretKeyRing, PGPPublicKeyRing>(secretKeyRing, publicKeyRing);
+
+ }
+
+ public Pair<PGPSecretKeyRing, PGPPublicKeyRing> buildSecretKey(PGPSecretKeyRing mKR,
+ PGPPublicKeyRing pKR,
+ SaveKeyringParcel saveParcel)
+ throws PgpGeneralMsgIdException, PGPException, SignatureException, IOException {
+
+ updateProgress(R.string.progress_building_key, 0, 100);
+ PGPSecretKey masterKey = saveParcel.keys.get(0);
+
+ if (saveParcel.oldPassphrase == null) {
+ saveParcel.oldPassphrase = "";
+ }
+ if (saveParcel.newPassphrase == null) {
+ saveParcel.newPassphrase = "";
+ }
+
+ if (mKR == null) {
+ return buildNewSecretKey(saveParcel.userIDs, saveParcel.keys, saveParcel.keysExpiryDates,
+ saveParcel.keysUsages, saveParcel.newPassphrase, saveParcel.oldPassphrase); //new Keyring
+ }
+
+ /*
+ IDs - NB This might not need to happen later, if we change the way the primary ID is chosen
+ remove deleted ids
+ if the primary ID changed we need to:
+ remove all of the IDs from the keyring, saving their certifications
+ add them all in again, updating certs of IDs which have changed
+ else
+ remove changed IDs and add in with new certs
+
+ if the master key changed, we need to remove the primary ID certification, so we can add
+ the new one when it is generated, and they don't conflict
+
+ Keys
+ remove deleted keys
+ if a key is modified, re-sign it
+ do we need to remove and add in?
+
+ Todo
+ identify more things which need to be preserved - e.g. trust levels?
+ user attributes
+ */
+
+ if (saveParcel.deletedKeys != null) {
+ for (PGPSecretKey dKey : saveParcel.deletedKeys) {
+ mKR = PGPSecretKeyRing.removeSecretKey(mKR, dKey);
+ }
+ }
+
+ masterKey = mKR.getSecretKey();
+ PGPPublicKey masterPublicKey = masterKey.getPublicKey();
+
+ int usageId = saveParcel.keysUsages.get(0);
+ boolean canSign;
+ String mainUserId = saveParcel.userIDs.get(0);
+
+ PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
+ Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(saveParcel.oldPassphrase.toCharArray());
+ PGPPrivateKey masterPrivateKey = masterKey.extractPrivateKey(keyDecryptor);
+
+ updateProgress(R.string.progress_certifying_master_key, 20, 100);
+
+ boolean anyIDChanged = false;
+ for (String delID : saveParcel.deletedIDs) {
+ anyIDChanged = true;
+ masterPublicKey = PGPPublicKey.removeCertification(masterPublicKey, delID);
+ }
+
+ int userIDIndex = 0;
+
+ PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator();
+ PGPSignatureSubpacketGenerator unhashedPacketsGen = new PGPSignatureSubpacketGenerator();
+
+ hashedPacketsGen.setKeyFlags(true, usageId);
+
+ hashedPacketsGen.setPreferredSymmetricAlgorithms(true, PREFERRED_SYMMETRIC_ALGORITHMS);
+ hashedPacketsGen.setPreferredHashAlgorithms(true, PREFERRED_HASH_ALGORITHMS);
+ hashedPacketsGen.setPreferredCompressionAlgorithms(true, PREFERRED_COMPRESSION_ALGORITHMS);
+
+ if (saveParcel.keysExpiryDates.get(0) != null) {
+ GregorianCalendar creationDate = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
+ creationDate.setTime(masterPublicKey.getCreationTime());
+ GregorianCalendar expiryDate = saveParcel.keysExpiryDates.get(0);
+ //note that the below, (a/c) - (b/c) is *not* the same as (a - b) /c
+ //here we purposefully ignore partial days in each date - long type has no fractional part!
+ long numDays = (expiryDate.getTimeInMillis() / 86400000) -
+ (creationDate.getTimeInMillis() / 86400000);
+ if (numDays <= 0) {
+ throw new PgpGeneralMsgIdException(R.string.error_expiry_must_come_after_creation);
+ }
+ hashedPacketsGen.setKeyExpirationTime(false, numDays * 86400);
+ } else {
+ hashedPacketsGen.setKeyExpirationTime(false, 0);
+ // do this explicitly, although since we're rebuilding,
+ // this happens anyway
+ }
+
+ if (saveParcel.primaryIDChanged ||
+ !saveParcel.originalIDs.get(0).equals(saveParcel.userIDs.get(0))) {
+ anyIDChanged = true;
+ ArrayList<Pair<String, PGPSignature>> sigList = new ArrayList<Pair<String, PGPSignature>>();
+ for (String userId : saveParcel.userIDs) {
+ String origID = saveParcel.originalIDs.get(userIDIndex);
+ if (origID.equals(userId) && !saveParcel.newIDs[userIDIndex] &&
+ !userId.equals(saveParcel.originalPrimaryID) && userIDIndex != 0) {
+ Iterator<PGPSignature> origSigs = masterPublicKey.getSignaturesForID(origID);
+ // TODO: make sure this iterator only has signatures we are interested in
+ while (origSigs.hasNext()) {
+ PGPSignature origSig = origSigs.next();
+ sigList.add(new Pair<String, PGPSignature>(origID, origSig));
+ }
+ } else {
+ PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
+ masterPublicKey.getAlgorithm(), HashAlgorithmTags.SHA1)
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
+ PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
+
+ sGen.init(PGPSignature.POSITIVE_CERTIFICATION, masterPrivateKey);
+ if (userIDIndex == 0) {
+ sGen.setHashedSubpackets(hashedPacketsGen.generate());
+ sGen.setUnhashedSubpackets(unhashedPacketsGen.generate());
+ }
+ PGPSignature certification = sGen.generateCertification(userId, masterPublicKey);
+ sigList.add(new Pair<String, PGPSignature>(userId, certification));
+ }
+ if (!saveParcel.newIDs[userIDIndex]) {
+ masterPublicKey = PGPPublicKey.removeCertification(masterPublicKey, origID);
+ }
+ userIDIndex++;
+ }
+ for (Pair<String, PGPSignature> toAdd : sigList) {
+ masterPublicKey =
+ PGPPublicKey.addCertification(masterPublicKey, toAdd.first, toAdd.second);
+ }
+ } else {
+ for (String userId : saveParcel.userIDs) {
+ String origID = saveParcel.originalIDs.get(userIDIndex);
+ if (!origID.equals(userId) || saveParcel.newIDs[userIDIndex]) {
+ anyIDChanged = true;
+ PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
+ masterPublicKey.getAlgorithm(), HashAlgorithmTags.SHA1)
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
+ PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
+
+ sGen.init(PGPSignature.POSITIVE_CERTIFICATION, masterPrivateKey);
+ if (userIDIndex == 0) {
+ sGen.setHashedSubpackets(hashedPacketsGen.generate());
+ sGen.setUnhashedSubpackets(unhashedPacketsGen.generate());
+ }
+ PGPSignature certification = sGen.generateCertification(userId, masterPublicKey);
+ if (!saveParcel.newIDs[userIDIndex]) {
+ masterPublicKey = PGPPublicKey.removeCertification(masterPublicKey, origID);
+ }
+ masterPublicKey =
+ PGPPublicKey.addCertification(masterPublicKey, userId, certification);
+ }
+ userIDIndex++;
+ }
+ }
+
+ ArrayList<Pair<String, PGPSignature>> sigList = new ArrayList<Pair<String, PGPSignature>>();
+ if (saveParcel.moddedKeys[0]) {
+ userIDIndex = 0;
+ for (String userId : saveParcel.userIDs) {
+ String origID = saveParcel.originalIDs.get(userIDIndex);
+ if (!(origID.equals(saveParcel.originalPrimaryID) && !saveParcel.primaryIDChanged)) {
+ Iterator<PGPSignature> sigs = masterPublicKey.getSignaturesForID(userId);
+ // TODO: make sure this iterator only has signatures we are interested in
+ while (sigs.hasNext()) {
+ PGPSignature sig = sigs.next();
+ sigList.add(new Pair<String, PGPSignature>(userId, sig));
+ }
+ }
+ masterPublicKey = PGPPublicKey.removeCertification(masterPublicKey, userId);
+ userIDIndex++;
+ }
+ anyIDChanged = true;
+ }
+
+ //update the keyring with the new ID information
+ if (anyIDChanged) {
+ pKR = PGPPublicKeyRing.insertPublicKey(pKR, masterPublicKey);
+ mKR = PGPSecretKeyRing.replacePublicKeys(mKR, pKR);
+ }
+
+ PGPKeyPair masterKeyPair = new PGPKeyPair(masterPublicKey, masterPrivateKey);
+
+ updateProgress(R.string.progress_building_master_key, 30, 100);
+
+ // define hashing and signing algos
+ PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder().build().get(
+ HashAlgorithmTags.SHA1);
+ PGPContentSignerBuilder certificationSignerBuilder = new JcaPGPContentSignerBuilder(
+ masterKeyPair.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA1);
+
+ // Build key encryptor based on old passphrase, as some keys may be unchanged
+ PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder(
+ PGPEncryptedData.CAST5, sha1Calc)
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
+ saveParcel.oldPassphrase.toCharArray());
+
+ //this generates one more signature than necessary...
+ PGPKeyRingGenerator keyGen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION,
+ masterKeyPair, mainUserId, sha1Calc, hashedPacketsGen.generate(),
+ unhashedPacketsGen.generate(), certificationSignerBuilder, keyEncryptor);
+
+ for (int i = 1; i < saveParcel.keys.size(); ++i) {
+ updateProgress(40 + 50 * i / saveParcel.keys.size(), 100);
+ if (saveParcel.moddedKeys[i]) {
+ PGPSecretKey subKey = saveParcel.keys.get(i);
+ PGPPublicKey subPublicKey = subKey.getPublicKey();
+
+ PBESecretKeyDecryptor keyDecryptor2;
+ if (saveParcel.newKeys[i]) {
+ keyDecryptor2 = new JcePBESecretKeyDecryptorBuilder()
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
+ "".toCharArray());
+ } else {
+ keyDecryptor2 = new JcePBESecretKeyDecryptorBuilder()
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
+ saveParcel.oldPassphrase.toCharArray());
+ }
+ PGPPrivateKey subPrivateKey = subKey.extractPrivateKey(keyDecryptor2);
+ PGPKeyPair subKeyPair = new PGPKeyPair(subPublicKey, subPrivateKey);
+
+ hashedPacketsGen = new PGPSignatureSubpacketGenerator();
+ unhashedPacketsGen = new PGPSignatureSubpacketGenerator();
+
+ usageId = saveParcel.keysUsages.get(i);
+ canSign = (usageId & KeyFlags.SIGN_DATA) > 0; //todo - separate function for this
+ if (canSign) {
+ Date todayDate = new Date(); //both sig times the same
+ // cross-certify signing keys
+ hashedPacketsGen.setSignatureCreationTime(false, todayDate); //set outer creation time
+ PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator();
+ subHashedPacketsGen.setSignatureCreationTime(false, todayDate); //set inner creation time
+ PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
+ subPublicKey.getAlgorithm(), PGPUtil.SHA1)
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
+ PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
+ sGen.init(PGPSignature.PRIMARYKEY_BINDING, subPrivateKey);
+ sGen.setHashedSubpackets(subHashedPacketsGen.generate());
+ PGPSignature certification = sGen.generateCertification(masterPublicKey,
+ subPublicKey);
+ unhashedPacketsGen.setEmbeddedSignature(false, certification);
+ }
+ hashedPacketsGen.setKeyFlags(false, usageId);
+
+ if (saveParcel.keysExpiryDates.get(i) != null) {
+ GregorianCalendar creationDate = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
+ creationDate.setTime(subPublicKey.getCreationTime());
+ GregorianCalendar expiryDate = saveParcel.keysExpiryDates.get(i);
+ // note that the below, (a/c) - (b/c) is *not* the same as (a - b) /c
+ // here we purposefully ignore partial days in each date - long type has
+ // no fractional part!
+ long numDays = (expiryDate.getTimeInMillis() / 86400000) -
+ (creationDate.getTimeInMillis() / 86400000);
+ if (numDays <= 0) {
+ throw new PgpGeneralMsgIdException(R.string.error_expiry_must_come_after_creation);
+ }
+ hashedPacketsGen.setKeyExpirationTime(false, numDays * 86400);
+ } else {
+ hashedPacketsGen.setKeyExpirationTime(false, 0);
+ // do this explicitly, although since we're rebuilding,
+ // this happens anyway
+ }
+
+ keyGen.addSubKey(subKeyPair, hashedPacketsGen.generate(), unhashedPacketsGen.generate());
+ // certifications will be discarded if the key is changed, because I think, for a start,
+ // they will be invalid. Binding certs are regenerated anyway, and other certs which
+ // need to be kept are on IDs and attributes
+ // TODO: don't let revoked keys be edited, other than removed - changing one would
+ // result in the revocation being wrong?
+ }
+ }
+
+ PGPSecretKeyRing updatedSecretKeyRing = keyGen.generateSecretKeyRing();
+ //finally, update the keyrings
+ Iterator<PGPSecretKey> itr = updatedSecretKeyRing.getSecretKeys();
+ while (itr.hasNext()) {
+ PGPSecretKey theNextKey = itr.next();
+ if ((theNextKey.isMasterKey() && saveParcel.moddedKeys[0]) || !theNextKey.isMasterKey()) {
+ mKR = PGPSecretKeyRing.insertSecretKey(mKR, theNextKey);
+ pKR = PGPPublicKeyRing.insertPublicKey(pKR, theNextKey.getPublicKey());
+ }
+ }
+
+ //replace lost IDs
+ if (saveParcel.moddedKeys[0]) {
+ masterPublicKey = mKR.getPublicKey();
+ for (Pair<String, PGPSignature> toAdd : sigList) {
+ masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, toAdd.first, toAdd.second);
+ }
+ pKR = PGPPublicKeyRing.insertPublicKey(pKR, masterPublicKey);
+ mKR = PGPSecretKeyRing.replacePublicKeys(mKR, pKR);
+ }
+
+ // Build key encryptor based on new passphrase
+ PBESecretKeyEncryptor keyEncryptorNew = new JcePBESecretKeyEncryptorBuilder(
+ PGPEncryptedData.CAST5, sha1Calc)
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
+ saveParcel.newPassphrase.toCharArray());
+
+ //update the passphrase
+ mKR = PGPSecretKeyRing.copyWithNewPassword(mKR, keyDecryptor, keyEncryptorNew);
+
+ /* additional handy debug info
+
+ Log.d(Constants.TAG, " ------- in private key -------");
+
+ for(String uid : new IterableIterator<String>(secretKeyRing.getPublicKey().getUserIDs())) {
+ for(PGPSignature sig : new IterableIterator<PGPSignature>(
+ secretKeyRing.getPublicKey().getSignaturesForID(uid))) {
+ Log.d(Constants.TAG, "sig: " +
+ PgpKeyHelper.convertKeyIdToHex(sig.getKeyID()) + " for " + uid);
+ }
+
+ }
+
+ Log.d(Constants.TAG, " ------- in public key -------");
+
+ for(String uid : new IterableIterator<String>(publicKeyRing.getPublicKey().getUserIDs())) {
+ for(PGPSignature sig : new IterableIterator<PGPSignature>(
+ publicKeyRing.getPublicKey().getSignaturesForID(uid))) {
+ Log.d(Constants.TAG, "sig: " +
+ PgpKeyHelper.convertKeyIdToHex(sig.getKeyID()) + " for " + uid);
+ }
+ }
+
+ */
+
+ return new Pair<PGPSecretKeyRing, PGPPublicKeyRing>(mKR, pKR);
+
+ }
+
+ /**
+ * Certify the given pubkeyid with the given masterkeyid.
+ *
+ * @param certificationKey Certifying key
+ * @param publicKey public key to certify
+ * @param userIds User IDs to certify, must not be null or empty
+ * @param passphrase Passphrase of the secret key
+ * @return A keyring with added certifications
+ */
+ public PGPPublicKey certifyKey(PGPSecretKey certificationKey, PGPPublicKey publicKey,
+ List<String> userIds, String passphrase)
+ throws PgpGeneralMsgIdException, NoSuchAlgorithmException, NoSuchProviderException,
+ PGPException, SignatureException {
+
+ // create a signatureGenerator from the supplied masterKeyId and passphrase
+ PGPSignatureGenerator signatureGenerator; {
+
+ if (certificationKey == null) {
+ throw new PgpGeneralMsgIdException(R.string.error_signature_failed);
+ }
+
+ PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
+ Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray());
+ PGPPrivateKey signaturePrivateKey = certificationKey.extractPrivateKey(keyDecryptor);
+ if (signaturePrivateKey == null) {
+ throw new PgpGeneralMsgIdException(R.string.error_could_not_extract_private_key);
+ }
+
+ // TODO: SHA256 fixed?
+ JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder(
+ certificationKey.getPublicKey().getAlgorithm(), PGPUtil.SHA256)
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
+
+ signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder);
+ signatureGenerator.init(PGPSignature.DEFAULT_CERTIFICATION, signaturePrivateKey);
+ }
+
+ { // supply signatureGenerator with a SubpacketVector
+ PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
+ PGPSignatureSubpacketVector packetVector = spGen.generate();
+ signatureGenerator.setHashedSubpackets(packetVector);
+ }
+
+ // fetch public key ring, add the certification and return it
+ for (String userId : new IterableIterator<String>(userIds.iterator())) {
+ PGPSignature sig = signatureGenerator.generateCertification(userId, publicKey);
+ publicKey = PGPPublicKey.addCertification(publicKey, userId, sig);
+ }
+
+ return publicKey;
+ }
+
+ /** Simple static subclass that stores two values.
+ *
+ * This is only used to return a pair of values in one function above. We specifically don't use
+ * com.android.Pair to keep this class free from android dependencies.
+ */
+ public static class Pair<K, V> {
+ public final K first;
+ public final V second;
+ public Pair(K first, V second) {
+ this.first = first;
+ this.second = second;
+ }
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java
new file mode 100644
index 000000000..a864a165d
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java
@@ -0,0 +1,607 @@
+/*
+ * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
+ *
+ * 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.pgp;
+
+import android.content.Context;
+
+import org.spongycastle.bcpg.ArmoredOutputStream;
+import org.spongycastle.bcpg.BCPGOutputStream;
+import org.spongycastle.openpgp.PGPCompressedDataGenerator;
+import org.spongycastle.openpgp.PGPEncryptedDataGenerator;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPLiteralData;
+import org.spongycastle.openpgp.PGPLiteralDataGenerator;
+import org.spongycastle.openpgp.PGPPrivateKey;
+import org.spongycastle.openpgp.PGPPublicKey;
+import org.spongycastle.openpgp.PGPSecretKey;
+import org.spongycastle.openpgp.PGPSecretKeyRing;
+import org.spongycastle.openpgp.PGPSignature;
+import org.spongycastle.openpgp.PGPSignatureGenerator;
+import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator;
+import org.spongycastle.openpgp.PGPV3SignatureGenerator;
+import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
+import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
+import org.spongycastle.openpgp.operator.jcajce.JcePBEKeyEncryptionMethodGenerator;
+import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
+import org.spongycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder;
+import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.Id;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.util.InputData;
+import org.sufficientlysecure.keychain.util.Log;
+import org.sufficientlysecure.keychain.util.ProgressDialogUpdater;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.SignatureException;
+import java.util.Date;
+
+/**
+ * This class uses a Builder pattern!
+ */
+public class PgpSignEncrypt {
+ private Context mContext;
+ private InputData mData;
+ private OutputStream mOutStream;
+
+ private ProgressDialogUpdater mProgress;
+ private boolean mEnableAsciiArmorOutput;
+ private int mCompressionId;
+ private long[] mEncryptionKeyIds;
+ private String mSymmetricPassphrase;
+ private int mSymmetricEncryptionAlgorithm;
+ private long mSignatureKeyId;
+ private int mSignatureHashAlgorithm;
+ private boolean mSignatureForceV3;
+ private String mSignaturePassphrase;
+
+ private PgpSignEncrypt(Builder builder) {
+ // private Constructor can only be called from Builder
+ this.mContext = builder.mContext;
+ this.mData = builder.mData;
+ this.mOutStream = builder.mOutStream;
+
+ this.mProgress = builder.mProgress;
+ this.mEnableAsciiArmorOutput = builder.mEnableAsciiArmorOutput;
+ this.mCompressionId = builder.mCompressionId;
+ this.mEncryptionKeyIds = builder.mEncryptionKeyIds;
+ this.mSymmetricPassphrase = builder.mSymmetricPassphrase;
+ this.mSymmetricEncryptionAlgorithm = builder.mSymmetricEncryptionAlgorithm;
+ this.mSignatureKeyId = builder.mSignatureKeyId;
+ this.mSignatureHashAlgorithm = builder.mSignatureHashAlgorithm;
+ this.mSignatureForceV3 = builder.mSignatureForceV3;
+ this.mSignaturePassphrase = builder.mSignaturePassphrase;
+ }
+
+ public static class Builder {
+ // mandatory parameter
+ private Context mContext;
+ private InputData mData;
+ private OutputStream mOutStream;
+
+ // optional
+ private ProgressDialogUpdater mProgress = null;
+ private boolean mEnableAsciiArmorOutput = false;
+ private int mCompressionId = Id.choice.compression.none;
+ private long[] mEncryptionKeyIds = null;
+ private String mSymmetricPassphrase = null;
+ private int mSymmetricEncryptionAlgorithm = 0;
+ private long mSignatureKeyId = Id.key.none;
+ private int mSignatureHashAlgorithm = 0;
+ private boolean mSignatureForceV3 = false;
+ private String mSignaturePassphrase = null;
+
+ public Builder(Context context, InputData data, OutputStream outStream) {
+ this.mContext = context;
+ this.mData = data;
+ this.mOutStream = outStream;
+ }
+
+ public Builder progress(ProgressDialogUpdater progress) {
+ this.mProgress = progress;
+ return this;
+ }
+
+ public Builder enableAsciiArmorOutput(boolean enableAsciiArmorOutput) {
+ this.mEnableAsciiArmorOutput = enableAsciiArmorOutput;
+ return this;
+ }
+
+ public Builder compressionId(int compressionId) {
+ this.mCompressionId = compressionId;
+ return this;
+ }
+
+ public Builder encryptionKeyIds(long[] encryptionKeyIds) {
+ this.mEncryptionKeyIds = encryptionKeyIds;
+ return this;
+ }
+
+ public Builder symmetricPassphrase(String symmetricPassphrase) {
+ this.mSymmetricPassphrase = symmetricPassphrase;
+ return this;
+ }
+
+ public Builder symmetricEncryptionAlgorithm(int symmetricEncryptionAlgorithm) {
+ this.mSymmetricEncryptionAlgorithm = symmetricEncryptionAlgorithm;
+ return this;
+ }
+
+ public Builder signatureKeyId(long signatureKeyId) {
+ this.mSignatureKeyId = signatureKeyId;
+ return this;
+ }
+
+ public Builder signatureHashAlgorithm(int signatureHashAlgorithm) {
+ this.mSignatureHashAlgorithm = signatureHashAlgorithm;
+ return this;
+ }
+
+ public Builder signatureForceV3(boolean signatureForceV3) {
+ this.mSignatureForceV3 = signatureForceV3;
+ return this;
+ }
+
+ public Builder signaturePassphrase(String signaturePassphrase) {
+ this.mSignaturePassphrase = signaturePassphrase;
+ return this;
+ }
+
+ public PgpSignEncrypt build() {
+ return new PgpSignEncrypt(this);
+ }
+ }
+
+ public void updateProgress(int message, int current, int total) {
+ if (mProgress != null) {
+ mProgress.setProgress(message, current, total);
+ }
+ }
+
+ public void updateProgress(int current, int total) {
+ if (mProgress != null) {
+ mProgress.setProgress(current, total);
+ }
+ }
+
+ /**
+ * Signs and/or encrypts data based on parameters of class
+ *
+ * @throws IOException
+ * @throws PgpGeneralException
+ * @throws PGPException
+ * @throws NoSuchProviderException
+ * @throws NoSuchAlgorithmException
+ * @throws SignatureException
+ */
+ public void execute()
+ throws IOException, PgpGeneralException, PGPException, NoSuchProviderException,
+ NoSuchAlgorithmException, SignatureException {
+
+ boolean enableSignature = mSignatureKeyId != Id.key.none;
+ boolean enableEncryption = ((mEncryptionKeyIds != null && mEncryptionKeyIds.length > 0)
+ || mSymmetricPassphrase != null);
+ boolean enableCompression = (enableEncryption && mCompressionId != Id.choice.compression.none);
+
+ Log.d(Constants.TAG, "enableSignature:" + enableSignature
+ + "\nenableEncryption:" + enableEncryption
+ + "\nenableCompression:" + enableCompression
+ + "\nenableAsciiArmorOutput:" + mEnableAsciiArmorOutput);
+
+ int signatureType;
+ if (mEnableAsciiArmorOutput && enableSignature && !enableEncryption && !enableCompression) {
+ // for sign-only ascii text
+ signatureType = PGPSignature.CANONICAL_TEXT_DOCUMENT;
+ } else {
+ signatureType = PGPSignature.BINARY_DOCUMENT;
+ }
+
+ ArmoredOutputStream armorOut = null;
+ OutputStream out;
+ if (mEnableAsciiArmorOutput) {
+ armorOut = new ArmoredOutputStream(mOutStream);
+ armorOut.setHeader("Version", PgpHelper.getFullVersion(mContext));
+ out = armorOut;
+ } else {
+ out = mOutStream;
+ }
+
+ /* Get keys for signature generation for later usage */
+ PGPSecretKey signingKey = null;
+ PGPSecretKeyRing signingKeyRing = null;
+ PGPPrivateKey signaturePrivateKey = null;
+ if (enableSignature) {
+ signingKeyRing = ProviderHelper.getPGPSecretKeyRingWithKeyId(mContext, mSignatureKeyId);
+ signingKey = PgpKeyHelper.getSigningKey(mContext, mSignatureKeyId);
+ if (signingKey == null) {
+ throw new PgpGeneralException(mContext.getString(R.string.error_signature_failed));
+ }
+
+ if (mSignaturePassphrase == null) {
+ throw new PgpGeneralException(
+ mContext.getString(R.string.error_no_signature_passphrase));
+ }
+
+ updateProgress(R.string.progress_extracting_signature_key, 0, 100);
+
+ PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
+ Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(mSignaturePassphrase.toCharArray());
+ signaturePrivateKey = signingKey.extractPrivateKey(keyDecryptor);
+ if (signaturePrivateKey == null) {
+ throw new PgpGeneralException(
+ mContext.getString(R.string.error_could_not_extract_private_key));
+ }
+ }
+ updateProgress(R.string.progress_preparing_streams, 5, 100);
+
+ /* Initialize PGPEncryptedDataGenerator for later usage */
+ PGPEncryptedDataGenerator cPk = null;
+ if (enableEncryption) {
+ // has Integrity packet enabled!
+ JcePGPDataEncryptorBuilder encryptorBuilder =
+ new JcePGPDataEncryptorBuilder(mSymmetricEncryptionAlgorithm)
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME)
+ .setWithIntegrityPacket(true);
+
+ cPk = new PGPEncryptedDataGenerator(encryptorBuilder);
+
+ if (mSymmetricPassphrase != null) {
+ // Symmetric encryption
+ Log.d(Constants.TAG, "encryptionKeyIds length is 0 -> symmetric encryption");
+
+ JcePBEKeyEncryptionMethodGenerator symmetricEncryptionGenerator =
+ new JcePBEKeyEncryptionMethodGenerator(mSymmetricPassphrase.toCharArray());
+ cPk.addMethod(symmetricEncryptionGenerator);
+ } else {
+ // Asymmetric encryption
+ for (long id : mEncryptionKeyIds) {
+ PGPPublicKey key = PgpKeyHelper.getEncryptPublicKey(mContext, id);
+ if (key != null) {
+ JcePublicKeyKeyEncryptionMethodGenerator pubKeyEncryptionGenerator =
+ new JcePublicKeyKeyEncryptionMethodGenerator(key);
+ cPk.addMethod(pubKeyEncryptionGenerator);
+ }
+ }
+ }
+ }
+
+ /* Initialize signature generator object for later usage */
+ PGPSignatureGenerator signatureGenerator = null;
+ PGPV3SignatureGenerator signatureV3Generator = null;
+ if (enableSignature) {
+ updateProgress(R.string.progress_preparing_signature, 10, 100);
+
+ // content signer based on signing key algorithm and chosen hash algorithm
+ JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder(
+ signingKey.getPublicKey().getAlgorithm(), mSignatureHashAlgorithm)
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
+
+ if (mSignatureForceV3) {
+ signatureV3Generator = new PGPV3SignatureGenerator(contentSignerBuilder);
+ signatureV3Generator.init(signatureType, signaturePrivateKey);
+ } else {
+ signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder);
+ signatureGenerator.init(signatureType, signaturePrivateKey);
+
+ String userId = PgpKeyHelper.getMainUserId(signingKeyRing.getSecretKey());
+ PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
+ spGen.setSignerUserID(false, userId);
+ signatureGenerator.setHashedSubpackets(spGen.generate());
+ }
+ }
+
+ PGPCompressedDataGenerator compressGen = null;
+ OutputStream pOut;
+ OutputStream encryptionOut = null;
+ BCPGOutputStream bcpgOut;
+ if (enableEncryption) {
+ /* actual encryption */
+
+ encryptionOut = cPk.open(out, new byte[1 << 16]);
+
+ if (enableCompression) {
+ compressGen = new PGPCompressedDataGenerator(mCompressionId);
+ bcpgOut = new BCPGOutputStream(compressGen.open(encryptionOut));
+ } else {
+ bcpgOut = new BCPGOutputStream(encryptionOut);
+ }
+
+ if (enableSignature) {
+ if (mSignatureForceV3) {
+ signatureV3Generator.generateOnePassVersion(false).encode(bcpgOut);
+ } else {
+ signatureGenerator.generateOnePassVersion(false).encode(bcpgOut);
+ }
+ }
+
+ PGPLiteralDataGenerator literalGen = new PGPLiteralDataGenerator();
+ // file name not needed, so empty string
+ pOut = literalGen.open(bcpgOut, PGPLiteralData.BINARY, "", new Date(),
+ new byte[1 << 16]);
+ updateProgress(R.string.progress_encrypting, 20, 100);
+
+ long progress = 0;
+ int n;
+ byte[] buffer = new byte[1 << 16];
+ InputStream in = mData.getInputStream();
+ while ((n = in.read(buffer)) > 0) {
+ pOut.write(buffer, 0, n);
+
+ // update signature buffer if signature is requested
+ if (enableSignature) {
+ if (mSignatureForceV3) {
+ signatureV3Generator.update(buffer, 0, n);
+ } else {
+ signatureGenerator.update(buffer, 0, n);
+ }
+ }
+
+ progress += n;
+ if (mData.getSize() != 0) {
+ updateProgress((int) (20 + (95 - 20) * progress / mData.getSize()), 100);
+ }
+ }
+
+ literalGen.close();
+ } else if (mEnableAsciiArmorOutput && enableSignature && !enableEncryption && !enableCompression) {
+ /* sign-only of ascii text */
+
+ updateProgress(R.string.progress_signing, 40, 100);
+
+ // write directly on armor output stream
+ armorOut.beginClearText(mSignatureHashAlgorithm);
+
+ InputStream in = mData.getInputStream();
+ final BufferedReader reader = new BufferedReader(new InputStreamReader(in));
+
+ final byte[] newline = "\r\n".getBytes("UTF-8");
+
+ if (mSignatureForceV3) {
+ processLine(reader.readLine(), armorOut, signatureV3Generator);
+ } else {
+ processLine(reader.readLine(), armorOut, signatureGenerator);
+ }
+
+ while (true) {
+ String line = reader.readLine();
+
+ if (line == null) {
+ armorOut.write(newline);
+ break;
+ }
+
+ armorOut.write(newline);
+
+ // update signature buffer with input line
+ if (mSignatureForceV3) {
+ signatureV3Generator.update(newline);
+ processLine(line, armorOut, signatureV3Generator);
+ } else {
+ signatureGenerator.update(newline);
+ processLine(line, armorOut, signatureGenerator);
+ }
+ }
+
+ armorOut.endClearText();
+
+ pOut = new BCPGOutputStream(armorOut);
+ } else {
+ // TODO: implement sign-only for files!
+ pOut = null;
+ Log.e(Constants.TAG, "not supported!");
+ }
+
+ if (enableSignature) {
+ updateProgress(R.string.progress_generating_signature, 95, 100);
+ if (mSignatureForceV3) {
+ signatureV3Generator.generate().encode(pOut);
+ } else {
+ signatureGenerator.generate().encode(pOut);
+ }
+ }
+
+ // closing outputs
+ // NOTE: closing needs to be done in the correct order!
+ // TODO: closing bcpgOut and pOut???
+ if (enableEncryption) {
+ if (enableCompression) {
+ compressGen.close();
+ }
+
+ encryptionOut.close();
+ }
+ if (mEnableAsciiArmorOutput) {
+ armorOut.close();
+ }
+
+ out.close();
+ mOutStream.close();
+
+ updateProgress(R.string.progress_done, 100, 100);
+ }
+
+ // TODO: merge this into execute method!
+ // TODO: allow binary input for this class
+ public void generateSignature()
+ throws PgpGeneralException, PGPException, IOException, NoSuchAlgorithmException,
+ SignatureException {
+
+ OutputStream out;
+ if (mEnableAsciiArmorOutput) {
+ // Ascii Armor (Radix-64)
+ ArmoredOutputStream armorOut = new ArmoredOutputStream(mOutStream);
+ armorOut.setHeader("Version", PgpHelper.getFullVersion(mContext));
+ out = armorOut;
+ } else {
+ out = mOutStream;
+ }
+
+ if (mSignatureKeyId == 0) {
+ throw new PgpGeneralException(mContext.getString(R.string.error_no_signature_key));
+ }
+
+ PGPSecretKeyRing signingKeyRing =
+ ProviderHelper.getPGPSecretKeyRingWithKeyId(mContext, mSignatureKeyId);
+ PGPSecretKey signingKey = PgpKeyHelper.getSigningKey(mContext, mSignatureKeyId);
+ if (signingKey == null) {
+ throw new PgpGeneralException(mContext.getString(R.string.error_signature_failed));
+ }
+
+ if (mSignaturePassphrase == null) {
+ throw new PgpGeneralException(mContext.getString(R.string.error_no_signature_passphrase));
+ }
+
+ PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
+ Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(mSignaturePassphrase.toCharArray());
+ PGPPrivateKey signaturePrivateKey = signingKey.extractPrivateKey(keyDecryptor);
+ if (signaturePrivateKey == null) {
+ throw new PgpGeneralException(
+ mContext.getString(R.string.error_could_not_extract_private_key));
+ }
+ updateProgress(R.string.progress_preparing_streams, 0, 100);
+
+ updateProgress(R.string.progress_preparing_signature, 30, 100);
+
+ int type = PGPSignature.CANONICAL_TEXT_DOCUMENT;
+// if (binary) {
+// type = PGPSignature.BINARY_DOCUMENT;
+// }
+
+ // content signer based on signing key algorithm and chosen hash algorithm
+ JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder(signingKey
+ .getPublicKey().getAlgorithm(), mSignatureHashAlgorithm)
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
+
+ PGPSignatureGenerator signatureGenerator = null;
+ PGPV3SignatureGenerator signatureV3Generator = null;
+ if (mSignatureForceV3) {
+ signatureV3Generator = new PGPV3SignatureGenerator(contentSignerBuilder);
+ signatureV3Generator.init(type, signaturePrivateKey);
+ } else {
+ signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder);
+ signatureGenerator.init(type, signaturePrivateKey);
+
+ PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
+ String userId = PgpKeyHelper.getMainUserId(signingKeyRing.getSecretKey());
+ spGen.setSignerUserID(false, userId);
+ signatureGenerator.setHashedSubpackets(spGen.generate());
+ }
+
+ updateProgress(R.string.progress_signing, 40, 100);
+
+ InputStream inStream = mData.getInputStream();
+// if (binary) {
+// byte[] buffer = new byte[1 << 16];
+// int n = 0;
+// while ((n = inStream.read(buffer)) > 0) {
+// if (signatureForceV3) {
+// signatureV3Generator.update(buffer, 0, n);
+// } else {
+// signatureGenerator.update(buffer, 0, n);
+// }
+// }
+// } else {
+ final BufferedReader reader = new BufferedReader(new InputStreamReader(inStream));
+ final byte[] newline = "\r\n".getBytes("UTF-8");
+
+ String line;
+ while ((line = reader.readLine()) != null) {
+ if (mSignatureForceV3) {
+ processLine(line, null, signatureV3Generator);
+ signatureV3Generator.update(newline);
+ } else {
+ processLine(line, null, signatureGenerator);
+ signatureGenerator.update(newline);
+ }
+ }
+// }
+
+ BCPGOutputStream bOut = new BCPGOutputStream(out);
+ if (mSignatureForceV3) {
+ signatureV3Generator.generate().encode(bOut);
+ } else {
+ signatureGenerator.generate().encode(bOut);
+ }
+ out.close();
+ mOutStream.close();
+
+ updateProgress(R.string.progress_done, 100, 100);
+ }
+
+
+ private static void processLine(final String pLine, final ArmoredOutputStream pArmoredOutput,
+ final PGPSignatureGenerator pSignatureGenerator)
+ throws IOException, SignatureException {
+
+ if (pLine == null) {
+ return;
+ }
+
+ final char[] chars = pLine.toCharArray();
+ int len = chars.length;
+
+ while (len > 0) {
+ if (!Character.isWhitespace(chars[len - 1])) {
+ break;
+ }
+ len--;
+ }
+
+ final byte[] data = pLine.substring(0, len).getBytes("UTF-8");
+
+ if (pArmoredOutput != null) {
+ pArmoredOutput.write(data);
+ }
+ pSignatureGenerator.update(data);
+ }
+
+ private static void processLine(final String pLine, final ArmoredOutputStream pArmoredOutput,
+ final PGPV3SignatureGenerator pSignatureGenerator)
+ throws IOException, SignatureException {
+
+ if (pLine == null) {
+ return;
+ }
+
+ final char[] chars = pLine.toCharArray();
+ int len = chars.length;
+
+ while (len > 0) {
+ if (!Character.isWhitespace(chars[len - 1])) {
+ break;
+ }
+ len--;
+ }
+
+ final byte[] data = pLine.substring(0, len).getBytes("UTF-8");
+
+ if (pArmoredOutput != null) {
+ pArmoredOutput.write(data);
+ }
+ pSignatureGenerator.update(data);
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpToX509.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpToX509.java
new file mode 100644
index 000000000..5bb1665b6
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpToX509.java
@@ -0,0 +1,313 @@
+/*
+ * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
+ *
+ * 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.pgp;
+
+import org.spongycastle.asn1.DERObjectIdentifier;
+import org.spongycastle.asn1.x509.AuthorityKeyIdentifier;
+import org.spongycastle.asn1.x509.BasicConstraints;
+import org.spongycastle.asn1.x509.GeneralName;
+import org.spongycastle.asn1.x509.GeneralNames;
+import org.spongycastle.asn1.x509.SubjectKeyIdentifier;
+import org.spongycastle.asn1.x509.X509Extensions;
+import org.spongycastle.asn1.x509.X509Name;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPPrivateKey;
+import org.spongycastle.openpgp.PGPPublicKey;
+import org.spongycastle.openpgp.PGPSecretKey;
+import org.spongycastle.x509.X509V3CertificateGenerator;
+import org.spongycastle.x509.extension.AuthorityKeyIdentifierStructure;
+import org.spongycastle.x509.extension.SubjectKeyIdentifierStructure;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.util.Log;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SignatureException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.text.DateFormat;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.Vector;
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+
+public class PgpToX509 {
+ public static final String DN_COMMON_PART_O = "OpenPGP to X.509 Bridge";
+ public static final String DN_COMMON_PART_OU = "OpenPGP Keychain cert";
+
+ /**
+ * Creates a self-signed certificate from a public and private key. The (critical) key-usage
+ * extension is set up with: digital signature, non-repudiation, key-encipherment, key-agreement
+ * and certificate-signing. The (non-critical) Netscape extension is set up with: SSL client and
+ * S/MIME. A URI subjectAltName may also be set up.
+ *
+ * @param pubKey public key
+ * @param privKey private key
+ * @param subject subject (and issuer) DN for this certificate, RFC 2253 format preferred.
+ * @param startDate date from which the certificate will be valid (defaults to current date and time
+ * if null)
+ * @param endDate date until which the certificate will be valid (defaults to current date and time
+ * if null) *
+ * @param subjAltNameURI URI to be placed in subjectAltName
+ * @return self-signed certificate
+ * @throws InvalidKeyException
+ * @throws SignatureException
+ * @throws NoSuchAlgorithmException
+ * @throws IllegalStateException
+ * @throws NoSuchProviderException
+ * @throws CertificateException
+ * @throws Exception
+ * @author Bruno Harbulot
+ */
+ public static X509Certificate createSelfSignedCert(
+ PublicKey pubKey, PrivateKey privKey, X509Name subject, Date startDate, Date endDate,
+ String subjAltNameURI)
+ throws InvalidKeyException, IllegalStateException, NoSuchAlgorithmException,
+ SignatureException, CertificateException, NoSuchProviderException {
+
+ X509V3CertificateGenerator certGenerator = new X509V3CertificateGenerator();
+
+ certGenerator.reset();
+ /*
+ * Sets up the subject distinguished name. Since it's a self-signed certificate, issuer and
+ * subject are the same.
+ */
+ certGenerator.setIssuerDN(subject);
+ certGenerator.setSubjectDN(subject);
+
+ /*
+ * Sets up the validity dates.
+ */
+ if (startDate == null) {
+ startDate = new Date(System.currentTimeMillis());
+ }
+ certGenerator.setNotBefore(startDate);
+ if (endDate == null) {
+ endDate = new Date(startDate.getTime() + (365L * 24L * 60L * 60L * 1000L));
+ Log.d(Constants.TAG, "end date is=" + DateFormat.getDateInstance().format(endDate));
+ }
+
+ certGenerator.setNotAfter(endDate);
+
+ /*
+ * The serial-number of this certificate is 1. It makes sense because it's self-signed.
+ */
+ certGenerator.setSerialNumber(BigInteger.ONE);
+ /*
+ * Sets the public-key to embed in this certificate.
+ */
+ certGenerator.setPublicKey(pubKey);
+ /*
+ * Sets the signature algorithm.
+ */
+ String pubKeyAlgorithm = pubKey.getAlgorithm();
+ if (pubKeyAlgorithm.equals("DSA")) {
+ certGenerator.setSignatureAlgorithm("SHA1WithDSA");
+ } else if (pubKeyAlgorithm.equals("RSA")) {
+ certGenerator.setSignatureAlgorithm("SHA1WithRSAEncryption");
+ } else {
+ RuntimeException re = new RuntimeException("Algorithm not recognised: "
+ + pubKeyAlgorithm);
+ Log.e(Constants.TAG, re.getMessage(), re);
+ throw re;
+ }
+
+ /*
+ * Adds the Basic Constraint (CA: true) extension.
+ */
+ certGenerator.addExtension(X509Extensions.BasicConstraints, true,
+ new BasicConstraints(true));
+
+ /*
+ * Adds the subject key identifier extension.
+ */
+ SubjectKeyIdentifier subjectKeyIdentifier = new SubjectKeyIdentifierStructure(pubKey);
+ certGenerator
+ .addExtension(X509Extensions.SubjectKeyIdentifier, false, subjectKeyIdentifier);
+
+ /*
+ * Adds the authority key identifier extension.
+ */
+ AuthorityKeyIdentifier authorityKeyIdentifier = new AuthorityKeyIdentifierStructure(pubKey);
+ certGenerator.addExtension(X509Extensions.AuthorityKeyIdentifier, false,
+ authorityKeyIdentifier);
+
+ /*
+ * Adds the subject alternative-name extension.
+ */
+ if (subjAltNameURI != null) {
+ GeneralNames subjectAltNames = new GeneralNames(new GeneralName(
+ GeneralName.uniformResourceIdentifier, subjAltNameURI));
+ certGenerator.addExtension(X509Extensions.SubjectAlternativeName, false,
+ subjectAltNames);
+ }
+
+ /*
+ * Creates and sign this certificate with the private key corresponding to the public key of
+ * the certificate (hence the name "self-signed certificate").
+ */
+ X509Certificate cert = certGenerator.generate(privKey);
+
+ /*
+ * Checks that this certificate has indeed been correctly signed.
+ */
+ cert.verify(pubKey);
+
+ return cert;
+ }
+
+ /**
+ * Creates a self-signed certificate from a PGP Secret Key.
+ *
+ * @param pgpSecKey PGP Secret Key (from which one can extract the public and private
+ * keys and other attributes).
+ * @param pgpPrivKey PGP Private Key corresponding to the Secret Key (password callbacks
+ * should be done before calling this method)
+ * @param subjAltNameURI optional URI to embed in the subject alternative-name
+ * @return self-signed certificate
+ * @throws PGPException
+ * @throws NoSuchProviderException
+ * @throws InvalidKeyException
+ * @throws NoSuchAlgorithmException
+ * @throws SignatureException
+ * @throws CertificateException
+ * @author Bruno Harbulot
+ */
+ public static X509Certificate createSelfSignedCert(
+ PGPSecretKey pgpSecKey, PGPPrivateKey pgpPrivKey, String subjAltNameURI)
+ throws PGPException, NoSuchProviderException, InvalidKeyException, NoSuchAlgorithmException,
+ SignatureException, CertificateException {
+ // get public key from secret key
+ PGPPublicKey pgpPubKey = pgpSecKey.getPublicKey();
+
+ // LOGGER.info("Key ID: " + Long.toHexString(pgpPubKey.getKeyID() & 0xffffffffL));
+
+ /*
+ * The X.509 Name to be the subject DN is prepared. The CN is extracted from the Secret Key
+ * user ID.
+ */
+ Vector<DERObjectIdentifier> x509NameOids = new Vector<DERObjectIdentifier>();
+ Vector<String> x509NameValues = new Vector<String>();
+
+ x509NameOids.add(X509Name.O);
+ x509NameValues.add(DN_COMMON_PART_O);
+
+ x509NameOids.add(X509Name.OU);
+ x509NameValues.add(DN_COMMON_PART_OU);
+
+ for (@SuppressWarnings("unchecked")
+ Iterator<Object> it = (Iterator<Object>) pgpSecKey.getUserIDs(); it.hasNext(); ) {
+ Object attrib = it.next();
+ x509NameOids.add(X509Name.CN);
+ x509NameValues.add("CryptoCall");
+ // x509NameValues.add(attrib.toString());
+ }
+
+ /*
+ * Currently unused.
+ */
+ Log.d(Constants.TAG, "User attributes: ");
+ for (@SuppressWarnings("unchecked")
+ Iterator<Object> it = (Iterator<Object>) pgpSecKey.getUserAttributes(); it.hasNext(); ) {
+ Object attrib = it.next();
+ Log.d(Constants.TAG, " - " + attrib + " -- " + attrib.getClass());
+ }
+
+ X509Name x509name = new X509Name(x509NameOids, x509NameValues);
+
+ Log.d(Constants.TAG, "Subject DN: " + x509name);
+
+ /*
+ * To check the signature from the certificate on the recipient side, the creation time
+ * needs to be embedded in the certificate. It seems natural to make this creation time be
+ * the "not-before" date of the X.509 certificate. Unlimited PGP keys have a validity of 0
+ * second. In this case, the "not-after" date will be the same as the not-before date. This
+ * is something that needs to be checked by the service receiving this certificate.
+ */
+ Date creationTime = pgpPubKey.getCreationTime();
+ Log.d(Constants.TAG,
+ "pgp pub key creation time=" + DateFormat.getDateInstance().format(creationTime));
+ Log.d(Constants.TAG, "pgp valid seconds=" + pgpPubKey.getValidSeconds());
+ Date validTo = null;
+ if (pgpPubKey.getValidSeconds() > 0) {
+ validTo = new Date(creationTime.getTime() + 1000L * pgpPubKey.getValidSeconds());
+ }
+
+ X509Certificate selfSignedCert = createSelfSignedCert(
+ pgpPubKey.getKey(Constants.BOUNCY_CASTLE_PROVIDER_NAME), pgpPrivKey.getKey(),
+ x509name, creationTime, validTo, subjAltNameURI);
+
+ return selfSignedCert;
+ }
+
+ /**
+ * This is a password callback handler that will fill in a password automatically. Useful to
+ * configure passwords in advance, but should be used with caution depending on how much you
+ * allow passwords to be stored within your application.
+ *
+ * @author Bruno Harbulot.
+ */
+ public static final class PredefinedPasswordCallbackHandler implements CallbackHandler {
+
+ private char[] mPassword;
+ private String mPrompt;
+
+ public PredefinedPasswordCallbackHandler(String password) {
+ this(password == null ? null : password.toCharArray(), null);
+ }
+
+ public PredefinedPasswordCallbackHandler(char[] password) {
+ this(password, null);
+ }
+
+ public PredefinedPasswordCallbackHandler(String password, String prompt) {
+ this(password == null ? null : password.toCharArray(), prompt);
+ }
+
+ public PredefinedPasswordCallbackHandler(char[] password, String prompt) {
+ this.mPassword = password;
+ this.mPrompt = prompt;
+ }
+
+ public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+ for (Callback callback : callbacks) {
+ if (callback instanceof PasswordCallback) {
+ PasswordCallback pwCallback = (PasswordCallback) callback;
+ if ((this.mPrompt == null) || (this.mPrompt.equals(pwCallback.getPrompt()))) {
+ pwCallback.setPassword(this.mPassword);
+ }
+ } else {
+ throw new UnsupportedCallbackException(callback, "Unrecognised callback.");
+ }
+ }
+ }
+
+ protected final Object clone() throws CloneNotSupportedException {
+ throw new CloneNotSupportedException();
+ }
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/exception/NoAsymmetricEncryptionException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/exception/NoAsymmetricEncryptionException.java
new file mode 100644
index 000000000..23c4bbbd9
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/exception/NoAsymmetricEncryptionException.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2012-2013 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
+ *
+ * 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.pgp.exception;
+
+public class NoAsymmetricEncryptionException extends Exception {
+ static final long serialVersionUID = 0xf812773343L;
+
+ public NoAsymmetricEncryptionException() {
+ super();
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/exception/PgpGeneralException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/exception/PgpGeneralException.java
new file mode 100644
index 000000000..418445367
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/exception/PgpGeneralException.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2012-2013 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
+ *
+ * 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.pgp.exception;
+
+public class PgpGeneralException extends Exception {
+ static final long serialVersionUID = 0xf812773342L;
+
+ public PgpGeneralException(String message) {
+ super(message);
+ }
+ public PgpGeneralException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/exception/PgpGeneralMsgIdException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/exception/PgpGeneralMsgIdException.java
new file mode 100644
index 000000000..caa7842db
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/exception/PgpGeneralMsgIdException.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2012-2013 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
+ *
+ * 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.pgp.exception;
+
+import android.content.Context;
+
+public class PgpGeneralMsgIdException extends Exception {
+ static final long serialVersionUID = 0xf812773343L;
+
+ private final int mMessageId;
+
+ public PgpGeneralMsgIdException(int messageId) {
+ super("msg[" + messageId + "]");
+ mMessageId = messageId;
+ }
+
+ public PgpGeneralException getContextualized(Context context) {
+ return new PgpGeneralException(context.getString(mMessageId), this);
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java
new file mode 100644
index 000000000..fc25faecd
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java
@@ -0,0 +1,297 @@
+/*
+ * 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.provider;
+
+import android.net.Uri;
+import android.provider.BaseColumns;
+
+import org.sufficientlysecure.keychain.Constants;
+
+public class KeychainContract {
+
+ interface KeyRingsColumns {
+ String MASTER_KEY_ID = "master_key_id"; // not a database id
+ String KEY_RING_DATA = "key_ring_data"; // PGPPublicKeyRing / PGPSecretKeyRing blob
+ }
+
+ interface KeysColumns {
+ String MASTER_KEY_ID = "master_key_id"; // not a database id
+ String RANK = "rank";
+
+ String KEY_ID = "key_id"; // not a database id
+ String ALGORITHM = "algorithm";
+ String FINGERPRINT = "fingerprint";
+
+ String KEY_SIZE = "key_size";
+ String CAN_SIGN = "can_sign";
+ String CAN_ENCRYPT = "can_encrypt";
+ String CAN_CERTIFY = "can_certify";
+ String IS_REVOKED = "is_revoked";
+
+ String CREATION = "creation";
+ String EXPIRY = "expiry";
+ }
+
+ interface UserIdsColumns {
+ String MASTER_KEY_ID = "master_key_id"; // foreign key to key_rings._ID
+ String USER_ID = "user_id"; // not a database id
+ String RANK = "rank"; // ONLY used for sorting! no key, no nothing!
+ String IS_PRIMARY = "is_primary";
+ String IS_REVOKED = "is_revoked";
+ }
+
+ interface CertsColumns {
+ String MASTER_KEY_ID = "master_key_id";
+ String RANK = "rank";
+ String KEY_ID_CERTIFIER = "key_id_certifier";
+ String TYPE = "type";
+ String VERIFIED = "verified";
+ String CREATION = "creation";
+ String DATA = "data";
+ }
+
+ interface ApiAppsColumns {
+ String PACKAGE_NAME = "package_name";
+ String PACKAGE_SIGNATURE = "package_signature";
+ }
+
+ interface ApiAppsAccountsColumns {
+ String ACCOUNT_NAME = "account_name";
+ String KEY_ID = "key_id"; // not a database id
+ String ENCRYPTION_ALGORITHM = "encryption_algorithm";
+ String HASH_ALORITHM = "hash_algorithm";
+ String COMPRESSION = "compression";
+ String PACKAGE_NAME = "package_name"; // foreign key to api_apps.package_name
+ }
+
+ public static final class KeyTypes {
+ public static final int PUBLIC = 0;
+ public static final int SECRET = 1;
+ }
+
+ public static final String CONTENT_AUTHORITY = Constants.PACKAGE_NAME + ".provider";
+
+ 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_DATA = "data";
+
+ public static final String PATH_UNIFIED = "unified";
+
+ public static final String PATH_FIND = "find";
+ public static final String PATH_BY_EMAIL = "email";
+ public static final String PATH_BY_SUBKEY = "subkey";
+
+ public static final String PATH_PUBLIC = "public";
+ public static final String PATH_SECRET = "secret";
+ public static final String PATH_USER_IDS = "user_ids";
+ public static final String PATH_KEYS = "keys";
+ public static final String PATH_CERTS = "certs";
+
+ public static final String BASE_API_APPS = "api_apps";
+ public static final String PATH_ACCOUNTS = "accounts";
+
+ public static class KeyRings implements BaseColumns, KeysColumns, UserIdsColumns {
+ public static final String MASTER_KEY_ID = KeysColumns.MASTER_KEY_ID;
+ public static final String IS_REVOKED = KeysColumns.IS_REVOKED;
+ public static final String VERIFIED = CertsColumns.VERIFIED;
+ public static final String HAS_SECRET = "has_secret";
+
+ public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
+ .appendPath(BASE_KEY_RINGS).build();
+
+ public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.sufficientlysecure.openkeychain.key_ring";
+ public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.sufficientlysecure.openkeychain.key_ring";
+
+ public static Uri buildUnifiedKeyRingsUri() {
+ return CONTENT_URI.buildUpon().appendPath(PATH_UNIFIED).build();
+ }
+
+ public static Uri buildGenericKeyRingUri(String masterKeyId) {
+ return CONTENT_URI.buildUpon().appendPath(masterKeyId).build();
+ }
+ public static Uri buildUnifiedKeyRingUri(String masterKeyId) {
+ return CONTENT_URI.buildUpon().appendPath(masterKeyId).appendPath(PATH_UNIFIED).build();
+ }
+ public static Uri buildUnifiedKeyRingUri(Uri uri) {
+ return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1)).appendPath(PATH_UNIFIED).build();
+ }
+
+ public static Uri buildUnifiedKeyRingsFindByEmailUri(String email) {
+ return CONTENT_URI.buildUpon().appendPath(PATH_FIND).appendPath(PATH_BY_EMAIL).appendPath(email).build();
+ }
+ public static Uri buildUnifiedKeyRingsFindBySubkeyUri(String subkey) {
+ return CONTENT_URI.buildUpon().appendPath(PATH_FIND).appendPath(PATH_BY_SUBKEY).appendPath(subkey).build();
+ }
+
+ }
+
+ public static class KeyRingData implements KeyRingsColumns, BaseColumns {
+ public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
+ .appendPath(BASE_KEY_RINGS).build();
+
+ public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.sufficientlysecure.openkeychain.key_ring_data";
+ public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.sufficientlysecure.openkeychain.key_ring_data";
+
+ public static Uri buildPublicKeyRingUri() {
+ return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).build();
+ }
+ public static Uri buildPublicKeyRingUri(String masterKeyId) {
+ return CONTENT_URI.buildUpon().appendPath(masterKeyId).appendPath(PATH_PUBLIC).build();
+ }
+ public static Uri buildPublicKeyRingUri(Uri uri) {
+ return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1)).appendPath(PATH_PUBLIC).build();
+ }
+
+ public static Uri buildSecretKeyRingUri() {
+ return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).build();
+ }
+ public static Uri buildSecretKeyRingUri(String masterKeyId) {
+ return CONTENT_URI.buildUpon().appendPath(masterKeyId).appendPath(PATH_SECRET).build();
+ }
+ public static Uri buildSecretKeyRingUri(Uri uri) {
+ return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1)).appendPath(PATH_SECRET).build();
+ }
+
+ }
+
+ public static class Keys implements KeysColumns, BaseColumns {
+ public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
+ .appendPath(BASE_KEY_RINGS).build();
+
+ /**
+ * Use if multiple items get returned
+ */
+ public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.sufficientlysecure.openkeychain.key";
+
+ /**
+ * Use if a single item is returned
+ */
+ public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.sufficientlysecure.openkeychain.key";
+
+ public static Uri buildKeysUri(String masterKeyId) {
+ return CONTENT_URI.buildUpon().appendPath(masterKeyId).appendPath(PATH_KEYS).build();
+ }
+ public static Uri buildKeysUri(Uri uri) {
+ return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1)).appendPath(PATH_KEYS).build();
+ }
+
+ }
+
+ public static class UserIds implements UserIdsColumns, BaseColumns {
+ public static final String VERIFIED = "verified";
+ public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
+ .appendPath(BASE_KEY_RINGS).build();
+
+ /**
+ * Use if multiple items get returned
+ */
+ public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.sufficientlysecure.openkeychain.user_id";
+
+ /**
+ * Use if a single item is returned
+ */
+ public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.sufficientlysecure.openkeychain.user_id";
+
+ public static Uri buildUserIdsUri(String masterKeyId) {
+ return CONTENT_URI.buildUpon().appendPath(masterKeyId).appendPath(PATH_USER_IDS).build();
+ }
+ public static Uri buildUserIdsUri(Uri uri) {
+ return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1)).appendPath(PATH_USER_IDS).build();
+ }
+ }
+
+ public static class ApiApps implements ApiAppsColumns, BaseColumns {
+ public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
+ .appendPath(BASE_API_APPS).build();
+
+ /**
+ * Use if multiple items get returned
+ */
+ public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.sufficientlysecure.openkeychain.api_apps";
+
+ /**
+ * Use if a single item is returned
+ */
+ public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.sufficientlysecure.openkeychain.api_app";
+
+ public static Uri buildByPackageNameUri(String packageName) {
+ return CONTENT_URI.buildUpon().appendEncodedPath(packageName).build();
+ }
+ }
+
+ public static class ApiAccounts implements ApiAppsAccountsColumns, BaseColumns {
+ public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
+ .appendPath(BASE_API_APPS).build();
+
+ /**
+ * Use if multiple items get returned
+ */
+ public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.sufficientlysecure.openkeychain.api_app.accounts";
+
+ /**
+ * Use if a single item is returned
+ */
+ public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.sufficientlysecure.openkeychain.api_app.account";
+
+ public static Uri buildBaseUri(String packageName) {
+ return CONTENT_URI.buildUpon().appendEncodedPath(packageName).appendPath(PATH_ACCOUNTS)
+ .build();
+ }
+
+ public static Uri buildByPackageAndAccountUri(String packageName, String accountName) {
+ return CONTENT_URI.buildUpon().appendEncodedPath(packageName).appendPath(PATH_ACCOUNTS)
+ .appendEncodedPath(accountName).build();
+ }
+ }
+
+ public static class Certs implements CertsColumns, BaseColumns {
+ public static final String USER_ID = UserIdsColumns.USER_ID;
+ public static final String SIGNER_UID = "signer_user_id";
+
+ public static final int VERIFIED_SECRET = 1;
+ public static final int VERIFIED_SELF = 2;
+
+ public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
+ .appendPath(BASE_KEY_RINGS).build();
+
+ public static Uri buildCertsUri(String masterKeyId) {
+ return CONTENT_URI.buildUpon().appendPath(masterKeyId).appendPath(PATH_CERTS).build();
+ }
+ public static Uri buildCertsSpecificUri(String masterKeyId, String rank, String certifier) {
+ return CONTENT_URI.buildUpon().appendPath(masterKeyId).appendPath(PATH_CERTS).appendPath(rank).appendPath(certifier).build();
+ }
+ public static Uri buildCertsUri(Uri uri) {
+ return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1)).appendPath(PATH_CERTS).build();
+ }
+
+ }
+
+ public static class DataStream {
+ public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
+ .appendPath(BASE_DATA).build();
+
+ public static Uri buildDataStreamUri(String streamFilename) {
+ return CONTENT_URI.buildUpon().appendPath(streamFilename).build();
+ }
+ }
+
+ private KeychainContract() {
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java
new file mode 100644
index 000000000..8674ad94b
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java
@@ -0,0 +1,301 @@
+/*
+ * 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.provider;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.provider.BaseColumns;
+
+import org.spongycastle.openpgp.PGPKeyRing;
+import org.spongycastle.openpgp.PGPPublicKeyRing;
+import org.spongycastle.openpgp.PGPSecretKeyRing;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.pgp.PgpConversionHelper;
+import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsColumns;
+import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsAccountsColumns;
+import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingsColumns;
+import org.sufficientlysecure.keychain.provider.KeychainContract.KeysColumns;
+import org.sufficientlysecure.keychain.provider.KeychainContract.UserIdsColumns;
+import org.sufficientlysecure.keychain.provider.KeychainContract.CertsColumns;
+import org.sufficientlysecure.keychain.util.Log;
+
+import java.io.IOException;
+
+public class KeychainDatabase extends SQLiteOpenHelper {
+ private static final String DATABASE_NAME = "openkeychain.db";
+ private static final int DATABASE_VERSION = 1;
+ static Boolean apgHack = false;
+
+ public interface Tables {
+ String KEY_RINGS_PUBLIC = "keyrings_public";
+ String KEY_RINGS_SECRET = "keyrings_secret";
+ String KEYS = "keys";
+ String USER_IDS = "user_ids";
+ String CERTS = "certs";
+ String API_APPS = "api_apps";
+ String API_ACCOUNTS = "api_accounts";
+ }
+
+ private static final String CREATE_KEYRINGS_PUBLIC =
+ "CREATE TABLE IF NOT EXISTS keyrings_public ("
+ + KeyRingsColumns.MASTER_KEY_ID + " INTEGER PRIMARY KEY,"
+ + KeyRingsColumns.KEY_RING_DATA + " BLOB"
+ + ")";
+
+ private static final String CREATE_KEYRINGS_SECRET =
+ "CREATE TABLE IF NOT EXISTS keyrings_secret ("
+ + KeyRingsColumns.MASTER_KEY_ID + " INTEGER PRIMARY KEY,"
+ + KeyRingsColumns.KEY_RING_DATA + " BLOB,"
+ + "FOREIGN KEY(" + KeyRingsColumns.MASTER_KEY_ID + ") "
+ + "REFERENCES keyrings_public(" + KeyRingsColumns.MASTER_KEY_ID + ") ON DELETE CASCADE"
+ + ")";
+
+ private static final String CREATE_KEYS =
+ "CREATE TABLE IF NOT EXISTS " + Tables.KEYS + " ("
+ + KeysColumns.MASTER_KEY_ID + " INTEGER, "
+ + KeysColumns.RANK + " INTEGER, "
+
+ + KeysColumns.KEY_ID + " INTEGER, "
+ + KeysColumns.KEY_SIZE + " INTEGER, "
+ + KeysColumns.ALGORITHM + " INTEGER, "
+ + KeysColumns.FINGERPRINT + " BLOB, "
+
+ + KeysColumns.CAN_CERTIFY + " BOOLEAN, "
+ + KeysColumns.CAN_SIGN + " BOOLEAN, "
+ + KeysColumns.CAN_ENCRYPT + " BOOLEAN, "
+ + KeysColumns.IS_REVOKED + " BOOLEAN, "
+
+ + KeysColumns.CREATION + " INTEGER, "
+ + KeysColumns.EXPIRY + " INTEGER, "
+
+ + "PRIMARY KEY(" + KeysColumns.MASTER_KEY_ID + ", " + KeysColumns.RANK + "),"
+ + "FOREIGN KEY(" + KeysColumns.MASTER_KEY_ID + ") REFERENCES "
+ + Tables.KEY_RINGS_PUBLIC + "(" + KeyRingsColumns.MASTER_KEY_ID + ") ON DELETE CASCADE"
+ + ")";
+
+ private static final String CREATE_USER_IDS =
+ "CREATE TABLE IF NOT EXISTS " + Tables.USER_IDS + "("
+ + UserIdsColumns.MASTER_KEY_ID + " INTEGER, "
+ + UserIdsColumns.USER_ID + " CHARMANDER, "
+
+ + UserIdsColumns.IS_PRIMARY + " BOOLEAN, "
+ + UserIdsColumns.IS_REVOKED + " BOOLEAN, "
+ + UserIdsColumns.RANK+ " INTEGER, "
+
+ + "PRIMARY KEY(" + UserIdsColumns.MASTER_KEY_ID + ", " + UserIdsColumns.USER_ID + "), "
+ + "UNIQUE (" + UserIdsColumns.MASTER_KEY_ID + ", " + UserIdsColumns.RANK + "), "
+ + "FOREIGN KEY(" + UserIdsColumns.MASTER_KEY_ID + ") REFERENCES "
+ + Tables.KEY_RINGS_PUBLIC + "(" + KeyRingsColumns.MASTER_KEY_ID + ") ON DELETE CASCADE"
+ + ")";
+
+ private static final String CREATE_CERTS =
+ "CREATE TABLE IF NOT EXISTS " + Tables.CERTS + "("
+ + CertsColumns.MASTER_KEY_ID + " INTEGER,"
+ + CertsColumns.RANK + " INTEGER, " // rank of certified uid
+
+ + CertsColumns.KEY_ID_CERTIFIER + " INTEGER, " // certifying key
+ + CertsColumns.TYPE + " INTEGER, "
+ + CertsColumns.VERIFIED + " INTEGER, "
+ + CertsColumns.CREATION + " INTEGER, "
+
+ + CertsColumns.DATA + " BLOB, "
+
+ + "PRIMARY KEY(" + CertsColumns.MASTER_KEY_ID + ", " + CertsColumns.RANK + ", "
+ + CertsColumns.KEY_ID_CERTIFIER + "), "
+ + "FOREIGN KEY(" + CertsColumns.MASTER_KEY_ID + ") REFERENCES "
+ + Tables.KEY_RINGS_PUBLIC + "(" + KeyRingsColumns.MASTER_KEY_ID + ") ON DELETE CASCADE,"
+ + "FOREIGN KEY(" + CertsColumns.MASTER_KEY_ID + ", " + CertsColumns.RANK + ") REFERENCES "
+ + Tables.USER_IDS + "(" + UserIdsColumns.MASTER_KEY_ID + ", " + UserIdsColumns.RANK + ") ON DELETE CASCADE"
+ + ")";
+
+ private static final String CREATE_API_APPS = "CREATE TABLE IF NOT EXISTS " + Tables.API_APPS
+ + " (" + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ + ApiAppsColumns.PACKAGE_NAME + " TEXT NOT NULL UNIQUE, "
+ + ApiAppsColumns.PACKAGE_SIGNATURE + " BLOB)";
+
+ private static final String CREATE_API_APPS_ACCOUNTS = "CREATE TABLE IF NOT EXISTS " + Tables.API_ACCOUNTS
+ + " (" + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ + ApiAppsAccountsColumns.ACCOUNT_NAME + " TEXT NOT NULL, "
+ + ApiAppsAccountsColumns.KEY_ID + " INT64, "
+ + ApiAppsAccountsColumns.ENCRYPTION_ALGORITHM + " INTEGER, "
+ + ApiAppsAccountsColumns.HASH_ALORITHM + " INTEGER, "
+ + ApiAppsAccountsColumns.COMPRESSION + " INTEGER, "
+ + ApiAppsAccountsColumns.PACKAGE_NAME + " TEXT NOT NULL, "
+ + "UNIQUE(" + ApiAppsAccountsColumns.ACCOUNT_NAME + ", "
+ + ApiAppsAccountsColumns.PACKAGE_NAME + "), "
+ + "FOREIGN KEY(" + ApiAppsAccountsColumns.PACKAGE_NAME + ") REFERENCES "
+ + Tables.API_APPS + "(" + ApiAppsColumns.PACKAGE_NAME + ") ON DELETE CASCADE)";
+
+ KeychainDatabase(Context context) {
+ super(context, DATABASE_NAME, null, DATABASE_VERSION);
+
+ // make sure this is only done once, on the first instance!
+ boolean iAmIt = false;
+ synchronized(apgHack) {
+ if(!apgHack) {
+ iAmIt = true;
+ apgHack = true;
+ }
+ }
+ // if it's us, do the import
+ if(iAmIt)
+ checkAndImportApg(context);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ Log.w(Constants.TAG, "Creating database...");
+
+ db.execSQL(CREATE_KEYRINGS_PUBLIC);
+ db.execSQL(CREATE_KEYRINGS_SECRET);
+ db.execSQL(CREATE_KEYS);
+ db.execSQL(CREATE_USER_IDS);
+ db.execSQL(CREATE_CERTS);
+ db.execSQL(CREATE_API_APPS);
+ db.execSQL(CREATE_API_APPS_ACCOUNTS);
+ }
+
+ @Override
+ public void onOpen(SQLiteDatabase db) {
+ super.onOpen(db);
+ if (!db.isReadOnly()) {
+ // Enable foreign key constraints
+ db.execSQL("PRAGMA foreign_keys=ON;");
+ // TODO remove, once we remove the "always migrate" debug stuff
+ // db.execSQL("DROP TABLE certs;");
+ // db.execSQL("DROP TABLE user_ids;");
+ db.execSQL(CREATE_USER_IDS);
+ db.execSQL(CREATE_CERTS);
+ }
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int old, int nu) {
+ // don't care (this is version 1)
+ }
+
+ /** This method tries to import data from a provided database.
+ *
+ * The sole assumptions made on this db are that there is a key_rings table
+ * with a key_ring_data, a master_key_id and a type column, the latter of
+ * which should be 1 for secret keys and 0 for public keys.
+ */
+ public void checkAndImportApg(Context context) {
+
+ boolean hasApgDb = false; {
+ // It's the Java way =(
+ String[] dbs = context.databaseList();
+ for(String db : dbs) {
+ if(db.equals("apg.db")) {
+ hasApgDb = true;
+ break;
+ }
+ }
+ }
+
+ if(!hasApgDb)
+ return;
+
+ Log.d(Constants.TAG, "apg.db exists! Importing...");
+
+ SQLiteDatabase db = new SQLiteOpenHelper(context, "apg.db", null, 1) {
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ // should never happen
+ assert false;
+ }
+ @Override
+ public void onDowngrade(SQLiteDatabase db, int old, int nu) {
+ // don't care
+ }
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int old, int nu) {
+ // don't care either
+ }
+ }.getReadableDatabase();
+
+ // kill current!
+ { // TODO don't kill current.
+ Log.d(Constants.TAG, "Truncating db...");
+ SQLiteDatabase d = getWritableDatabase();
+ d.execSQL("DELETE FROM keyrings_public");
+ d.close();
+ Log.d(Constants.TAG, "Ok.");
+ }
+
+ Cursor c = null;
+ try {
+ // we insert in two steps: first, all public keys that have secret keys
+ c = db.rawQuery("SELECT key_ring_data FROM key_rings WHERE type = 1 OR EXISTS ("
+ + " SELECT 1 FROM key_rings d2 WHERE key_rings.master_key_id = d2.master_key_id"
+ + " AND d2.type = 1) ORDER BY type ASC", null);
+ Log.d(Constants.TAG, "Importing " + c.getCount() + " secret keyrings from apg.db...");
+ for(int i = 0; i < c.getCount(); i++) {
+ c.moveToPosition(i);
+ byte[] data = c.getBlob(0);
+ PGPKeyRing ring = PgpConversionHelper.BytesToPGPKeyRing(data);
+ if(ring instanceof PGPPublicKeyRing)
+ ProviderHelper.saveKeyRing(context, (PGPPublicKeyRing) ring);
+ else if(ring instanceof PGPSecretKeyRing)
+ ProviderHelper.saveKeyRing(context, (PGPSecretKeyRing) ring);
+ else {
+ Log.e(Constants.TAG, "Unknown blob data type!");
+ }
+ }
+
+ // afterwards, insert all keys, starting with public keys that have secret keys, then
+ // secret keys, then all others. this order is necessary to ensure all certifications
+ // are recognized properly.
+ c = db.rawQuery("SELECT key_ring_data FROM key_rings ORDER BY (type = 0 AND EXISTS ("
+ + " SELECT 1 FROM key_rings d2 WHERE key_rings.master_key_id = d2.master_key_id AND"
+ + " d2.type = 1)) DESC, type DESC", null);
+ // import from old database
+ Log.d(Constants.TAG, "Importing " + c.getCount() + " keyrings from apg.db...");
+ for(int i = 0; i < c.getCount(); i++) {
+ c.moveToPosition(i);
+ byte[] data = c.getBlob(0);
+ PGPKeyRing ring = PgpConversionHelper.BytesToPGPKeyRing(data);
+ if(ring instanceof PGPPublicKeyRing)
+ ProviderHelper.saveKeyRing(context, (PGPPublicKeyRing) ring);
+ else if(ring instanceof PGPSecretKeyRing)
+ ProviderHelper.saveKeyRing(context, (PGPSecretKeyRing) ring);
+ else {
+ Log.e(Constants.TAG, "Unknown blob data type!");
+ }
+ }
+
+ } catch(IOException e) {
+ Log.e(Constants.TAG, "Error importing apg db!", e);
+ return;
+ } finally {
+ if(c != null)
+ c.close();
+ if(db != null)
+ db.close();
+ }
+
+ // TODO delete old db, if we are sure this works
+ // context.deleteDatabase("apg.db");
+ Log.d(Constants.TAG, "All done, (not) deleting apg.db");
+
+
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java
new file mode 100644
index 000000000..9b9e4991d
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java
@@ -0,0 +1,752 @@
+/*
+ * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
+ *
+ * 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.provider;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.UriMatcher;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.database.sqlite.SQLiteConstraintException;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.net.Uri;
+import android.text.TextUtils;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAccounts;
+import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
+import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
+import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData;
+import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
+import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
+import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
+import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
+import org.sufficientlysecure.keychain.util.Log;
+
+import java.util.Arrays;
+import java.util.HashMap;
+
+public class KeychainProvider extends ContentProvider {
+
+ private static final int KEY_RINGS_UNIFIED = 101;
+ private static final int KEY_RINGS_PUBLIC = 102;
+ private static final int KEY_RINGS_SECRET = 103;
+
+ private static final int KEY_RING_UNIFIED = 200;
+ private static final int KEY_RING_KEYS = 201;
+ private static final int KEY_RING_USER_IDS = 202;
+ private static final int KEY_RING_PUBLIC = 203;
+ private static final int KEY_RING_SECRET = 204;
+ private static final int KEY_RING_CERTS = 205;
+ private static final int KEY_RING_CERTS_SPECIFIC = 206;
+
+ private static final int API_APPS = 301;
+ private static final int API_APPS_BY_PACKAGE_NAME = 303;
+ private static final int API_ACCOUNTS = 304;
+ private static final int API_ACCOUNTS_BY_ACCOUNT_NAME = 306;
+
+ private static final int KEY_RINGS_FIND_BY_EMAIL = 400;
+ private static final int KEY_RINGS_FIND_BY_SUBKEY = 401;
+
+ // private static final int DATA_STREAM = 501;
+
+ protected UriMatcher mUriMatcher;
+
+ /**
+ * Build and return a {@link UriMatcher} that catches all {@link Uri} variations supported by
+ * this {@link ContentProvider}.
+ */
+ protected UriMatcher buildUriMatcher() {
+ final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
+
+ String authority = KeychainContract.CONTENT_AUTHORITY;
+
+ /**
+ * list key_rings
+ *
+ * <pre>
+ * key_rings/unified
+ * key_rings/public
+ * </pre>
+ */
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS
+ + "/" + KeychainContract.PATH_UNIFIED,
+ KEY_RINGS_UNIFIED);
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS
+ + "/" + KeychainContract.PATH_PUBLIC,
+ KEY_RINGS_PUBLIC);
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS
+ + "/" + KeychainContract.PATH_SECRET,
+ KEY_RINGS_SECRET);
+
+ /**
+ * find by criteria other than master key id
+ *
+ * key_rings/find/email/_
+ * key_rings/find/subkey/_
+ *
+ */
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ + KeychainContract.PATH_FIND + "/" + KeychainContract.PATH_BY_EMAIL + "/*",
+ KEY_RINGS_FIND_BY_EMAIL);
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ + KeychainContract.PATH_FIND + "/" + KeychainContract.PATH_BY_SUBKEY + "/*",
+ KEY_RINGS_FIND_BY_SUBKEY);
+
+ /**
+ * list key_ring specifics
+ *
+ * <pre>
+ * key_rings/_/unified
+ * key_rings/_/keys
+ * key_rings/_/user_ids
+ * key_rings/_/public
+ * key_rings/_/secret
+ * key_rings/_/certs
+ * key_rings/_/certs/_/_
+ * </pre>
+ */
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/"
+ + KeychainContract.PATH_UNIFIED,
+ KEY_RING_UNIFIED);
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/"
+ + KeychainContract.PATH_KEYS,
+ KEY_RING_KEYS);
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/"
+ + KeychainContract.PATH_USER_IDS,
+ KEY_RING_USER_IDS);
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/"
+ + KeychainContract.PATH_PUBLIC,
+ KEY_RING_PUBLIC);
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/"
+ + KeychainContract.PATH_SECRET,
+ KEY_RING_SECRET);
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/"
+ + KeychainContract.PATH_CERTS,
+ KEY_RING_CERTS);
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/"
+ + KeychainContract.PATH_CERTS + "/*/*",
+ KEY_RING_CERTS_SPECIFIC);
+
+ /**
+ * API apps
+ *
+ * <pre>
+ * api_apps
+ * api_apps/_ (package name)
+ *
+ * api_apps/_/accounts
+ * api_apps/_/accounts/_ (account name)
+ * </pre>
+ */
+ matcher.addURI(authority, KeychainContract.BASE_API_APPS, API_APPS);
+ matcher.addURI(authority, KeychainContract.BASE_API_APPS + "/*", API_APPS_BY_PACKAGE_NAME);
+
+ matcher.addURI(authority, KeychainContract.BASE_API_APPS + "/*/"
+ + KeychainContract.PATH_ACCOUNTS, API_ACCOUNTS);
+ matcher.addURI(authority, KeychainContract.BASE_API_APPS + "/*/"
+ + KeychainContract.PATH_ACCOUNTS + "/*", API_ACCOUNTS_BY_ACCOUNT_NAME);
+
+ /**
+ * data stream
+ *
+ * <pre>
+ * data / _
+ * </pre>
+ */
+ // matcher.addURI(authority, KeychainContract.BASE_DATA + "/*", DATA_STREAM);
+
+ return matcher;
+ }
+
+ private KeychainDatabase mKeychainDatabase;
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean onCreate() {
+ mUriMatcher = buildUriMatcher();
+ return true;
+ }
+
+ public KeychainDatabase getDb() {
+ if(mKeychainDatabase == null)
+ mKeychainDatabase = new KeychainDatabase(getContext());
+ return mKeychainDatabase;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getType(Uri uri) {
+ final int match = mUriMatcher.match(uri);
+ switch (match) {
+ case KEY_RING_PUBLIC:
+ return KeyRings.CONTENT_ITEM_TYPE;
+
+ case KEY_RING_KEYS:
+ return Keys.CONTENT_TYPE;
+
+ case KEY_RING_USER_IDS:
+ return UserIds.CONTENT_TYPE;
+
+ case KEY_RING_SECRET:
+ return KeyRings.CONTENT_ITEM_TYPE;
+
+ case API_APPS:
+ return ApiApps.CONTENT_TYPE;
+
+ case API_APPS_BY_PACKAGE_NAME:
+ return ApiApps.CONTENT_ITEM_TYPE;
+
+ case API_ACCOUNTS:
+ return ApiAccounts.CONTENT_TYPE;
+
+ case API_ACCOUNTS_BY_ACCOUNT_NAME:
+ return ApiAccounts.CONTENT_ITEM_TYPE;
+
+ default:
+ throw new UnsupportedOperationException("Unknown uri: " + uri);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ Log.v(Constants.TAG, "query(uri=" + uri + ", proj=" + Arrays.toString(projection) + ")");
+
+ SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+
+ int match = mUriMatcher.match(uri);
+
+ // all query() parameters, for good measure
+ String groupBy = null, having = null;
+
+ switch (match) {
+ case KEY_RING_UNIFIED:
+ case KEY_RINGS_UNIFIED:
+ case KEY_RINGS_FIND_BY_EMAIL:
+ case KEY_RINGS_FIND_BY_SUBKEY: {
+ HashMap<String, String> projectionMap = new HashMap<String, String>();
+ projectionMap.put(KeyRings._ID, Tables.KEYS + ".oid AS _id");
+ projectionMap.put(KeyRings.MASTER_KEY_ID, Tables.KEYS + "." + Keys.MASTER_KEY_ID);
+ projectionMap.put(KeyRings.KEY_ID, Keys.KEY_ID);
+ projectionMap.put(KeyRings.KEY_SIZE, Keys.KEY_SIZE);
+ projectionMap.put(KeyRings.IS_REVOKED, Tables.KEYS + "." + Keys.IS_REVOKED);
+ projectionMap.put(KeyRings.CAN_CERTIFY, Keys.CAN_CERTIFY);
+ projectionMap.put(KeyRings.CAN_ENCRYPT, Keys.CAN_ENCRYPT);
+ projectionMap.put(KeyRings.CAN_SIGN, Keys.CAN_SIGN);
+ projectionMap.put(KeyRings.CREATION, Tables.KEYS + "." + Keys.CREATION);
+ projectionMap.put(KeyRings.EXPIRY, Keys.EXPIRY);
+ projectionMap.put(KeyRings.ALGORITHM, Keys.ALGORITHM);
+ projectionMap.put(KeyRings.FINGERPRINT, Keys.FINGERPRINT);
+ projectionMap.put(KeyRings.USER_ID, UserIds.USER_ID);
+ projectionMap.put(KeyRings.VERIFIED, KeyRings.VERIFIED);
+ projectionMap.put(KeyRings.HAS_SECRET, "(" + Tables.KEY_RINGS_SECRET + "." + KeyRings.MASTER_KEY_ID + " IS NOT NULL) AS " + KeyRings.HAS_SECRET);
+ qb.setProjectionMap(projectionMap);
+
+ qb.setTables(
+ Tables.KEYS
+ + " INNER JOIN " + Tables.USER_IDS + " ON ("
+ + Tables.KEYS + "." + Keys.MASTER_KEY_ID
+ + " = "
+ + Tables.USER_IDS + "." + UserIds.MASTER_KEY_ID
+ + " AND " + Tables.USER_IDS + "." + UserIds.RANK + " = 0"
+ + ") LEFT JOIN " + Tables.KEY_RINGS_SECRET + " ON ("
+ + Tables.KEYS + "." + Keys.MASTER_KEY_ID
+ + " = "
+ + Tables.KEY_RINGS_SECRET + "." + KeyRings.MASTER_KEY_ID
+ + ") LEFT JOIN " + Tables.CERTS + " ON ("
+ + Tables.KEYS + "." + Keys.MASTER_KEY_ID
+ + " = "
+ + Tables.CERTS + "." + KeyRings.MASTER_KEY_ID
+ + " AND " + Tables.CERTS + "." + Certs.VERIFIED
+ + " = " + Certs.VERIFIED_SECRET
+ + ")"
+ );
+ qb.appendWhere(Tables.KEYS + "." + Keys.RANK + " = 0");
+ // in case there are multiple verifying certificates
+ groupBy = Tables.KEYS + "." + Keys.MASTER_KEY_ID;
+
+ switch(match) {
+ case KEY_RING_UNIFIED: {
+ qb.appendWhere(" AND " + Tables.KEYS + "." + Keys.MASTER_KEY_ID + " = ");
+ qb.appendWhereEscapeString(uri.getPathSegments().get(1));
+ break;
+ }
+ case KEY_RINGS_FIND_BY_SUBKEY: {
+ try {
+ String subkey = Long.valueOf(uri.getLastPathSegment()).toString();
+ qb.appendWhere(" AND EXISTS ("
+ + " SELECT 1 FROM " + Tables.KEYS + " AS tmp"
+ + " WHERE tmp." + UserIds.MASTER_KEY_ID
+ + " = " + Tables.KEYS + "." + Keys.MASTER_KEY_ID
+ + " AND tmp." + Keys.KEY_ID + " = " + subkey + ""
+ + ")");
+ } catch(NumberFormatException e) {
+ Log.e(Constants.TAG, "Malformed find by subkey query!", e);
+ qb.appendWhere(" AND 0");
+ }
+ break;
+ }
+ case KEY_RINGS_FIND_BY_EMAIL: {
+ String chunks[] = uri.getLastPathSegment().split(" *, *");
+ boolean gotCondition = false;
+ String emailWhere = "";
+ // JAVA ♥
+ for (int i = 0; i < chunks.length; ++i) {
+ if (chunks[i].length() == 0) {
+ continue;
+ }
+ if (i != 0) {
+ emailWhere += " OR ";
+ }
+ emailWhere += "tmp." + UserIds.USER_ID + " LIKE ";
+ // match '*<email>', so it has to be at the *end* of the user id
+ emailWhere += DatabaseUtils.sqlEscapeString("%<" + chunks[i] + ">");
+ gotCondition = true;
+ }
+ if(gotCondition) {
+ qb.appendWhere(" AND EXISTS ("
+ + " SELECT 1 FROM " + Tables.USER_IDS + " AS tmp"
+ + " WHERE tmp." + UserIds.MASTER_KEY_ID
+ + " = " + Tables.KEYS + "." + Keys.MASTER_KEY_ID
+ + " AND (" + emailWhere + ")"
+ + ")");
+ } else {
+ // TODO better way to do this?
+ Log.e(Constants.TAG, "Malformed find by email query!");
+ qb.appendWhere(" AND 0");
+ }
+ break;
+ }
+ }
+
+ if (TextUtils.isEmpty(sortOrder)) {
+ sortOrder =
+ Tables.KEY_RINGS_SECRET + "." + KeyRings.MASTER_KEY_ID + " IS NULL ASC, "
+ + Tables.USER_IDS + "." + UserIds.USER_ID + " ASC";
+ }
+
+ // uri to watch is all /key_rings/
+ uri = KeyRings.CONTENT_URI;
+
+ break;
+ }
+
+ case KEY_RING_KEYS: {
+ HashMap<String, String> projectionMap = new HashMap<String, String>();
+ projectionMap.put(Keys._ID, Tables.KEYS + ".oid AS _id");
+ projectionMap.put(Keys.MASTER_KEY_ID, Tables.KEYS + "." + Keys.MASTER_KEY_ID);
+ projectionMap.put(Keys.RANK, Tables.KEYS + "." + Keys.RANK);
+ projectionMap.put(Keys.KEY_ID, Keys.KEY_ID);
+ projectionMap.put(Keys.KEY_SIZE, Keys.KEY_SIZE);
+ projectionMap.put(Keys.IS_REVOKED, Keys.IS_REVOKED);
+ projectionMap.put(Keys.CAN_CERTIFY, Keys.CAN_CERTIFY);
+ projectionMap.put(Keys.CAN_ENCRYPT, Keys.CAN_ENCRYPT);
+ projectionMap.put(Keys.CAN_SIGN, Keys.CAN_SIGN);
+ projectionMap.put(Keys.CREATION, Keys.CREATION);
+ projectionMap.put(Keys.EXPIRY, Keys.EXPIRY);
+ projectionMap.put(Keys.ALGORITHM, Keys.ALGORITHM);
+ projectionMap.put(Keys.FINGERPRINT, Keys.FINGERPRINT);
+ qb.setProjectionMap(projectionMap);
+
+ qb.setTables(Tables.KEYS);
+ qb.appendWhere(Keys.MASTER_KEY_ID + " = ");
+ qb.appendWhereEscapeString(uri.getPathSegments().get(1));
+
+ break;
+ }
+
+ case KEY_RING_USER_IDS: {
+ HashMap<String, String> projectionMap = new HashMap<String, String>();
+ projectionMap.put(UserIds._ID, Tables.USER_IDS + ".oid AS _id");
+ projectionMap.put(UserIds.MASTER_KEY_ID, Tables.USER_IDS + "." + UserIds.MASTER_KEY_ID);
+ projectionMap.put(UserIds.USER_ID, Tables.USER_IDS + "." + UserIds.USER_ID);
+ projectionMap.put(UserIds.RANK, Tables.USER_IDS + "." + UserIds.RANK);
+ projectionMap.put(UserIds.IS_PRIMARY, Tables.USER_IDS + "." + UserIds.IS_PRIMARY);
+ projectionMap.put(UserIds.IS_REVOKED, Tables.USER_IDS + "." + UserIds.IS_REVOKED);
+ // we take the minimum (>0) here, where "1" is "verified by known secret key"
+ projectionMap.put(UserIds.VERIFIED, "MIN(" + Certs.VERIFIED + ") AS " + UserIds.VERIFIED);
+ qb.setProjectionMap(projectionMap);
+
+ qb.setTables(Tables.USER_IDS
+ + " LEFT JOIN " + Tables.CERTS + " ON ("
+ + Tables.USER_IDS + "." + UserIds.MASTER_KEY_ID + " = "
+ + Tables.CERTS + "." + Certs.MASTER_KEY_ID
+ + " AND " + Tables.USER_IDS + "." + UserIds.RANK + " = "
+ + Tables.CERTS + "." + Certs.RANK
+ + " AND " + Tables.CERTS + "." + Certs.VERIFIED + " > 0"
+ + ")");
+ groupBy = Tables.USER_IDS + "." + UserIds.RANK;
+
+ qb.appendWhere(Tables.USER_IDS + "." + UserIds.MASTER_KEY_ID + " = ");
+ qb.appendWhereEscapeString(uri.getPathSegments().get(1));
+
+ if (TextUtils.isEmpty(sortOrder)) {
+ sortOrder = Tables.USER_IDS + "." + UserIds.RANK + " ASC";
+ }
+
+ break;
+
+ }
+
+ case KEY_RINGS_PUBLIC:
+ case KEY_RING_PUBLIC: {
+ HashMap<String, String> projectionMap = new HashMap<String, String>();
+ projectionMap.put(KeyRingData._ID, Tables.KEY_RINGS_PUBLIC + ".oid AS _id");
+ projectionMap.put(KeyRingData.MASTER_KEY_ID, KeyRingData.MASTER_KEY_ID);
+ projectionMap.put(KeyRingData.KEY_RING_DATA, KeyRingData.KEY_RING_DATA);
+ qb.setProjectionMap(projectionMap);
+
+ qb.setTables(Tables.KEY_RINGS_PUBLIC);
+
+ if(match == KEY_RING_PUBLIC) {
+ qb.appendWhere(KeyRings.MASTER_KEY_ID + " = ");
+ qb.appendWhereEscapeString(uri.getPathSegments().get(1));
+ }
+
+ break;
+ }
+
+ case KEY_RINGS_SECRET:
+ case KEY_RING_SECRET: {
+ HashMap<String, String> projectionMap = new HashMap<String, String>();
+ projectionMap.put(KeyRingData._ID, Tables.KEY_RINGS_SECRET + ".oid AS _id");
+ projectionMap.put(KeyRingData.MASTER_KEY_ID, KeyRingData.MASTER_KEY_ID);
+ projectionMap.put(KeyRingData.KEY_RING_DATA, KeyRingData.KEY_RING_DATA);
+ qb.setProjectionMap(projectionMap);
+
+ qb.setTables(Tables.KEY_RINGS_SECRET);
+
+ if(match == KEY_RING_SECRET) {
+ qb.appendWhere(KeyRings.MASTER_KEY_ID + " = ");
+ qb.appendWhereEscapeString(uri.getPathSegments().get(1));
+ }
+
+ break;
+ }
+
+ case KEY_RING_CERTS:
+ case KEY_RING_CERTS_SPECIFIC: {
+ HashMap<String, String> projectionMap = new HashMap<String, String>();
+ projectionMap.put(Certs._ID, Tables.CERTS + ".oid AS " + Certs._ID);
+ projectionMap.put(Certs.MASTER_KEY_ID, Tables.CERTS + "." + Certs.MASTER_KEY_ID);
+ projectionMap.put(Certs.RANK, Tables.CERTS + "." + Certs.RANK);
+ projectionMap.put(Certs.VERIFIED, Tables.CERTS + "." + Certs.VERIFIED);
+ projectionMap.put(Certs.TYPE, Tables.CERTS + "." + Certs.TYPE);
+ projectionMap.put(Certs.CREATION, Tables.CERTS + "." + Certs.CREATION);
+ projectionMap.put(Certs.KEY_ID_CERTIFIER, Tables.CERTS + "." + Certs.KEY_ID_CERTIFIER);
+ projectionMap.put(Certs.DATA, Tables.CERTS + "." + Certs.DATA);
+ projectionMap.put(Certs.USER_ID, Tables.USER_IDS + "." + UserIds.USER_ID);
+ projectionMap.put(Certs.SIGNER_UID, "signer." + UserIds.USER_ID + " AS " + Certs.SIGNER_UID);
+ qb.setProjectionMap(projectionMap);
+
+ qb.setTables(Tables.CERTS
+ + " JOIN " + Tables.USER_IDS + " ON ("
+ + Tables.CERTS + "." + Certs.MASTER_KEY_ID + " = "
+ + Tables.USER_IDS + "." + UserIds.MASTER_KEY_ID
+ + " AND "
+ + Tables.CERTS + "." + Certs.RANK + " = "
+ + Tables.USER_IDS + "." + UserIds.RANK
+ + ") LEFT JOIN " + Tables.USER_IDS + " AS signer ON ("
+ + Tables.CERTS + "." + Certs.KEY_ID_CERTIFIER + " = "
+ + "signer." + UserIds.MASTER_KEY_ID
+ + " AND "
+ + "signer." + Keys.RANK + " = 0"
+ + ")");
+
+ groupBy = Tables.CERTS + "." + Certs.RANK + ", "
+ + Tables.CERTS + "." + Certs.KEY_ID_CERTIFIER;
+
+ qb.appendWhere(Tables.CERTS + "." + Certs.MASTER_KEY_ID + " = ");
+ qb.appendWhereEscapeString(uri.getPathSegments().get(1));
+ if(match == KEY_RING_CERTS_SPECIFIC) {
+ qb.appendWhere(" AND " + Tables.CERTS + "." + Certs.RANK + " = ");
+ qb.appendWhereEscapeString(uri.getPathSegments().get(3));
+ qb.appendWhere(" AND " + Tables.CERTS + "." + Certs.KEY_ID_CERTIFIER+ " = ");
+ qb.appendWhereEscapeString(uri.getPathSegments().get(4));
+ }
+
+ break;
+ }
+
+ case API_APPS:
+ qb.setTables(Tables.API_APPS);
+
+ break;
+ case API_APPS_BY_PACKAGE_NAME:
+ qb.setTables(Tables.API_APPS);
+ qb.appendWhere(ApiApps.PACKAGE_NAME + " = ");
+ qb.appendWhereEscapeString(uri.getLastPathSegment());
+
+ break;
+ 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:
+ qb.setTables(Tables.API_ACCOUNTS);
+ qb.appendWhere(Tables.API_ACCOUNTS + "." + ApiAccounts.PACKAGE_NAME + " = ");
+ qb.appendWhereEscapeString(uri.getPathSegments().get(1));
+
+ qb.appendWhere(" AND " + Tables.API_ACCOUNTS + "." + ApiAccounts.ACCOUNT_NAME + " = ");
+ qb.appendWhereEscapeString(uri.getLastPathSegment());
+
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown URI " + uri + " (" + match + ")");
+
+ }
+
+ // If no sort order is specified use the default
+ String orderBy;
+ if (TextUtils.isEmpty(sortOrder)) {
+ orderBy = null;
+ } else {
+ orderBy = sortOrder;
+ }
+
+ SQLiteDatabase db = getDb().getReadableDatabase();
+ Cursor c = qb.query(db, projection, selection, selectionArgs, groupBy, having, orderBy);
+
+ // Tell the cursor what uri to watch, so it knows when its source data changes
+ c.setNotificationUri(getContext().getContentResolver(), uri);
+
+ if (Constants.DEBUG) {
+ Log.d(Constants.TAG,
+ "Query: "
+ + qb.buildQuery(projection, selection, selectionArgs, null, null,
+ orderBy, null));
+ Log.d(Constants.TAG, "Cursor: " + DatabaseUtils.dumpCursorToString(c));
+ }
+
+ return c;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ Log.d(Constants.TAG, "insert(uri=" + uri + ", values=" + values.toString() + ")");
+
+ final SQLiteDatabase db = getDb().getWritableDatabase();
+
+ Uri rowUri = null;
+ Long keyId = null;
+ try {
+ final int match = mUriMatcher.match(uri);
+
+ switch (match) {
+ case KEY_RING_PUBLIC:
+ db.insertOrThrow(Tables.KEY_RINGS_PUBLIC, null, values);
+ keyId = values.getAsLong(KeyRings.MASTER_KEY_ID);
+ break;
+
+ case KEY_RING_SECRET:
+ db.insertOrThrow(Tables.KEY_RINGS_SECRET, null, values);
+ keyId = values.getAsLong(KeyRings.MASTER_KEY_ID);
+ break;
+
+ case KEY_RING_KEYS:
+ Log.d(Constants.TAG, "keys");
+ db.insertOrThrow(Tables.KEYS, null, values);
+ keyId = values.getAsLong(Keys.MASTER_KEY_ID);
+ break;
+
+ case KEY_RING_USER_IDS:
+ db.insertOrThrow(Tables.USER_IDS, null, values);
+ keyId = values.getAsLong(UserIds.MASTER_KEY_ID);
+ break;
+
+ case KEY_RING_CERTS:
+ // we replace here, keeping only the latest signature
+ // TODO this would be better handled in saveKeyRing directly!
+ db.replaceOrThrow(Tables.CERTS, null, values);
+ keyId = values.getAsLong(Certs.MASTER_KEY_ID);
+ 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/
+ String packageName = uri.getPathSegments().get(1);
+ values.put(ApiAccounts.PACKAGE_NAME, packageName);
+
+ Log.d(Constants.TAG, "provider packageName: " + packageName);
+
+ db.insertOrThrow(Tables.API_ACCOUNTS, null, values);
+ // TODO: this is wrong:
+// rowUri = ApiAccounts.buildIdUri(Long.toString(rowId));
+
+ break;
+
+ default:
+ throw new UnsupportedOperationException("Unknown uri: " + uri);
+ }
+
+ if(keyId != null) {
+ uri = KeyRings.buildGenericKeyRingUri(keyId.toString());
+ rowUri = uri;
+ }
+
+ // notify of changes in db
+ getContext().getContentResolver().notifyChange(uri, null);
+
+ } catch (SQLiteConstraintException e) {
+ Log.e(Constants.TAG, "Constraint exception on insert! Entry already existing?", e);
+ }
+
+ return rowUri;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int delete(Uri uri, String additionalSelection, String[] selectionArgs) {
+ Log.v(Constants.TAG, "delete(uri=" + uri + ")");
+
+ final SQLiteDatabase db = getDb().getWritableDatabase();
+
+ int count;
+ final int match = mUriMatcher.match(uri);
+
+ switch (match) {
+ case KEY_RING_PUBLIC: {
+ @SuppressWarnings("ConstantConditions") // ensured by uriMatcher above
+ String selection = KeyRings.MASTER_KEY_ID + " = " + uri.getPathSegments().get(1);
+ if (!TextUtils.isEmpty(additionalSelection)) {
+ selection += " AND (" + additionalSelection + ")";
+ }
+ // corresponding keys and userIds are deleted by ON DELETE CASCADE
+ count = db.delete(Tables.KEY_RINGS_PUBLIC, selection, selectionArgs);
+ uri = KeyRings.buildGenericKeyRingUri(uri.getPathSegments().get(1));
+ break;
+ }
+ case KEY_RING_SECRET: {
+ @SuppressWarnings("ConstantConditions") // ensured by uriMatcher above
+ String selection = KeyRings.MASTER_KEY_ID + " = " + uri.getPathSegments().get(1);
+ if (!TextUtils.isEmpty(additionalSelection)) {
+ selection += " AND (" + additionalSelection + ")";
+ }
+ count = db.delete(Tables.KEY_RINGS_SECRET, selection, selectionArgs);
+ uri = KeyRings.buildGenericKeyRingUri(uri.getPathSegments().get(1));
+ break;
+ }
+
+ case API_APPS_BY_PACKAGE_NAME:
+ count = db.delete(Tables.API_APPS, buildDefaultApiAppsSelection(uri, additionalSelection),
+ selectionArgs);
+ break;
+ case API_ACCOUNTS_BY_ACCOUNT_NAME:
+ count = db.delete(Tables.API_ACCOUNTS, buildDefaultApiAccountsSelection(uri, additionalSelection),
+ selectionArgs);
+ break;
+ default:
+ throw new UnsupportedOperationException("Unknown uri: " + uri);
+ }
+
+ // notify of changes in db
+ getContext().getContentResolver().notifyChange(uri, null);
+
+ return count;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ Log.v(Constants.TAG, "update(uri=" + uri + ", values=" + values.toString() + ")");
+
+ final SQLiteDatabase db = mKeychainDatabase.getWritableDatabase();
+
+ String defaultSelection = null;
+ int count = 0;
+ try {
+ final int match = mUriMatcher.match(uri);
+ switch (match) {
+ case API_APPS_BY_PACKAGE_NAME:
+ count = db.update(Tables.API_APPS, values,
+ buildDefaultApiAppsSelection(uri, selection), selectionArgs);
+ break;
+ case API_ACCOUNTS_BY_ACCOUNT_NAME:
+ count = db.update(Tables.API_ACCOUNTS, values,
+ buildDefaultApiAccountsSelection(uri, selection), selectionArgs);
+ break;
+ default:
+ throw new UnsupportedOperationException("Unknown uri: " + uri);
+ }
+
+ // notify of changes in db
+ getContext().getContentResolver().notifyChange(uri, null);
+
+ } catch (SQLiteConstraintException e) {
+ Log.e(Constants.TAG, "Constraint exception on update! Entry already existing?");
+ }
+
+ return count;
+ }
+
+ /**
+ * Build default selection statement for API apps. If no extra selection is specified only build
+ * where clause with rowId
+ *
+ * @param uri
+ * @param selection
+ * @return
+ */
+ private String buildDefaultApiAppsSelection(Uri uri, String selection) {
+ String packageName = DatabaseUtils.sqlEscapeString(uri.getLastPathSegment());
+
+ String andSelection = "";
+ if (!TextUtils.isEmpty(selection)) {
+ andSelection = " AND (" + selection + ")";
+ }
+
+ return ApiApps.PACKAGE_NAME + "=" + packageName + andSelection;
+ }
+
+ private String buildDefaultApiAccountsSelection(Uri uri, String selection) {
+ String packageName = DatabaseUtils.sqlEscapeString(uri.getPathSegments().get(1));
+ String accountName = DatabaseUtils.sqlEscapeString(uri.getLastPathSegment());
+
+ String andSelection = "";
+ if (!TextUtils.isEmpty(selection)) {
+ andSelection = " AND (" + selection + ")";
+ }
+
+ return ApiAccounts.PACKAGE_NAME + "=" + packageName + " AND "
+ + ApiAccounts.ACCOUNT_NAME + "=" + accountName
+ + andSelection;
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainServiceBlobContract.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainServiceBlobContract.java
new file mode 100644
index 000000000..701ffc6af
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainServiceBlobContract.java
@@ -0,0 +1,40 @@
+/*
+ * 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.provider;
+
+import android.net.Uri;
+import android.provider.BaseColumns;
+import org.sufficientlysecure.keychain.Constants;
+
+public class KeychainServiceBlobContract {
+
+ interface BlobsColumns {
+ String KEY = "key";
+ }
+
+ public static final String CONTENT_AUTHORITY = Constants.PACKAGE_NAME + ".blobs";
+
+ private static final Uri BASE_CONTENT_URI = Uri.parse("content://" + CONTENT_AUTHORITY);
+
+ public static class Blobs implements BlobsColumns, BaseColumns {
+ public static final Uri CONTENT_URI = BASE_CONTENT_URI;
+ }
+
+ private KeychainServiceBlobContract() {
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainServiceBlobDatabase.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainServiceBlobDatabase.java
new file mode 100644
index 000000000..bc7de0b37
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainServiceBlobDatabase.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2012-2013 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2011 Markus Doits <markus.doits@googlemail.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.provider;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.provider.BaseColumns;
+import org.sufficientlysecure.keychain.provider.KeychainServiceBlobContract.BlobsColumns;
+
+public class KeychainServiceBlobDatabase extends SQLiteOpenHelper {
+ private static final String DATABASE_NAME = "openkeychain_blob.db";
+ private static final int DATABASE_VERSION = 2;
+
+ public static final String TABLE = "data";
+
+ public KeychainServiceBlobDatabase(Context context) {
+ super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ db.execSQL("CREATE TABLE " + TABLE + " ( " + BaseColumns._ID
+ + " INTEGER PRIMARY KEY AUTOINCREMENT, " + BlobsColumns.KEY + " TEXT NOT NULL)");
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ // no upgrade necessary yet
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainServiceBlobProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainServiceBlobProvider.java
new file mode 100644
index 000000000..aa30e845d
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainServiceBlobProvider.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2012-2013 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2011 Markus Doits <markus.doits@googlemail.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.provider;
+
+import android.content.ContentProvider;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import android.provider.BaseColumns;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.provider.KeychainServiceBlobContract.Blobs;
+import org.sufficientlysecure.keychain.provider.KeychainServiceBlobContract.BlobsColumns;
+import org.sufficientlysecure.keychain.util.Log;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.List;
+import java.util.UUID;
+
+public class KeychainServiceBlobProvider extends ContentProvider {
+ private static final String STORE_PATH = Constants.Path.APP_DIR + "/KeychainBlobs";
+
+ private KeychainServiceBlobDatabase mBlobDatabase = null;
+
+ public KeychainServiceBlobProvider() {
+ File dir = new File(STORE_PATH);
+ dir.mkdirs();
+ }
+
+ @Override
+ public boolean onCreate() {
+ mBlobDatabase = new KeychainServiceBlobDatabase(getContext());
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Uri insert(Uri uri, ContentValues ignored) {
+ // ContentValues are actually ignored, because we want to store a blob with no more
+ // information but have to create an record with the password generated here first
+ ContentValues vals = new ContentValues();
+
+ // Insert a random key in the database. This has to provided by the caller when updating or
+ // getting the blob
+ String password = UUID.randomUUID().toString();
+ vals.put(BlobsColumns.KEY, password);
+
+ SQLiteDatabase db = mBlobDatabase.getWritableDatabase();
+ long newRowId = db.insert(KeychainServiceBlobDatabase.TABLE, null, vals);
+ Uri insertedUri = ContentUris.withAppendedId(Blobs.CONTENT_URI, newRowId);
+
+ return Uri.withAppendedPath(insertedUri, password);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ParcelFileDescriptor openFile(Uri uri, String mode) throws SecurityException,
+ FileNotFoundException {
+ Log.d(Constants.TAG, "openFile() called with uri: " + uri.toString() + " and mode: " + mode);
+
+ List<String> segments = uri.getPathSegments();
+ if (segments.size() < 2) {
+ throw new SecurityException("Password not found in URI");
+ }
+ String id = segments.get(0);
+ String key = segments.get(1);
+
+ Log.d(Constants.TAG, "Got id: " + id + " and key: " + key);
+
+ // get the data
+ SQLiteDatabase db = mBlobDatabase.getReadableDatabase();
+ Cursor result = db.query(KeychainServiceBlobDatabase.TABLE, new String[]{BaseColumns._ID},
+ BaseColumns._ID + " = ? and " + BlobsColumns.KEY + " = ?",
+ new String[]{id, key}, null, null, null);
+
+ if (result.getCount() == 0) {
+ // either the key is wrong or no id exists
+ throw new FileNotFoundException("No file found with that ID and/or password");
+ }
+
+ File targetFile = new File(STORE_PATH, id);
+ if (mode.equals("w")) {
+ Log.d(Constants.TAG, "Try to open file w");
+ if (!targetFile.exists()) {
+ try {
+ targetFile.createNewFile();
+ } catch (IOException e) {
+ Log.e(Constants.TAG, "Got IEOException on creating new file", e);
+ throw new FileNotFoundException("Could not create file to write to");
+ }
+ }
+ return ParcelFileDescriptor.open(targetFile, ParcelFileDescriptor.MODE_WRITE_ONLY
+ | ParcelFileDescriptor.MODE_TRUNCATE);
+ } else if (mode.equals("r")) {
+ Log.d(Constants.TAG, "Try to open file r");
+ if (!targetFile.exists()) {
+ throw new FileNotFoundException("Error: Could not find the file requested");
+ }
+ return ParcelFileDescriptor.open(targetFile, ParcelFileDescriptor.MODE_READ_ONLY);
+ }
+
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getType(Uri uri) {
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ return 0;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ return 0;
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java
new file mode 100644
index 000000000..5e6d37237
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java
@@ -0,0 +1,673 @@
+/*
+ * 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.provider;
+
+import android.content.ContentProviderOperation;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.OperationApplicationException;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.net.Uri;
+import android.os.RemoteException;
+
+import org.spongycastle.bcpg.ArmoredOutputStream;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPKeyRing;
+import org.spongycastle.openpgp.PGPPublicKey;
+import org.spongycastle.openpgp.PGPPublicKeyRing;
+import org.spongycastle.openpgp.PGPSecretKeyRing;
+import org.spongycastle.openpgp.PGPSignature;
+import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.pgp.PgpConversionHelper;
+import org.sufficientlysecure.keychain.pgp.PgpHelper;
+import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
+import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
+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.UserIds;
+import org.sufficientlysecure.keychain.remote.AccountSettings;
+import org.sufficientlysecure.keychain.remote.AppSettings;
+import org.sufficientlysecure.keychain.util.IterableIterator;
+import org.sufficientlysecure.keychain.util.Log;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.security.SignatureException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class ProviderHelper {
+
+ // If we ever switch to api level 11, we can ditch this whole mess!
+ public static final int FIELD_TYPE_NULL = 1;
+ // this is called integer to stay coherent with the constants in Cursor (api level 11)
+ public static final int FIELD_TYPE_INTEGER = 2;
+ public static final int FIELD_TYPE_FLOAT = 3;
+ public static final int FIELD_TYPE_STRING = 4;
+ public static final int FIELD_TYPE_BLOB = 5;
+
+ public static Object getGenericData(Context context, Uri uri, String column, int type) {
+ return getGenericData(context, uri, new String[] { column }, new int[] { type }).get(column);
+ }
+
+ public static HashMap<String,Object> getGenericData(Context context, Uri uri, String[] proj, int[] types) {
+ Cursor cursor = context.getContentResolver().query(uri, proj, null, null, null);
+
+ HashMap<String, Object> result = new HashMap<String, Object>(proj.length);
+ if (cursor != null && cursor.moveToFirst()) {
+ int pos = 0;
+ for(String p : proj) {
+ switch(types[pos]) {
+ case FIELD_TYPE_NULL: result.put(p, cursor.isNull(pos)); break;
+ case FIELD_TYPE_INTEGER: result.put(p, cursor.getLong(pos)); break;
+ case FIELD_TYPE_FLOAT: result.put(p, cursor.getFloat(pos)); break;
+ case FIELD_TYPE_STRING: result.put(p, cursor.getString(pos)); break;
+ case FIELD_TYPE_BLOB: result.put(p, cursor.getBlob(pos)); break;
+ }
+ pos += 1;
+ }
+ }
+
+ if (cursor != null) {
+ cursor.close();
+ }
+
+ return result;
+ }
+
+ public static Object getUnifiedData(Context context, long masterKeyId, String column, int type) {
+ return getUnifiedData(context, masterKeyId, new String[] { column }, new int[] { type }).get(column);
+ }
+
+ public static HashMap<String,Object> getUnifiedData(Context context, long masterKeyId, String[] proj, int[] types) {
+ return getGenericData(context, KeyRings.buildUnifiedKeyRingUri(Long.toString(masterKeyId)), proj, types);
+ }
+
+ /**
+ * Find the master key id related to a given query. The id will either be extracted from the
+ * query, which should work for all specific /key_rings/ queries, or will be queried if it can't.
+ */
+ public static long getMasterKeyId(Context context, Uri queryUri) {
+ // try extracting from the uri first
+ String firstSegment = queryUri.getPathSegments().get(1);
+ if(!firstSegment.equals("find")) try {
+ return Long.parseLong(firstSegment);
+ } catch(NumberFormatException e) {
+ // didn't work? oh well.
+ Log.d(Constants.TAG, "Couldn't get masterKeyId from URI, querying...");
+ }
+ Object data = getGenericData(context, queryUri, KeyRings.MASTER_KEY_ID, FIELD_TYPE_INTEGER);
+ if(data != null)
+ return (Long) data;
+ // TODO better error handling?
+ return 0L;
+ }
+
+ public static Map<Long, PGPKeyRing> getPGPKeyRings(Context context, Uri queryUri) {
+ Cursor cursor = context.getContentResolver().query(queryUri,
+ new String[]{KeyRingData.MASTER_KEY_ID, KeyRingData.KEY_RING_DATA },
+ null, null, null);
+
+ Map<Long, PGPKeyRing> result = new HashMap<Long, PGPKeyRing>(cursor.getCount());
+ if (cursor != null && cursor.moveToFirst()) do {
+ long masterKeyId = cursor.getLong(0);
+ byte[] data = cursor.getBlob(1);
+ if (data != null) {
+ result.put(masterKeyId, PgpConversionHelper.BytesToPGPKeyRing(data));
+ }
+ } while(cursor.moveToNext());
+
+ if (cursor != null) {
+ cursor.close();
+ }
+
+ return result;
+ }
+ public static PGPKeyRing getPGPKeyRing(Context context, Uri queryUri) {
+ Map<Long, PGPKeyRing> result = getPGPKeyRings(context, queryUri);
+ if(result.isEmpty())
+ return null;
+ return result.values().iterator().next();
+ }
+
+ public static PGPPublicKeyRing getPGPPublicKeyRingWithKeyId(Context context, long keyId) {
+ Uri uri = KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(Long.toString(keyId));
+ long masterKeyId = getMasterKeyId(context, uri);
+ if(masterKeyId != 0)
+ return getPGPPublicKeyRing(context, masterKeyId);
+ return null;
+ }
+ public static PGPSecretKeyRing getPGPSecretKeyRingWithKeyId(Context context, long keyId) {
+ Uri uri = KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(Long.toString(keyId));
+ long masterKeyId = getMasterKeyId(context, uri);
+ if(masterKeyId != 0)
+ return getPGPSecretKeyRing(context, masterKeyId);
+ return null;
+ }
+
+ /**
+ * Retrieves the actual PGPPublicKeyRing object from the database blob based on the masterKeyId
+ */
+ public static PGPPublicKeyRing getPGPPublicKeyRing(Context context,
+ long masterKeyId) {
+ Uri queryUri = KeyRingData.buildPublicKeyRingUri(Long.toString(masterKeyId));
+ return (PGPPublicKeyRing) getPGPKeyRing(context, queryUri);
+ }
+
+ /**
+ * Retrieves the actual PGPSecretKeyRing object from the database blob based on the maserKeyId
+ */
+ public static PGPSecretKeyRing getPGPSecretKeyRing(Context context,
+ long masterKeyId) {
+ Uri queryUri = KeyRingData.buildSecretKeyRingUri(Long.toString(masterKeyId));
+ return (PGPSecretKeyRing) getPGPKeyRing(context, queryUri);
+ }
+
+ /**
+ * Saves PGPPublicKeyRing with its keys and userIds in DB
+ */
+ @SuppressWarnings("unchecked")
+ public static void saveKeyRing(Context context, PGPPublicKeyRing keyRing) throws IOException {
+ PGPPublicKey masterKey = keyRing.getPublicKey();
+ long masterKeyId = masterKey.getKeyID();
+
+ // IF there is a secret key, preserve it!
+ PGPSecretKeyRing secretRing = ProviderHelper.getPGPSecretKeyRing(context, masterKeyId);
+
+ // delete old version of this keyRing, which also deletes all keys and userIds on cascade
+ try {
+ context.getContentResolver().delete(KeyRingData.buildPublicKeyRingUri(Long.toString(masterKeyId)), null, null);
+ } catch (UnsupportedOperationException e) {
+ Log.e(Constants.TAG, "Key could not be deleted! Maybe we are creating a new one!", e);
+ }
+
+ ContentValues values = new ContentValues();
+ // use exactly the same _ID again to replace key in-place.
+ // NOTE: If we would not use the same _ID again,
+ // getting back to the ViewKeyActivity would result in Nullpointer,
+ // because the currently loaded key would be gone from the database
+ values.put(KeyRingData.MASTER_KEY_ID, masterKeyId);
+ values.put(KeyRingData.KEY_RING_DATA, keyRing.getEncoded());
+
+ // insert new version of this keyRing
+ Uri uri = KeyRingData.buildPublicKeyRingUri(Long.toString(masterKeyId));
+ Uri insertedUri = context.getContentResolver().insert(uri, values);
+
+ // save all keys and userIds included in keyRing object in database
+ ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();
+
+ int rank = 0;
+ for (PGPPublicKey key : new IterableIterator<PGPPublicKey>(keyRing.getPublicKeys())) {
+ operations.add(buildPublicKeyOperations(context, masterKeyId, key, rank));
+ ++rank;
+ }
+
+ // get a list of owned secret keys, for verification filtering
+ Map<Long, PGPKeyRing> allKeyRings = getPGPKeyRings(context, KeyRingData.buildSecretKeyRingUri());
+ // special case: available secret keys verify themselves!
+ if(secretRing != null)
+ allKeyRings.put(secretRing.getSecretKey().getKeyID(), secretRing);
+
+ // classify and order user ids. primary are moved to the front, revoked to the back,
+ // otherwise the order in the keyfile is preserved.
+ List<UserIdItem> uids = new ArrayList<UserIdItem>();
+
+ for (String userId : new IterableIterator<String>(masterKey.getUserIDs())) {
+ UserIdItem item = new UserIdItem();
+ uids.add(item);
+ item.userId = userId;
+
+ // look through signatures for this specific key
+ for (PGPSignature cert : new IterableIterator<PGPSignature>(
+ masterKey.getSignaturesForID(userId))) {
+ long certId = cert.getKeyID();
+ try {
+ // self signature
+ if(certId == masterKeyId) {
+ cert.init(new JcaPGPContentVerifierBuilderProvider().setProvider(
+ Constants.BOUNCY_CASTLE_PROVIDER_NAME), masterKey);
+ if(!cert.verifyCertification(userId, masterKey)) {
+ // not verified?! dang! TODO notify user? this is kinda serious...
+ Log.e(Constants.TAG, "Could not verify self signature for " + userId + "!");
+ continue;
+ }
+ // is this the first, or a more recent certificate?
+ if(item.selfCert == null ||
+ item.selfCert.getCreationTime().before(cert.getCreationTime())) {
+ item.selfCert = cert;
+ item.isPrimary = cert.getHashedSubPackets().isPrimaryUserID();
+ item.isRevoked =
+ cert.getSignatureType() == PGPSignature.CERTIFICATION_REVOCATION;
+ }
+ }
+ // verify signatures from known private keys
+ if(allKeyRings.containsKey(certId)) {
+ // mark them as verified
+ cert.init(new JcaPGPContentVerifierBuilderProvider().setProvider(
+ Constants.BOUNCY_CASTLE_PROVIDER_NAME),
+ allKeyRings.get(certId).getPublicKey());
+ if(cert.verifyCertification(userId, masterKey)) {
+ item.trustedCerts.add(cert);
+ }
+ }
+ } catch(SignatureException e) {
+ Log.e(Constants.TAG, "Signature verification failed! "
+ + PgpKeyHelper.convertKeyIdToHex(masterKey.getKeyID())
+ + " from "
+ + PgpKeyHelper.convertKeyIdToHex(cert.getKeyID()), e);
+ } catch(PGPException e) {
+ Log.e(Constants.TAG, "Signature verification failed! "
+ + PgpKeyHelper.convertKeyIdToHex(masterKey.getKeyID())
+ + " from "
+ + PgpKeyHelper.convertKeyIdToHex(cert.getKeyID()), e);
+ }
+ }
+ }
+
+ // primary before regular before revoked (see UserIdItem.compareTo)
+ // this is a stable sort, so the order of keys is otherwise preserved.
+ Collections.sort(uids);
+ // iterate and put into db
+ for(int userIdRank = 0; userIdRank < uids.size(); userIdRank++) {
+ UserIdItem item = uids.get(userIdRank);
+ operations.add(buildUserIdOperations(masterKeyId, item, userIdRank));
+ // no self cert is bad, but allowed by the rfc...
+ if(item.selfCert != null) {
+ operations.add(buildCertOperations(
+ masterKeyId, userIdRank, item.selfCert, Certs.VERIFIED_SELF));
+ }
+ // don't bother with trusted certs if the uid is revoked, anyways
+ if(item.isRevoked) {
+ continue;
+ }
+ for(int i = 0; i < item.trustedCerts.size(); i++) {
+ operations.add(buildCertOperations(
+ masterKeyId, userIdRank, item.trustedCerts.get(i), Certs.VERIFIED_SECRET));
+ }
+ }
+
+ try {
+ context.getContentResolver().applyBatch(KeychainContract.CONTENT_AUTHORITY, operations);
+ } catch (RemoteException e) {
+ Log.e(Constants.TAG, "applyBatch failed!", e);
+ } catch (OperationApplicationException e) {
+ Log.e(Constants.TAG, "applyBatch failed!", e);
+ }
+
+ // Save the saved keyring (if any)
+ if(secretRing != null) {
+ saveKeyRing(context, secretRing);
+ }
+
+ }
+
+ private static class UserIdItem implements Comparable<UserIdItem> {
+ String userId;
+ boolean isPrimary = false;
+ boolean isRevoked = false;
+ PGPSignature selfCert;
+ List<PGPSignature> trustedCerts = new ArrayList<PGPSignature>();
+
+ @Override
+ public int compareTo(UserIdItem o) {
+ // if one key is primary but the other isn't, the primary one always comes first
+ if(isPrimary != o.isPrimary)
+ return isPrimary ? -1 : 1;
+ // revoked keys always come last!
+ if(isRevoked != o.isRevoked)
+ return isRevoked ? 1 : -1;
+ return 0;
+ }
+ }
+
+ /**
+ * Saves a PGPSecretKeyRing in the DB. This will only work if a corresponding public keyring
+ * is already in the database!
+ */
+ @SuppressWarnings("unchecked")
+ public static void saveKeyRing(Context context, PGPSecretKeyRing keyRing) throws IOException {
+ long masterKeyId = keyRing.getPublicKey().getKeyID();
+
+ // save secret keyring
+ ContentValues values = new ContentValues();
+ values.put(KeyRingData.MASTER_KEY_ID, masterKeyId);
+ values.put(KeyRingData.KEY_RING_DATA, keyRing.getEncoded());
+ // insert new version of this keyRing
+ Uri uri = KeyRingData.buildSecretKeyRingUri(Long.toString(masterKeyId));
+ context.getContentResolver().insert(uri, values);
+
+ }
+
+ /**
+ * Saves (or updates) a pair of public and secret KeyRings in the database
+ */
+ @SuppressWarnings("unchecked")
+ public static void saveKeyRing(Context context, PGPPublicKeyRing pubRing, PGPSecretKeyRing privRing) throws IOException {
+ long masterKeyId = pubRing.getPublicKey().getKeyID();
+
+ // delete secret keyring (so it isn't unnecessarily saved by public-saveKeyRing below)
+ context.getContentResolver().delete(KeyRingData.buildSecretKeyRingUri(Long.toString(masterKeyId)), null, null);
+
+ // save public keyring
+ saveKeyRing(context, pubRing);
+ saveKeyRing(context, privRing);
+ }
+
+ /**
+ * Build ContentProviderOperation to add PGPPublicKey to database corresponding to a keyRing
+ */
+ private static ContentProviderOperation buildPublicKeyOperations(Context context,
+ long masterKeyId, PGPPublicKey key, int rank) throws IOException {
+
+ ContentValues values = new ContentValues();
+ values.put(Keys.MASTER_KEY_ID, masterKeyId);
+ values.put(Keys.RANK, rank);
+
+ values.put(Keys.KEY_ID, key.getKeyID());
+ values.put(Keys.KEY_SIZE, key.getBitStrength());
+ values.put(Keys.ALGORITHM, key.getAlgorithm());
+ values.put(Keys.FINGERPRINT, key.getFingerprint());
+
+ values.put(Keys.CAN_CERTIFY, (PgpKeyHelper.isCertificationKey(key)));
+ values.put(Keys.CAN_SIGN, (PgpKeyHelper.isSigningKey(key)));
+ values.put(Keys.CAN_ENCRYPT, PgpKeyHelper.isEncryptionKey(key));
+ values.put(Keys.IS_REVOKED, key.isRevoked());
+
+ values.put(Keys.CREATION, PgpKeyHelper.getCreationDate(key).getTime() / 1000);
+ Date expiryDate = PgpKeyHelper.getExpiryDate(key);
+ if (expiryDate != null) {
+ values.put(Keys.EXPIRY, expiryDate.getTime() / 1000);
+ }
+
+ Uri uri = Keys.buildKeysUri(Long.toString(masterKeyId));
+
+ return ContentProviderOperation.newInsert(uri).withValues(values).build();
+ }
+
+ /**
+ * Build ContentProviderOperation to add PGPPublicKey to database corresponding to a keyRing
+ */
+ private static ContentProviderOperation buildCertOperations(long masterKeyId,
+ int rank,
+ PGPSignature cert,
+ int verified)
+ throws IOException {
+ ContentValues values = new ContentValues();
+ values.put(Certs.MASTER_KEY_ID, masterKeyId);
+ values.put(Certs.RANK, rank);
+ values.put(Certs.KEY_ID_CERTIFIER, cert.getKeyID());
+ values.put(Certs.TYPE, cert.getSignatureType());
+ values.put(Certs.CREATION, cert.getCreationTime().getTime() / 1000);
+ values.put(Certs.VERIFIED, verified);
+ values.put(Certs.DATA, cert.getEncoded());
+
+ Uri uri = Certs.buildCertsUri(Long.toString(masterKeyId));
+
+ return ContentProviderOperation.newInsert(uri).withValues(values).build();
+ }
+
+ /**
+ * Build ContentProviderOperation to add PublicUserIds to database corresponding to a keyRing
+ */
+ private static ContentProviderOperation buildUserIdOperations(long masterKeyId, UserIdItem item,
+ int rank) {
+ ContentValues values = new ContentValues();
+ values.put(UserIds.MASTER_KEY_ID, masterKeyId);
+ values.put(UserIds.USER_ID, item.userId);
+ values.put(UserIds.IS_PRIMARY, item.isPrimary);
+ values.put(UserIds.IS_REVOKED, item.isRevoked);
+ values.put(UserIds.RANK, rank);
+
+ Uri uri = UserIds.buildUserIdsUri(Long.toString(masterKeyId));
+
+ return ContentProviderOperation.newInsert(uri).withValues(values).build();
+ }
+
+ public static ArrayList<String> getKeyRingsAsArmoredString(Context context, long[] masterKeyIds) {
+ ArrayList<String> output = new ArrayList<String>();
+
+ if (masterKeyIds != null && masterKeyIds.length > 0) {
+
+ Cursor cursor = getCursorWithSelectedKeyringMasterKeyIds(context, masterKeyIds);
+
+ if (cursor != null) {
+ int masterIdCol = cursor.getColumnIndex(KeyRingData.MASTER_KEY_ID);
+ int dataCol = cursor.getColumnIndex(KeyRingData.KEY_RING_DATA);
+ if (cursor.moveToFirst()) {
+ do {
+ Log.d(Constants.TAG, "masterKeyId: " + cursor.getLong(masterIdCol));
+
+ // get actual keyring data blob and write it to ByteArrayOutputStream
+ try {
+ Object keyRing = null;
+ byte[] data = cursor.getBlob(dataCol);
+ if (data != null) {
+ keyRing = PgpConversionHelper.BytesToPGPKeyRing(data);
+ }
+
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ ArmoredOutputStream aos = new ArmoredOutputStream(bos);
+ aos.setHeader("Version", PgpHelper.getFullVersion(context));
+
+ if (keyRing instanceof PGPSecretKeyRing) {
+ aos.write(((PGPSecretKeyRing) keyRing).getEncoded());
+ } else if (keyRing instanceof PGPPublicKeyRing) {
+ aos.write(((PGPPublicKeyRing) keyRing).getEncoded());
+ }
+ aos.close();
+
+ String armoredKey = bos.toString("UTF-8");
+
+ Log.d(Constants.TAG, "armoredKey:" + armoredKey);
+
+ output.add(armoredKey);
+ } catch (IOException e) {
+ Log.e(Constants.TAG, "IOException", e);
+ }
+ } while (cursor.moveToNext());
+ }
+ }
+
+ if (cursor != null) {
+ cursor.close();
+ }
+
+ } else {
+ Log.e(Constants.TAG, "No master keys given!");
+ }
+
+ if (output.size() > 0) {
+ return output;
+ } else {
+ return null;
+ }
+ }
+
+ private static Cursor getCursorWithSelectedKeyringMasterKeyIds(Context context, long[] masterKeyIds) {
+ Cursor cursor = null;
+ if (masterKeyIds != null && masterKeyIds.length > 0) {
+
+ String inMasterKeyList = KeyRingData.MASTER_KEY_ID + " IN (";
+ for (int i = 0; i < masterKeyIds.length; ++i) {
+ if (i != 0) {
+ inMasterKeyList += ", ";
+ }
+ inMasterKeyList += DatabaseUtils.sqlEscapeString("" + masterKeyIds[i]);
+ }
+ inMasterKeyList += ")";
+
+ cursor = context.getContentResolver().query(KeyRingData.buildPublicKeyRingUri(), new String[] {
+ KeyRingData._ID, KeyRingData.MASTER_KEY_ID, KeyRingData.KEY_RING_DATA
+ }, inMasterKeyList, null, null);
+ }
+
+ return cursor;
+ }
+
+ public static ArrayList<String> getRegisteredApiApps(Context context) {
+ Cursor cursor = context.getContentResolver().query(ApiApps.CONTENT_URI, null, null, null,
+ null);
+
+ ArrayList<String> packageNames = new ArrayList<String>();
+ if (cursor != null) {
+ int packageNameCol = cursor.getColumnIndex(ApiApps.PACKAGE_NAME);
+ if (cursor.moveToFirst()) {
+ do {
+ packageNames.add(cursor.getString(packageNameCol));
+ } while (cursor.moveToNext());
+ }
+ }
+
+ if (cursor != null) {
+ cursor.close();
+ }
+
+ return packageNames;
+ }
+
+ private static ContentValues contentValueForApiApps(AppSettings appSettings) {
+ ContentValues values = new ContentValues();
+ values.put(ApiApps.PACKAGE_NAME, appSettings.getPackageName());
+ values.put(ApiApps.PACKAGE_SIGNATURE, appSettings.getPackageSignature());
+ return values;
+ }
+
+ private static ContentValues contentValueForApiAccounts(AccountSettings accSettings) {
+ ContentValues values = new ContentValues();
+ values.put(KeychainContract.ApiAccounts.ACCOUNT_NAME, accSettings.getAccountName());
+ values.put(KeychainContract.ApiAccounts.KEY_ID, accSettings.getKeyId());
+ values.put(KeychainContract.ApiAccounts.COMPRESSION, accSettings.getCompression());
+ values.put(KeychainContract.ApiAccounts.ENCRYPTION_ALGORITHM, accSettings.getEncryptionAlgorithm());
+ values.put(KeychainContract.ApiAccounts.HASH_ALORITHM, accSettings.getHashAlgorithm());
+ return values;
+ }
+
+ public static void insertApiApp(Context context, AppSettings appSettings) {
+ context.getContentResolver().insert(KeychainContract.ApiApps.CONTENT_URI,
+ contentValueForApiApps(appSettings));
+ }
+
+ public static void insertApiAccount(Context context, Uri uri, AccountSettings accSettings) {
+ context.getContentResolver().insert(uri, contentValueForApiAccounts(accSettings));
+ }
+
+ public static void updateApiApp(Context context, AppSettings appSettings, Uri uri) {
+ if (context.getContentResolver().update(uri, contentValueForApiApps(appSettings), null,
+ null) <= 0) {
+ throw new RuntimeException();
+ }
+ }
+
+ public static void updateApiAccount(Context context, AccountSettings accSettings, Uri uri) {
+ if (context.getContentResolver().update(uri, contentValueForApiAccounts(accSettings), null,
+ null) <= 0) {
+ throw new RuntimeException();
+ }
+ }
+
+ /**
+ * Must be an uri pointing to an account
+ *
+ * @param context
+ * @param uri
+ * @return
+ */
+ public static AppSettings getApiAppSettings(Context context, Uri uri) {
+ AppSettings settings = null;
+
+ Cursor cur = context.getContentResolver().query(uri, null, null, null, null);
+ if (cur != null && cur.moveToFirst()) {
+ settings = new AppSettings();
+ settings.setPackageName(cur.getString(
+ cur.getColumnIndex(KeychainContract.ApiApps.PACKAGE_NAME)));
+ settings.setPackageSignature(cur.getBlob(
+ cur.getColumnIndex(KeychainContract.ApiApps.PACKAGE_SIGNATURE)));
+ }
+
+ return settings;
+ }
+
+ public static AccountSettings getApiAccountSettings(Context context, Uri accountUri) {
+ AccountSettings settings = null;
+
+ Cursor cur = context.getContentResolver().query(accountUri, null, null, null, null);
+ if (cur != null && cur.moveToFirst()) {
+ settings = new AccountSettings();
+
+ settings.setAccountName(cur.getString(
+ cur.getColumnIndex(KeychainContract.ApiAccounts.ACCOUNT_NAME)));
+ settings.setKeyId(cur.getLong(
+ cur.getColumnIndex(KeychainContract.ApiAccounts.KEY_ID)));
+ settings.setCompression(cur.getInt(
+ cur.getColumnIndexOrThrow(KeychainContract.ApiAccounts.COMPRESSION)));
+ settings.setHashAlgorithm(cur.getInt(
+ cur.getColumnIndexOrThrow(KeychainContract.ApiAccounts.HASH_ALORITHM)));
+ settings.setEncryptionAlgorithm(cur.getInt(
+ cur.getColumnIndexOrThrow(KeychainContract.ApiAccounts.ENCRYPTION_ALGORITHM)));
+ }
+
+ return settings;
+ }
+
+ public static Set<Long> getAllKeyIdsForApp(Context context, Uri uri) {
+ Set<Long> keyIds = new HashSet<Long>();
+
+ Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
+ if (cursor != null) {
+ int keyIdColumn = cursor.getColumnIndex(KeychainContract.ApiAccounts.KEY_ID);
+ while (cursor.moveToNext()) {
+ keyIds.add(cursor.getLong(keyIdColumn));
+ }
+ }
+
+ return keyIds;
+ }
+
+ public static byte[] getApiAppSignature(Context context, String packageName) {
+ Uri queryUri = ApiApps.buildByPackageNameUri(packageName);
+
+ String[] projection = new String[]{ApiApps.PACKAGE_SIGNATURE};
+
+ ContentResolver cr = context.getContentResolver();
+ Cursor cursor = cr.query(queryUri, projection, null, null, null);
+
+ byte[] signature = null;
+ if (cursor != null && cursor.moveToFirst()) {
+ int signatureCol = 0;
+
+ signature = cursor.getBlob(signatureCol);
+ }
+
+ if (cursor != null) {
+ cursor.close();
+ }
+
+ return signature;
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AccountSettings.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AccountSettings.java
new file mode 100644
index 000000000..832cbc752
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AccountSettings.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.remote;
+
+import org.spongycastle.bcpg.HashAlgorithmTags;
+import org.spongycastle.openpgp.PGPEncryptedData;
+import org.sufficientlysecure.keychain.Id;
+
+public class AccountSettings {
+ private String mAccountName;
+ private long mKeyId = Id.key.none;
+ private int mEncryptionAlgorithm;
+ private int mHashAlgorithm;
+ private int mCompression;
+
+ public AccountSettings() {
+
+ }
+
+ public AccountSettings(String accountName) {
+ super();
+ this.mAccountName = accountName;
+
+ // defaults:
+ this.mEncryptionAlgorithm = PGPEncryptedData.AES_256;
+ this.mHashAlgorithm = HashAlgorithmTags.SHA512;
+ this.mCompression = Id.choice.compression.zlib;
+ }
+
+ public String getAccountName() {
+ return mAccountName;
+ }
+
+ public void setAccountName(String mAccountName) {
+ this.mAccountName = mAccountName;
+ }
+
+ public long getKeyId() {
+ return mKeyId;
+ }
+
+ public void setKeyId(long scretKeyId) {
+ this.mKeyId = scretKeyId;
+ }
+
+ public int getEncryptionAlgorithm() {
+ return mEncryptionAlgorithm;
+ }
+
+ public void setEncryptionAlgorithm(int encryptionAlgorithm) {
+ this.mEncryptionAlgorithm = encryptionAlgorithm;
+ }
+
+ public int getHashAlgorithm() {
+ return mHashAlgorithm;
+ }
+
+ public void setHashAlgorithm(int hashAlgorithm) {
+ this.mHashAlgorithm = hashAlgorithm;
+ }
+
+ public int getCompression() {
+ return mCompression;
+ }
+
+ public void setCompression(int compression) {
+ this.mCompression = compression;
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AppSettings.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AppSettings.java
new file mode 100644
index 000000000..a3f9f84c9
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AppSettings.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.remote;
+
+public class AppSettings {
+ private String mPackageName;
+ private byte[] mPackageSignature;
+
+ public AppSettings() {
+
+ }
+
+ public AppSettings(String packageName, byte[] packageSignature) {
+ super();
+ this.mPackageName = packageName;
+ this.mPackageSignature = packageSignature;
+ }
+
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ public void setPackageName(String packageName) {
+ this.mPackageName = packageName;
+ }
+
+ public byte[] getPackageSignature() {
+ return mPackageSignature;
+ }
+
+ public void setPackageSignature(byte[] packageSignature) {
+ this.mPackageSignature = packageSignature;
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java
new file mode 100644
index 000000000..b38fea5a9
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java
@@ -0,0 +1,484 @@
+/*
+ * 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.remote;
+
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+
+import org.openintents.openpgp.IOpenPgpService;
+import org.openintents.openpgp.OpenPgpError;
+import org.openintents.openpgp.OpenPgpSignatureResult;
+import org.openintents.openpgp.util.OpenPgpApi;
+import org.spongycastle.util.Arrays;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.Id;
+import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify;
+import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyResult;
+import org.sufficientlysecure.keychain.pgp.PgpSignEncrypt;
+import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
+import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
+import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAccounts;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.remote.ui.RemoteServiceActivity;
+import org.sufficientlysecure.keychain.service.PassphraseCacheService;
+import org.sufficientlysecure.keychain.ui.ImportKeysActivity;
+import org.sufficientlysecure.keychain.util.InputData;
+import org.sufficientlysecure.keychain.util.Log;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Set;
+
+public class OpenPgpService extends RemoteService {
+
+ /**
+ * Search database for key ids based on emails.
+ *
+ * @param encryptionUserIds
+ * @return
+ */
+ private Intent getKeyIdsFromEmails(Intent data, String[] encryptionUserIds) {
+ // find key ids to given emails in database
+ ArrayList<Long> keyIds = new ArrayList<Long>();
+
+ boolean missingUserIdsCheck = false;
+ boolean duplicateUserIdsCheck = false;
+ ArrayList<String> missingUserIds = new ArrayList<String>();
+ ArrayList<String> duplicateUserIds = new ArrayList<String>();
+
+ for (String email : encryptionUserIds) {
+ Uri uri = KeyRings.buildUnifiedKeyRingsFindByEmailUri(email);
+ Cursor cur = getContentResolver().query(uri, null, null, null, null);
+ if (cur.moveToFirst()) {
+ long id = cur.getLong(cur.getColumnIndex(KeyRings.MASTER_KEY_ID));
+ keyIds.add(id);
+ } else {
+ missingUserIdsCheck = true;
+ missingUserIds.add(email);
+ Log.d(Constants.TAG, "user id missing");
+ }
+ if (cur.moveToNext()) {
+ duplicateUserIdsCheck = true;
+ duplicateUserIds.add(email);
+ Log.d(Constants.TAG, "more than one user id with the same email");
+ }
+ }
+
+ // convert to long[]
+ long[] keyIdsArray = new long[keyIds.size()];
+ for (int i = 0; i < keyIdsArray.length; i++) {
+ keyIdsArray[i] = keyIds.get(i);
+ }
+
+ // allow the user to verify pub key selection
+ if (missingUserIdsCheck || duplicateUserIdsCheck) {
+ // build PendingIntent
+ Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class);
+ intent.setAction(RemoteServiceActivity.ACTION_SELECT_PUB_KEYS);
+ intent.putExtra(RemoteServiceActivity.EXTRA_SELECTED_MASTER_KEY_IDS, keyIdsArray);
+ intent.putExtra(RemoteServiceActivity.EXTRA_MISSING_USER_IDS, missingUserIds);
+ intent.putExtra(RemoteServiceActivity.EXTRA_DUBLICATE_USER_IDS, duplicateUserIds);
+ intent.putExtra(RemoteServiceActivity.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_INTENT, pi);
+ result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
+ return result;
+ }
+
+ if (keyIdsArray.length == 0) {
+ return null;
+ }
+
+ Intent result = new Intent();
+ result.putExtra(OpenPgpApi.EXTRA_KEY_IDS, keyIdsArray);
+ result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
+ return result;
+ }
+
+ private Intent getPassphraseBundleIntent(Intent data, long keyId) {
+ // build PendingIntent for passphrase input
+ Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class);
+ intent.setAction(RemoteServiceActivity.ACTION_CACHE_PASSPHRASE);
+ intent.putExtra(RemoteServiceActivity.EXTRA_SECRET_KEY_ID, keyId);
+ // pass params through to activity that it can be returned again later to repeat pgp operation
+ intent.putExtra(RemoteServiceActivity.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_INTENT, pi);
+ result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
+ return result;
+ }
+
+ private Intent signImpl(Intent data, ParcelFileDescriptor input,
+ ParcelFileDescriptor output, AccountSettings accSettings) {
+ try {
+ boolean asciiArmor = data.getBooleanExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
+
+ // get passphrase from cache, if key has "no" passphrase, this returns an empty String
+ String passphrase;
+ if (data.hasExtra(OpenPgpApi.EXTRA_PASSPHRASE)) {
+ passphrase = data.getStringExtra(OpenPgpApi.EXTRA_PASSPHRASE);
+ } else {
+ passphrase = PassphraseCacheService.getCachedPassphrase(getContext(), accSettings.getKeyId());
+ }
+ if (passphrase == null) {
+ // get PendingIntent for passphrase input, add it to given params and return to client
+ Intent passphraseBundle = getPassphraseBundleIntent(data, accSettings.getKeyId());
+ return passphraseBundle;
+ }
+
+ // Get Input- and OutputStream from ParcelFileDescriptor
+ InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(input);
+ OutputStream os = new ParcelFileDescriptor.AutoCloseOutputStream(output);
+ try {
+ long inputLength = is.available();
+ InputData inputData = new InputData(is, inputLength);
+
+ // sign-only
+ PgpSignEncrypt.Builder builder = new PgpSignEncrypt.Builder(getContext(), inputData, os);
+ builder.enableAsciiArmorOutput(asciiArmor)
+ .signatureHashAlgorithm(accSettings.getHashAlgorithm())
+ .signatureForceV3(false)
+ .signatureKeyId(accSettings.getKeyId())
+ .signaturePassphrase(passphrase);
+ builder.build().execute();
+ } finally {
+ is.close();
+ os.close();
+ }
+
+ Intent result = new Intent();
+ result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
+ return result;
+ } catch (Exception e) {
+ Intent result = new Intent();
+ result.putExtra(OpenPgpApi.RESULT_ERROR,
+ new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage()));
+ result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
+ return result;
+ }
+ }
+
+ private Intent encryptAndSignImpl(Intent data, ParcelFileDescriptor input,
+ ParcelFileDescriptor output, AccountSettings accSettings,
+ boolean sign) {
+ try {
+ boolean asciiArmor = data.getBooleanExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
+
+ long[] keyIds;
+ if (data.hasExtra(OpenPgpApi.EXTRA_KEY_IDS)) {
+ keyIds = data.getLongArrayExtra(OpenPgpApi.EXTRA_KEY_IDS);
+ } else if (data.hasExtra(OpenPgpApi.EXTRA_USER_IDS)) {
+ // get key ids based on given user ids
+ String[] userIds = data.getStringArrayExtra(OpenPgpApi.EXTRA_USER_IDS);
+ // give params through to activity...
+ Intent result = getKeyIdsFromEmails(data, userIds);
+
+ if (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0) == OpenPgpApi.RESULT_CODE_SUCCESS) {
+ keyIds = result.getLongArrayExtra(OpenPgpApi.EXTRA_KEY_IDS);
+ } else {
+ // if not success -> result contains a PendingIntent for user interaction
+ return result;
+ }
+ } else {
+ Intent result = new Intent();
+ result.putExtra(OpenPgpApi.RESULT_ERROR,
+ new OpenPgpError(OpenPgpError.GENERIC_ERROR,
+ "Missing parameter user_ids or key_ids!"));
+ result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
+ return result;
+ }
+
+ // add own key for encryption
+ keyIds = Arrays.copyOf(keyIds, keyIds.length + 1);
+ keyIds[keyIds.length - 1] = accSettings.getKeyId();
+
+ // build InputData and write into OutputStream
+ // Get Input- and OutputStream from ParcelFileDescriptor
+ InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(input);
+ OutputStream os = new ParcelFileDescriptor.AutoCloseOutputStream(output);
+ try {
+ long inputLength = is.available();
+ InputData inputData = new InputData(is, inputLength);
+
+ PgpSignEncrypt.Builder builder = new PgpSignEncrypt.Builder(getContext(), inputData, os);
+ builder.enableAsciiArmorOutput(asciiArmor)
+ .compressionId(accSettings.getCompression())
+ .symmetricEncryptionAlgorithm(accSettings.getEncryptionAlgorithm())
+ .encryptionKeyIds(keyIds);
+
+ if (sign) {
+ String passphrase;
+ if (data.hasExtra(OpenPgpApi.EXTRA_PASSPHRASE)) {
+ passphrase = data.getStringExtra(OpenPgpApi.EXTRA_PASSPHRASE);
+ } else {
+ passphrase = PassphraseCacheService.getCachedPassphrase(getContext(),
+ accSettings.getKeyId());
+ }
+ if (passphrase == null) {
+ // get PendingIntent for passphrase input, add it to given params and return to client
+ Intent passphraseBundle = getPassphraseBundleIntent(data, accSettings.getKeyId());
+ return passphraseBundle;
+ }
+
+ // sign and encrypt
+ builder.signatureHashAlgorithm(accSettings.getHashAlgorithm())
+ .signatureForceV3(false)
+ .signatureKeyId(accSettings.getKeyId())
+ .signaturePassphrase(passphrase);
+ } else {
+ // encrypt only
+ builder.signatureKeyId(Id.key.none);
+ }
+ // execute PGP operation!
+ builder.build().execute();
+ } finally {
+ is.close();
+ os.close();
+ }
+
+ Intent result = new Intent();
+ result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
+ return result;
+ } catch (Exception e) {
+ Intent result = new Intent();
+ result.putExtra(OpenPgpApi.RESULT_ERROR,
+ new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage()));
+ result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
+ return result;
+ }
+ }
+
+ private Intent decryptAndVerifyImpl(Intent data, ParcelFileDescriptor input,
+ ParcelFileDescriptor output, Set<Long> allowedKeyIds) {
+ try {
+ // Get Input- and OutputStream from ParcelFileDescriptor
+ InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(input);
+ OutputStream os = new ParcelFileDescriptor.AutoCloseOutputStream(output);
+
+ Intent result = new Intent();
+ try {
+
+ String passphrase = data.getStringExtra(OpenPgpApi.EXTRA_PASSPHRASE);
+ long inputLength = is.available();
+ InputData inputData = new InputData(is, inputLength);
+
+ PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder(this, inputData, os);
+ builder.allowSymmetricDecryption(false) // no support for symmetric encryption
+ .allowedKeyIds(allowedKeyIds) // allow only private keys associated with
+ // accounts of this app
+ .passphrase(passphrase);
+
+ // TODO: currently does not support binary signed-only content
+ PgpDecryptVerifyResult decryptVerifyResult = builder.build().execute();
+
+ if (PgpDecryptVerifyResult.KEY_PASSHRASE_NEEDED == decryptVerifyResult.getStatus()) {
+ // get PendingIntent for passphrase input, add it to given params and return to client
+ Intent passphraseBundle =
+ getPassphraseBundleIntent(data, decryptVerifyResult.getKeyIdPassphraseNeeded());
+ return passphraseBundle;
+ } else if (PgpDecryptVerifyResult.SYMMETRIC_PASSHRASE_NEEDED ==
+ decryptVerifyResult.getStatus()) {
+ throw new PgpGeneralException("Decryption of symmetric content not supported by API!");
+ }
+
+ OpenPgpSignatureResult signatureResult = decryptVerifyResult.getSignatureResult();
+ if (signatureResult != null) {
+ if (signatureResult.getStatus() == OpenPgpSignatureResult.SIGNATURE_UNKNOWN_PUB_KEY) {
+ // If signature is unknown we return an _additional_ PendingIntent
+ // to retrieve the missing key
+ Intent intent = new Intent(getBaseContext(), ImportKeysActivity.class);
+ intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN);
+ intent.putExtra(ImportKeysActivity.EXTRA_KEY_ID, signatureResult.getKeyId());
+ intent.putExtra(ImportKeysActivity.EXTRA_PENDING_INTENT_DATA, data);
+
+ PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0,
+ intent,
+ PendingIntent.FLAG_CANCEL_CURRENT);
+
+ result.putExtra(OpenPgpApi.RESULT_INTENT, pi);
+ }
+
+ result.putExtra(OpenPgpApi.RESULT_SIGNATURE, signatureResult);
+ }
+
+ } finally {
+ is.close();
+ os.close();
+ }
+
+ result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
+ return result;
+ } catch (Exception e) {
+ Intent result = new Intent();
+ result.putExtra(OpenPgpApi.RESULT_ERROR,
+ new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage()));
+ result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
+ return result;
+ }
+ }
+
+ private Intent getKeyImpl(Intent data) {
+ try {
+ long keyId = data.getLongExtra(OpenPgpApi.EXTRA_KEY_ID, 0);
+
+ if (ProviderHelper.getPGPPublicKeyRing(this, keyId) == null) {
+ Intent result = new Intent();
+
+ // If keys are not in db we return an additional PendingIntent
+ // to retrieve the missing key
+ Intent intent = new Intent(getBaseContext(), ImportKeysActivity.class);
+ intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN);
+ intent.putExtra(ImportKeysActivity.EXTRA_KEY_ID, keyId);
+ intent.putExtra(ImportKeysActivity.EXTRA_PENDING_INTENT_DATA, data);
+
+ PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0,
+ intent,
+ PendingIntent.FLAG_CANCEL_CURRENT);
+
+ result.putExtra(OpenPgpApi.RESULT_INTENT, pi);
+ result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
+ return result;
+ } else {
+ Intent result = new Intent();
+ result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
+
+ // TODO: also return PendingIntent that opens the key view activity
+
+ return result;
+ }
+ } catch (Exception e) {
+ Intent result = new Intent();
+ result.putExtra(OpenPgpApi.RESULT_ERROR,
+ new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage()));
+ result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
+ return result;
+ }
+ }
+
+ private Intent getKeyIdsImpl(Intent data) {
+ // get key ids based on given user ids
+ String[] userIds = data.getStringArrayExtra(OpenPgpApi.EXTRA_USER_IDS);
+ Intent result = getKeyIdsFromEmails(data, userIds);
+ return result;
+ }
+
+ /**
+ * Check requirements:
+ * - params != null
+ * - has supported API version
+ * - is allowed to call the service (access has been granted)
+ *
+ * @param data
+ * @return null if everything is okay, or a Bundle with an error/PendingIntent
+ */
+ private Intent checkRequirements(Intent data) {
+ // params Bundle is required!
+ if (data == null) {
+ Intent result = new Intent();
+ OpenPgpError error = new OpenPgpError(OpenPgpError.GENERIC_ERROR, "params Bundle required!");
+ result.putExtra(OpenPgpApi.RESULT_ERROR, error);
+ result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
+ return result;
+ }
+
+ // version code is required and needs to correspond to version code of service!
+ if (data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) != OpenPgpApi.API_VERSION) {
+ Intent result = new Intent();
+ OpenPgpError error = new OpenPgpError
+ (OpenPgpError.INCOMPATIBLE_API_VERSIONS, "Incompatible API versions!");
+ result.putExtra(OpenPgpApi.RESULT_ERROR, error);
+ result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
+ return result;
+ }
+
+ // check if caller is allowed to access openpgp keychain
+ Intent result = isAllowed(data);
+ if (result != null) {
+ return result;
+ }
+
+ return null;
+ }
+
+ // TODO: multi-threading
+ private final IOpenPgpService.Stub mBinder = new IOpenPgpService.Stub() {
+
+ @Override
+ public Intent execute(Intent data, ParcelFileDescriptor input, ParcelFileDescriptor output) {
+ Intent errorResult = checkRequirements(data);
+ if (errorResult != null) {
+ return errorResult;
+ }
+
+ String accName;
+ if (data.getStringExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME) != null) {
+ accName = data.getStringExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME);
+ } else {
+ accName = "default";
+ }
+ final AccountSettings accSettings = getAccSettings(accName);
+ if (accSettings == null) {
+ return getCreateAccountIntent(data, accName);
+ }
+
+ String action = data.getAction();
+ if (OpenPgpApi.ACTION_SIGN.equals(action)) {
+ return signImpl(data, input, output, accSettings);
+ } else if (OpenPgpApi.ACTION_ENCRYPT.equals(action)) {
+ return encryptAndSignImpl(data, input, output, accSettings, false);
+ } else if (OpenPgpApi.ACTION_SIGN_AND_ENCRYPT.equals(action)) {
+ return encryptAndSignImpl(data, input, output, accSettings, true);
+ } else if (OpenPgpApi.ACTION_DECRYPT_VERIFY.equals(action)) {
+ String currentPkg = getCurrentCallingPackage();
+ Set<Long> allowedKeyIds =
+ ProviderHelper.getAllKeyIdsForApp(mContext,
+ ApiAccounts.buildBaseUri(currentPkg));
+ return decryptAndVerifyImpl(data, input, output, allowedKeyIds);
+ } else if (OpenPgpApi.ACTION_GET_KEY.equals(action)) {
+ return getKeyImpl(data);
+ } else if (OpenPgpApi.ACTION_GET_KEY_IDS.equals(action)) {
+ return getKeyIdsImpl(data);
+ } else {
+ return null;
+ }
+ }
+
+ };
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mBinder;
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/RemoteService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/RemoteService.java
new file mode 100644
index 000000000..16a800022
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/RemoteService.java
@@ -0,0 +1,261 @@
+/*
+ * 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.remote;
+
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.Signature;
+import android.net.Uri;
+import android.os.Binder;
+
+import org.openintents.openpgp.OpenPgpError;
+import org.openintents.openpgp.util.OpenPgpApi;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.provider.KeychainContract;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.remote.ui.RemoteServiceActivity;
+import org.sufficientlysecure.keychain.util.Log;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * Abstract service class for remote APIs that handle app registration and user input.
+ */
+public abstract class RemoteService extends Service {
+ Context mContext;
+
+ public Context getContext() {
+ return mContext;
+ }
+
+ protected Intent isAllowed(Intent data) {
+ try {
+ if (isCallerAllowed(false)) {
+ return null;
+ } else {
+ String packageName = getCurrentCallingPackage();
+ Log.d(Constants.TAG, "isAllowed packageName: " + packageName);
+
+ byte[] packageSignature;
+ try {
+ packageSignature = getPackageSignature(packageName);
+ } catch (NameNotFoundException e) {
+ Log.e(Constants.TAG, "Should not happen, returning!", e);
+ // return error
+ Intent result = new Intent();
+ result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
+ result.putExtra(OpenPgpApi.RESULT_ERROR,
+ new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage()));
+ return result;
+ }
+ Log.e(Constants.TAG, "Not allowed to use service! return PendingIntent for registration!");
+
+ Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class);
+ intent.setAction(RemoteServiceActivity.ACTION_REGISTER);
+ intent.putExtra(RemoteServiceActivity.EXTRA_PACKAGE_NAME, packageName);
+ intent.putExtra(RemoteServiceActivity.EXTRA_PACKAGE_SIGNATURE, packageSignature);
+ intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data);
+
+ PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0,
+ intent,
+ PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
+
+ // 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;
+ }
+ } catch (WrongPackageSignatureException e) {
+ Log.e(Constants.TAG, "wrong signature!", e);
+
+ Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class);
+ intent.setAction(RemoteServiceActivity.ACTION_ERROR_MESSAGE);
+ intent.putExtra(RemoteServiceActivity.EXTRA_ERROR_MESSAGE,
+ getString(R.string.api_error_wrong_signature));
+ intent.putExtra(RemoteServiceActivity.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 result;
+ }
+ }
+
+ private byte[] getPackageSignature(String packageName) throws NameNotFoundException {
+ PackageInfo pkgInfo = getPackageManager().getPackageInfo(packageName,
+ PackageManager.GET_SIGNATURES);
+ Signature[] signatures = pkgInfo.signatures;
+ // TODO: Only first signature?!
+ byte[] packageSignature = signatures[0].toByteArray();
+
+ return packageSignature;
+ }
+
+ /**
+ * Returns package name associated with the UID, which is assigned to the process that sent you the
+ * current transaction that is being processed :)
+ *
+ * @return package name
+ */
+ protected String getCurrentCallingPackage() {
+ // TODO:
+ // callingPackages contains more than one entry when sharedUserId has been used...
+ String[] callingPackages = getPackageManager().getPackagesForUid(Binder.getCallingUid());
+ String currentPkg = callingPackages[0];
+ Log.d(Constants.TAG, "currentPkg: " + currentPkg);
+
+ return currentPkg;
+ }
+
+ /**
+ * Retrieves AccountSettings from database for the application calling this remote service
+ *
+ * @return
+ */
+ protected AccountSettings getAccSettings(String accountName) {
+ String currentPkg = getCurrentCallingPackage();
+ Log.d(Constants.TAG, "accountName: " + accountName);
+
+ Uri uri = KeychainContract.ApiAccounts.buildByPackageAndAccountUri(currentPkg, accountName);
+
+ AccountSettings settings = ProviderHelper.getApiAccountSettings(this, uri);
+
+ return settings; // can be null!
+ }
+
+ protected Intent getCreateAccountIntent(Intent data, String accountName) {
+ String packageName = getCurrentCallingPackage();
+ Log.d(Constants.TAG, "accountName: " + accountName);
+
+ Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class);
+ intent.setAction(RemoteServiceActivity.ACTION_CREATE_ACCOUNT);
+ intent.putExtra(RemoteServiceActivity.EXTRA_PACKAGE_NAME, packageName);
+ intent.putExtra(RemoteServiceActivity.EXTRA_ACC_NAME, accountName);
+ intent.putExtra(RemoteServiceActivity.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 result;
+ }
+
+ /**
+ * 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 WrongPackageSignatureException
+ */
+ private boolean isCallerAllowed(boolean allowOnlySelf) throws WrongPackageSignatureException {
+ return isUidAllowed(Binder.getCallingUid(), allowOnlySelf);
+ }
+
+ private boolean isUidAllowed(int uid, boolean allowOnlySelf)
+ throws WrongPackageSignatureException {
+ if (android.os.Process.myUid() == uid) {
+ return true;
+ }
+ if (allowOnlySelf) { // barrier
+ return false;
+ }
+
+ String[] callingPackages = getPackageManager().getPackagesForUid(uid);
+
+ // is calling package allowed to use this service?
+ for (int i = 0; i < callingPackages.length; i++) {
+ String currentPkg = callingPackages[i];
+
+ if (isPackageAllowed(currentPkg)) {
+ return true;
+ }
+ }
+
+ Log.d(Constants.TAG, "Uid is NOT allowed!");
+ return false;
+ }
+
+ /**
+ * Checks if packageName is a registered app for the API. Does not return true for own package!
+ *
+ * @param packageName
+ * @return
+ * @throws WrongPackageSignatureException
+ */
+ private boolean isPackageAllowed(String packageName) throws WrongPackageSignatureException {
+ Log.d(Constants.TAG, "isPackageAllowed packageName: " + packageName);
+
+ ArrayList<String> allowedPkgs = ProviderHelper.getRegisteredApiApps(this);
+ Log.d(Constants.TAG, "allowed: " + allowedPkgs);
+
+ // check if package is allowed to use our service
+ if (allowedPkgs.contains(packageName)) {
+ Log.d(Constants.TAG, "Package is allowed! packageName: " + packageName);
+
+ // check package signature
+ byte[] currentSig;
+ try {
+ currentSig = getPackageSignature(packageName);
+ } catch (NameNotFoundException e) {
+ throw new WrongPackageSignatureException(e.getMessage());
+ }
+
+ byte[] storedSig = ProviderHelper.getApiAppSignature(this, packageName);
+ if (Arrays.equals(currentSig, storedSig)) {
+ Log.d(Constants.TAG,
+ "Package signature is correct! (equals signature from database)");
+ return true;
+ } else {
+ throw new WrongPackageSignatureException(
+ "PACKAGE NOT ALLOWED! Signature wrong! (Signature not " +
+ "equals signature from database)");
+ }
+ }
+
+ Log.d(Constants.TAG, "Package is NOT allowed! packageName: " + packageName);
+ return false;
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mContext = this;
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/WrongPackageSignatureException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/WrongPackageSignatureException.java
new file mode 100644
index 000000000..6f44a65e9
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/WrongPackageSignatureException.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.remote;
+
+public class WrongPackageSignatureException extends Exception {
+
+ private static final long serialVersionUID = -8294642703122196028L;
+
+ public WrongPackageSignatureException(String message) {
+ super(message);
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsActivity.java
new file mode 100644
index 000000000..123ed526f
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsActivity.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.remote.ui;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v7.app.ActionBarActivity;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.helper.ActionBarHelper;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.remote.AccountSettings;
+import org.sufficientlysecure.keychain.util.Log;
+
+public class AccountSettingsActivity extends ActionBarActivity {
+ private Uri mAccountUri;
+
+ private AccountSettingsFragment mAccountSettingsFragment;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Inflate a "Done" custom action bar
+ ActionBarHelper.setOneButtonView(getSupportActionBar(),
+ R.string.api_settings_save, R.drawable.ic_action_done,
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // "Done"
+ save();
+ }
+ });
+
+ setContentView(R.layout.api_account_settings_activity);
+
+ mAccountSettingsFragment = (AccountSettingsFragment) getSupportFragmentManager().findFragmentById(
+ R.id.api_account_settings_fragment);
+
+ Intent intent = getIntent();
+ mAccountUri = intent.getData();
+ if (mAccountUri == null) {
+ Log.e(Constants.TAG, "Intent data missing. Should be Uri of app!");
+ finish();
+ return;
+ } else {
+ Log.d(Constants.TAG, "uri: " + mAccountUri);
+ loadData(mAccountUri);
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ getMenuInflater().inflate(R.menu.api_account_settings, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.menu_account_settings_delete:
+ deleteAccount();
+ return true;
+ case R.id.menu_account_settings_cancel:
+ finish();
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ private void loadData(Uri accountUri) {
+ AccountSettings settings = ProviderHelper.getApiAccountSettings(this, accountUri);
+ mAccountSettingsFragment.setAccSettings(settings);
+ }
+
+ private void deleteAccount() {
+ if (getContentResolver().delete(mAccountUri, null, null) <= 0) {
+ throw new RuntimeException();
+ }
+ finish();
+ }
+
+ private void save() {
+ ProviderHelper.updateApiAccount(this, mAccountSettingsFragment.getAccSettings(), mAccountUri);
+ finish();
+ }
+
+}
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
new file mode 100644
index 000000000..0a3ec3c3b
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsFragment.java
@@ -0,0 +1,201 @@
+/*
+ * 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.remote.ui;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.net.Uri;
+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.AdapterView;
+import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+import com.beardedhen.androidbootstrap.BootstrapButton;
+
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.provider.KeychainContract;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.remote.AccountSettings;
+import org.sufficientlysecure.keychain.ui.EditKeyActivity;
+import org.sufficientlysecure.keychain.ui.SelectSecretKeyLayoutFragment;
+import org.sufficientlysecure.keychain.ui.adapter.KeyValueSpinnerAdapter;
+import org.sufficientlysecure.keychain.util.AlgorithmNames;
+
+public class AccountSettingsFragment extends Fragment implements
+ SelectSecretKeyLayoutFragment.SelectSecretKeyCallback {
+
+ private static final int REQUEST_CODE_CREATE_KEY = 0x00008884;
+
+ // model
+ private AccountSettings mAccSettings;
+
+ // view
+ private TextView mAccNameView;
+ private Spinner mEncryptionAlgorithm;
+ private Spinner mHashAlgorithm;
+ private Spinner mCompression;
+
+ private SelectSecretKeyLayoutFragment mSelectKeyFragment;
+ private BootstrapButton mCreateKeyButton;
+
+ KeyValueSpinnerAdapter mEncryptionAdapter;
+ KeyValueSpinnerAdapter mHashAdapter;
+ KeyValueSpinnerAdapter mCompressionAdapter;
+
+ public AccountSettings getAccSettings() {
+ return mAccSettings;
+ }
+
+ public void setAccSettings(AccountSettings accountSettings) {
+ this.mAccSettings = accountSettings;
+
+ mAccNameView.setText(accountSettings.getAccountName());
+ mSelectKeyFragment.selectKey(accountSettings.getKeyId());
+ mEncryptionAlgorithm.setSelection(mEncryptionAdapter.getPosition(accountSettings
+ .getEncryptionAlgorithm()));
+ mHashAlgorithm.setSelection(mHashAdapter.getPosition(accountSettings.getHashAlgorithm()));
+ mCompression.setSelection(mCompressionAdapter.getPosition(accountSettings.getCompression()));
+ }
+
+ /**
+ * Inflate the layout for this fragment
+ */
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.api_account_settings_fragment, container, false);
+ initView(view);
+ return view;
+ }
+
+ /**
+ * Set error String on key selection
+ *
+ * @param error
+ */
+ public void setErrorOnSelectKeyFragment(String error) {
+ mSelectKeyFragment.setError(error);
+ }
+
+ private void initView(View view) {
+ mSelectKeyFragment = (SelectSecretKeyLayoutFragment) getFragmentManager().findFragmentById(
+ R.id.api_account_settings_select_key_fragment);
+ mSelectKeyFragment.setCallback(this);
+
+ mAccNameView = (TextView) view.findViewById(R.id.api_account_settings_acc_name);
+ mEncryptionAlgorithm = (Spinner) view
+ .findViewById(R.id.api_account_settings_encryption_algorithm);
+ mHashAlgorithm = (Spinner) view.findViewById(R.id.api_account_settings_hash_algorithm);
+ mCompression = (Spinner) view.findViewById(R.id.api_account_settings_compression);
+ mCreateKeyButton = (BootstrapButton) view.findViewById(R.id.api_account_settings_create_key);
+
+ mCreateKeyButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ createKey();
+ }
+ });
+
+ AlgorithmNames algorithmNames = new AlgorithmNames(getActivity());
+
+ mEncryptionAdapter = new KeyValueSpinnerAdapter(getActivity(),
+ algorithmNames.getEncryptionNames());
+ mEncryptionAlgorithm.setAdapter(mEncryptionAdapter);
+ mEncryptionAlgorithm.setOnItemSelectedListener(new OnItemSelectedListener() {
+
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ mAccSettings.setEncryptionAlgorithm((int) id);
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+ }
+ });
+
+ mHashAdapter = new KeyValueSpinnerAdapter(getActivity(), algorithmNames.getHashNames());
+ mHashAlgorithm.setAdapter(mHashAdapter);
+ mHashAlgorithm.setOnItemSelectedListener(new OnItemSelectedListener() {
+
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ mAccSettings.setHashAlgorithm((int) id);
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+ }
+ });
+
+ mCompressionAdapter = new KeyValueSpinnerAdapter(getActivity(),
+ algorithmNames.getCompressionNames());
+ mCompression.setAdapter(mCompressionAdapter);
+ mCompression.setOnItemSelectedListener(new OnItemSelectedListener() {
+
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ mAccSettings.setCompression((int) id);
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+ }
+ });
+ }
+
+ private void createKey() {
+ Intent intent = new Intent(getActivity(), EditKeyActivity.class);
+ intent.setAction(EditKeyActivity.ACTION_CREATE_KEY);
+ intent.putExtra(EditKeyActivity.EXTRA_GENERATE_DEFAULT_KEYS, true);
+ // set default user id to account name
+ intent.putExtra(EditKeyActivity.EXTRA_USER_IDS, mAccSettings.getAccountName());
+ startActivityForResult(intent, REQUEST_CODE_CREATE_KEY);
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ switch (requestCode) {
+ case REQUEST_CODE_CREATE_KEY: {
+ if (resultCode == Activity.RESULT_OK) {
+ // select newly created key
+ long masterKeyId = ProviderHelper.getMasterKeyId(getActivity(), data.getData());
+ mSelectKeyFragment.selectKey(masterKeyId);
+ }
+ break;
+ }
+
+ default:
+ super.onActivityResult(requestCode, resultCode, data);
+
+ break;
+ }
+ }
+
+ /**
+ * callback from select secret key fragment
+ */
+ @Override
+ public void onKeySelected(long secretKeyId) {
+ mAccSettings.setKeyId(secretKeyId);
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountsListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountsListFragment.java
new file mode 100644
index 000000000..4d99e1923
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountsListFragment.java
@@ -0,0 +1,198 @@
+/*
+ * 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.remote.ui;
+
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v4.app.ListFragment;
+import android.support.v4.app.LoaderManager;
+import android.support.v4.content.CursorLoader;
+import android.support.v4.content.Loader;
+import android.support.v4.widget.CursorAdapter;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.provider.KeychainContract;
+import org.sufficientlysecure.keychain.ui.widget.FixedListView;
+import org.sufficientlysecure.keychain.util.Log;
+
+public class AccountsListFragment extends ListFragment implements
+ LoaderManager.LoaderCallbacks<Cursor> {
+
+ private static final String ARG_DATA_URI = "uri";
+
+ // This is the Adapter being used to display the list's data.
+ AccountsAdapter mAdapter;
+
+ private Uri mDataUri;
+
+ /**
+ * Creates new instance of this fragment
+ */
+ public static AccountsListFragment newInstance(Uri dataUri) {
+ AccountsListFragment frag = new AccountsListFragment();
+
+ Bundle args = new Bundle();
+ args.putParcelable(ARG_DATA_URI, dataUri);
+
+ frag.setArguments(args);
+
+ return frag;
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View layout = super.onCreateView(inflater, container,
+ savedInstanceState);
+ ListView lv = (ListView) layout.findViewById(android.R.id.list);
+ ViewGroup parent = (ViewGroup) lv.getParent();
+
+ /*
+ * http://stackoverflow.com/a/15880684
+ * Remove ListView and add FixedListView in its place.
+ * This is done here programatically to be still able to use the progressBar of ListFragment.
+ *
+ * We want FixedListView to be able to put this ListFragment inside a ScrollView
+ */
+ int lvIndex = parent.indexOfChild(lv);
+ parent.removeViewAt(lvIndex);
+ FixedListView newLv = new FixedListView(getActivity());
+ newLv.setId(android.R.id.list);
+ parent.addView(newLv, lvIndex, lv.getLayoutParams());
+ return layout;
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ mDataUri = getArguments().getParcelable(ARG_DATA_URI);
+
+ getListView().setOnItemClickListener(new OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
+ String selectedAccountName = mAdapter.getItemAccountName(position);
+ Uri accountUri = mDataUri.buildUpon().appendEncodedPath(selectedAccountName).build();
+ Log.d(Constants.TAG, "accountUri: " + accountUri);
+
+ // edit account settings
+ Intent intent = new Intent(getActivity(), AccountSettingsActivity.class);
+ intent.setData(accountUri);
+ startActivity(intent);
+ }
+ });
+
+ // Give some text to display if there is no data. In a real
+ // application this would come from a resource.
+ setEmptyText(getString(R.string.api_settings_accounts_empty));
+
+ // We have a menu item to show in action bar.
+ setHasOptionsMenu(true);
+
+ // Create an empty adapter we will use to display the loaded data.
+ mAdapter = new AccountsAdapter(getActivity(), null, 0);
+ setListAdapter(mAdapter);
+
+ // Prepare the loader. Either re-connect with an existing one,
+ // or start a new one.
+ getLoaderManager().initLoader(0, null, this);
+ }
+
+ // These are the Contacts rows that we will retrieve.
+ static final String[] PROJECTION = new String[]{
+ KeychainContract.ApiAccounts._ID, // 0
+ KeychainContract.ApiAccounts.ACCOUNT_NAME // 1
+ };
+
+ public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+ // This is called when a new Loader needs to be created. This
+ // sample only has one Loader, so we don't care about the ID.
+
+ // Now create and return a CursorLoader that will take care of
+ // creating a Cursor for the data being displayed.
+ return new CursorLoader(getActivity(), mDataUri, PROJECTION, null, null,
+ KeychainContract.ApiAccounts.ACCOUNT_NAME + " COLLATE LOCALIZED ASC");
+ }
+
+ public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+ // Swap the new cursor in. (The framework will take care of closing the
+ // old cursor once we return.)
+ mAdapter.swapCursor(data);
+ }
+
+ public void onLoaderReset(Loader<Cursor> loader) {
+ // This is called when the last Cursor provided to onLoadFinished()
+ // above is about to be closed. We need to make sure we are no
+ // longer using it.
+ mAdapter.swapCursor(null);
+ }
+
+ private class AccountsAdapter extends CursorAdapter {
+ private LayoutInflater mInflater;
+
+ public AccountsAdapter(Context context, Cursor c, int flags) {
+ super(context, c, flags);
+
+ mInflater = LayoutInflater.from(context);
+ }
+
+ /**
+ * Similar to CursorAdapter.getItemId().
+ * Required to build Uris for api accounts, which are not based on row ids
+ *
+ * @param position
+ * @return
+ */
+ public String getItemAccountName(int position) {
+ if (mDataValid && mCursor != null) {
+ if (mCursor.moveToPosition(position)) {
+ return mCursor.getString(1);
+ } else {
+ return null;
+ }
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public void bindView(View view, Context context, Cursor cursor) {
+ TextView text = (TextView) view.findViewById(R.id.api_accounts_adapter_item_name);
+
+ String accountName = cursor.getString(1);
+ text.setText(accountName);
+ }
+
+ @Override
+ public View newView(Context context, Cursor cursor, ViewGroup parent) {
+ return mInflater.inflate(R.layout.api_accounts_adapter_list_item, null);
+ }
+ }
+
+}
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
new file mode 100644
index 000000000..818c296c1
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.remote.ui;
+
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.ActionBarActivity;
+import android.view.Menu;
+import android.view.MenuItem;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.provider.KeychainContract;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.remote.AppSettings;
+import org.sufficientlysecure.keychain.util.Log;
+
+public class AppSettingsActivity extends ActionBarActivity {
+ private Uri mAppUri;
+
+ private AppSettingsFragment mSettingsFragment;
+ private AccountsListFragment mAccountsListFragment;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // let the actionbar look like Android's contact app
+ ActionBar actionBar = getSupportActionBar();
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ actionBar.setIcon(android.R.color.transparent);
+ actionBar.setHomeButtonEnabled(true);
+
+ setContentView(R.layout.api_app_settings_activity);
+
+ mSettingsFragment = (AppSettingsFragment) getSupportFragmentManager().findFragmentById(
+ R.id.api_app_settings_fragment);
+
+ Intent intent = getIntent();
+ mAppUri = intent.getData();
+ if (mAppUri == null) {
+ Log.e(Constants.TAG, "Intent data missing. Should be Uri of app!");
+ finish();
+ return;
+ } else {
+ Log.d(Constants.TAG, "uri: " + mAppUri);
+ loadData(savedInstanceState, mAppUri);
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ getMenuInflater().inflate(R.menu.api_app_settings, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.menu_api_settings_revoke:
+ revokeAccess();
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ private void loadData(Bundle savedInstanceState, Uri appUri) {
+ AppSettings settings = ProviderHelper.getApiAppSettings(this, appUri);
+ mSettingsFragment.setAppSettings(settings);
+
+ String appName;
+ PackageManager pm = getPackageManager();
+ try {
+ ApplicationInfo ai = pm.getApplicationInfo(settings.getPackageName(), 0);
+ appName = (String) pm.getApplicationLabel(ai);
+ } catch (PackageManager.NameNotFoundException e) {
+ // fallback
+ appName = settings.getPackageName();
+ }
+ setTitle(appName);
+
+ Uri accountsUri = appUri.buildUpon().appendPath(KeychainContract.PATH_ACCOUNTS).build();
+ Log.d(Constants.TAG, "accountsUri: " + accountsUri);
+ startListFragment(savedInstanceState, accountsUri);
+ }
+
+ private void startListFragment(Bundle savedInstanceState, Uri dataUri) {
+ // 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
+ mAccountsListFragment = AccountsListFragment.newInstance(dataUri);
+
+ // Add the fragment to the 'fragment_container' FrameLayout
+ // NOTE: We use commitAllowingStateLoss() to prevent weird crashes!
+ getSupportFragmentManager().beginTransaction()
+ .replace(R.id.api_accounts_list_fragment, mAccountsListFragment)
+ .commitAllowingStateLoss();
+ // do it immediately!
+ getSupportFragmentManager().executePendingTransactions();
+ }
+
+ private void revokeAccess() {
+ if (getContentResolver().delete(mAppUri, null, null) <= 0) {
+ throw new RuntimeException();
+ }
+ finish();
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsFragment.java
new file mode 100644
index 000000000..a6db02708
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsFragment.java
@@ -0,0 +1,108 @@
+/*
+ * 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.remote.ui;
+
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.graphics.drawable.Drawable;
+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.ImageView;
+import android.widget.TextView;
+
+import org.spongycastle.util.encoders.Hex;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.remote.AppSettings;
+import org.sufficientlysecure.keychain.util.Log;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+public class AppSettingsFragment extends Fragment {
+
+ // model
+ private AppSettings mAppSettings;
+
+ // view
+ private TextView mAppNameView;
+ private ImageView mAppIconView;
+ private TextView mPackageName;
+ private TextView mPackageSignature;
+
+ public AppSettings getAppSettings() {
+ return mAppSettings;
+ }
+
+ public void setAppSettings(AppSettings appSettings) {
+ this.mAppSettings = appSettings;
+ updateView(appSettings);
+ }
+
+ /**
+ * Inflate the layout for this fragment
+ */
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.api_app_settings_fragment, container, false);
+ 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);
+ return view;
+ }
+
+ private void updateView(AppSettings appSettings) {
+ // get application name and icon from package manager
+ String appName;
+ Drawable appIcon = null;
+ PackageManager pm = getActivity().getApplicationContext().getPackageManager();
+ try {
+ ApplicationInfo ai = pm.getApplicationInfo(appSettings.getPackageName(), 0);
+
+ appName = (String) pm.getApplicationLabel(ai);
+ appIcon = pm.getApplicationIcon(ai);
+ } catch (NameNotFoundException e) {
+ // fallback
+ appName = appSettings.getPackageName();
+ }
+ mAppNameView.setText(appName);
+ mAppIconView.setImageDrawable(appIcon);
+
+ // advanced info: package name
+ mPackageName.setText(appSettings.getPackageName());
+
+ // advanced info: package signature SHA-256
+ try {
+ MessageDigest md = MessageDigest.getInstance("SHA-256");
+ md.update(appSettings.getPackageSignature());
+ byte[] digest = md.digest();
+ String signature = new String(Hex.encode(digest));
+
+ mPackageSignature.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/AppsListActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppsListActivity.java
new file mode 100644
index 000000000..f86d279f0
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppsListActivity.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.remote.ui;
+
+import android.os.Bundle;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.ui.DrawerActivity;
+
+public class AppsListActivity extends DrawerActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.api_apps_list_activity);
+
+ setupDrawerNavigation(savedInstanceState);
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppsListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppsListFragment.java
new file mode 100644
index 000000000..9d0e6d3ef
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppsListFragment.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.remote.ui;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v4.app.ListFragment;
+import android.support.v4.app.LoaderManager;
+import android.support.v4.content.CursorLoader;
+import android.support.v4.content.Loader;
+import android.support.v4.widget.CursorAdapter;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.provider.KeychainContract;
+import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
+
+public class AppsListFragment extends ListFragment implements
+ LoaderManager.LoaderCallbacks<Cursor> {
+
+ // This is the Adapter being used to display the list's data.
+ RegisteredAppsAdapter mAdapter;
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ getListView().setOnItemClickListener(new OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
+ String selectedPackageName = mAdapter.getItemPackageName(position);
+ // edit app settings
+ Intent intent = new Intent(getActivity(), AppSettingsActivity.class);
+ intent.setData(KeychainContract.ApiApps.buildByPackageNameUri(selectedPackageName));
+ startActivity(intent);
+ }
+ });
+
+ // Give some text to display if there is no data. In a real
+ // application this would come from a resource.
+ setEmptyText(getString(R.string.api_no_apps));
+
+ // We have a menu item to show in action bar.
+ setHasOptionsMenu(true);
+
+ // Create an empty adapter we will use to display the loaded data.
+ mAdapter = new RegisteredAppsAdapter(getActivity(), null, 0);
+ setListAdapter(mAdapter);
+
+ // Prepare the loader. Either re-connect with an existing one,
+ // or start a new one.
+ getLoaderManager().initLoader(0, null, this);
+ }
+
+ // These are the Contacts rows that we will retrieve.
+ static final String[] PROJECTION = new String[]{
+ ApiApps._ID, // 0
+ ApiApps.PACKAGE_NAME // 1
+ };
+
+ public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+ // This is called when a new Loader needs to be created. This
+ // sample only has one Loader, so we don't care about the ID.
+ // First, pick the base URI to use depending on whether we are
+ // currently filtering.
+ Uri baseUri = ApiApps.CONTENT_URI;
+
+ // Now create and return a CursorLoader that will take care of
+ // creating a Cursor for the data being displayed.
+ return new CursorLoader(getActivity(), baseUri, PROJECTION, null, null,
+ ApiApps.PACKAGE_NAME + " COLLATE LOCALIZED ASC");
+ }
+
+ public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+ // Swap the new cursor in. (The framework will take care of closing the
+ // old cursor once we return.)
+ mAdapter.swapCursor(data);
+ }
+
+ public void onLoaderReset(Loader<Cursor> loader) {
+ // This is called when the last Cursor provided to onLoadFinished()
+ // above is about to be closed. We need to make sure we are no
+ // longer using it.
+ mAdapter.swapCursor(null);
+ }
+
+ private class RegisteredAppsAdapter extends CursorAdapter {
+
+ private LayoutInflater mInflater;
+ private PackageManager mPM;
+
+ public RegisteredAppsAdapter(Context context, Cursor c, int flags) {
+ super(context, c, flags);
+
+ mInflater = LayoutInflater.from(context);
+ mPM = context.getApplicationContext().getPackageManager();
+ }
+
+ /**
+ * Similar to CursorAdapter.getItemId().
+ * Required to build Uris for api apps, which are not based on row ids
+ *
+ * @param position
+ * @return
+ */
+ public String getItemPackageName(int position) {
+ if (mDataValid && mCursor != null) {
+ if (mCursor.moveToPosition(position)) {
+ return mCursor.getString(1);
+ } else {
+ return null;
+ }
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public void bindView(View view, Context context, Cursor cursor) {
+ TextView text = (TextView) view.findViewById(R.id.api_apps_adapter_item_name);
+ ImageView icon = (ImageView) view.findViewById(R.id.api_apps_adapter_item_icon);
+
+ String packageName = cursor.getString(cursor.getColumnIndex(ApiApps.PACKAGE_NAME));
+ if (packageName != null) {
+ // get application name
+ try {
+ ApplicationInfo ai = mPM.getApplicationInfo(packageName, 0);
+
+ text.setText(mPM.getApplicationLabel(ai));
+ icon.setImageDrawable(mPM.getApplicationIcon(ai));
+ } catch (final PackageManager.NameNotFoundException e) {
+ // fallback
+ text.setText(packageName);
+ }
+ } else {
+ // fallback
+ text.setText(packageName);
+ }
+
+ }
+
+ @Override
+ public View newView(Context context, Cursor cursor, ViewGroup parent) {
+ return mInflater.inflate(R.layout.api_apps_adapter_list_item, null);
+ }
+ }
+
+}
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
new file mode 100644
index 000000000..ab95f2691
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java
@@ -0,0 +1,303 @@
+/*
+ * 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.remote.ui;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.support.v7.app.ActionBarActivity;
+import android.view.View;
+
+import org.openintents.openpgp.util.OpenPgpApi;
+import org.sufficientlysecure.htmltextview.HtmlTextView;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.Id;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.helper.ActionBarHelper;
+import org.sufficientlysecure.keychain.provider.KeychainContract;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.remote.AccountSettings;
+import org.sufficientlysecure.keychain.remote.AppSettings;
+import org.sufficientlysecure.keychain.ui.SelectPublicKeyFragment;
+import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
+import org.sufficientlysecure.keychain.util.Log;
+
+import java.util.ArrayList;
+
+public class RemoteServiceActivity extends ActionBarActivity {
+
+ public static final String ACTION_REGISTER = Constants.INTENT_PREFIX + "API_ACTIVITY_REGISTER";
+ public static final String ACTION_CREATE_ACCOUNT = Constants.INTENT_PREFIX
+ + "API_ACTIVITY_CREATE_ACCOUNT";
+ public static final String ACTION_CACHE_PASSPHRASE = Constants.INTENT_PREFIX
+ + "API_ACTIVITY_CACHE_PASSPHRASE";
+ public static final String ACTION_SELECT_PUB_KEYS = Constants.INTENT_PREFIX
+ + "API_ACTIVITY_SELECT_PUB_KEYS";
+ public static final String ACTION_ERROR_MESSAGE = Constants.INTENT_PREFIX
+ + "API_ACTIVITY_ERROR_MESSAGE";
+
+ public static final String EXTRA_MESSENGER = "messenger";
+
+ public static final String EXTRA_DATA = "data";
+
+ // passphrase action
+ public static final String EXTRA_SECRET_KEY_ID = "secret_key_id";
+ // register action
+ public static final String EXTRA_PACKAGE_NAME = "package_name";
+ public static final String EXTRA_PACKAGE_SIGNATURE = "package_signature";
+ // create acc action
+ 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_DUBLICATE_USER_IDS = "dublicate_user_ids";
+ // error message
+ public static final String EXTRA_ERROR_MESSAGE = "error_message";
+
+ // register view
+ private AppSettingsFragment mAppSettingsFragment;
+ // create acc view
+ private AccountSettingsFragment mAccSettingsFragment;
+ // select pub keys view
+ private SelectPublicKeyFragment mSelectFragment;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ handleActions(getIntent(), savedInstanceState);
+ }
+
+ protected void handleActions(Intent intent, Bundle savedInstanceState) {
+
+ String action = intent.getAction();
+ final Bundle extras = intent.getExtras();
+
+
+ if (ACTION_REGISTER.equals(action)) {
+ final String packageName = extras.getString(EXTRA_PACKAGE_NAME);
+ final byte[] packageSignature = extras.getByteArray(EXTRA_PACKAGE_SIGNATURE);
+ Log.d(Constants.TAG, "ACTION_REGISTER packageName: " + packageName);
+
+ // Inflate a "Done"/"Cancel" custom action bar view
+ ActionBarHelper.setTwoButtonView(getSupportActionBar(),
+ R.string.api_register_allow, R.drawable.ic_action_done,
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // Allow
+
+ ProviderHelper.insertApiApp(RemoteServiceActivity.this,
+ mAppSettingsFragment.getAppSettings());
+
+ // give data through for new service call
+ Intent resultData = extras.getParcelable(EXTRA_DATA);
+ RemoteServiceActivity.this.setResult(RESULT_OK, resultData);
+ RemoteServiceActivity.this.finish();
+ }
+ }, R.string.api_register_disallow, R.drawable.ic_action_cancel,
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // Disallow
+ RemoteServiceActivity.this.setResult(RESULT_CANCELED);
+ RemoteServiceActivity.this.finish();
+ }
+ }
+ );
+
+ setContentView(R.layout.api_remote_register_app);
+
+ mAppSettingsFragment = (AppSettingsFragment) getSupportFragmentManager().findFragmentById(
+ R.id.api_app_settings_fragment);
+
+ AppSettings settings = new AppSettings(packageName, packageSignature);
+ mAppSettingsFragment.setAppSettings(settings);
+ } else if (ACTION_CREATE_ACCOUNT.equals(action)) {
+ final String packageName = extras.getString(EXTRA_PACKAGE_NAME);
+ final String accName = extras.getString(EXTRA_ACC_NAME);
+
+ // Inflate a "Done"/"Cancel" custom action bar view
+ ActionBarHelper.setTwoButtonView(getSupportActionBar(),
+ R.string.api_settings_save, R.drawable.ic_action_done,
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // Save
+
+ // user needs to select a key!
+ if (mAccSettingsFragment.getAccSettings().getKeyId() == Id.key.none) {
+ mAccSettingsFragment.setErrorOnSelectKeyFragment(
+ getString(R.string.api_register_error_select_key));
+ } else {
+ ProviderHelper.insertApiAccount(RemoteServiceActivity.this,
+ KeychainContract.ApiAccounts.buildBaseUri(packageName),
+ mAccSettingsFragment.getAccSettings());
+
+ // give data through for new service call
+ Intent resultData = extras.getParcelable(EXTRA_DATA);
+ RemoteServiceActivity.this.setResult(RESULT_OK, resultData);
+ RemoteServiceActivity.this.finish();
+ }
+ }
+ }, R.string.api_settings_cancel, R.drawable.ic_action_cancel,
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // Cancel
+ RemoteServiceActivity.this.setResult(RESULT_CANCELED);
+ RemoteServiceActivity.this.finish();
+ }
+ }
+ );
+
+ setContentView(R.layout.api_remote_create_account);
+
+ mAccSettingsFragment = (AccountSettingsFragment) getSupportFragmentManager().findFragmentById(
+ R.id.api_account_settings_fragment);
+
+ AccountSettings settings = new AccountSettings(accName);
+ mAccSettingsFragment.setAccSettings(settings);
+ } else if (ACTION_CACHE_PASSPHRASE.equals(action)) {
+ long secretKeyId = extras.getLong(EXTRA_SECRET_KEY_ID);
+ final Intent resultData = extras.getParcelable(EXTRA_DATA);
+
+ PassphraseDialogFragment.show(this, secretKeyId,
+ new Handler() {
+ @Override
+ public void handleMessage(Message message) {
+ if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) {
+ // return given params again, for calling the service method again
+ RemoteServiceActivity.this.setResult(RESULT_OK, resultData);
+ } else {
+ RemoteServiceActivity.this.setResult(RESULT_CANCELED);
+ }
+
+ RemoteServiceActivity.this.finish();
+ }
+ });
+
+ } else if (ACTION_SELECT_PUB_KEYS.equals(action)) {
+ long[] selectedMasterKeyIds = intent.getLongArrayExtra(EXTRA_SELECTED_MASTER_KEY_IDS);
+ ArrayList<String> missingUserIds = intent
+ .getStringArrayListExtra(EXTRA_MISSING_USER_IDS);
+ ArrayList<String> dublicateUserIds = intent
+ .getStringArrayListExtra(EXTRA_DUBLICATE_USER_IDS);
+
+ // TODO: do this with spannable instead of HTML to prevent parsing failures with weird user ids
+ String text = "<b>" + getString(R.string.api_select_pub_keys_text) + "</b>";
+ text += "<br/><br/>";
+ if (missingUserIds != null && missingUserIds.size() > 0) {
+ text += getString(R.string.api_select_pub_keys_missing_text);
+ text += "<br/>";
+ text += "<ul>";
+ for (String userId : missingUserIds) {
+ text += "<li>" + userId + "</li>";
+ }
+ text += "</ul>";
+ text += "<br/>";
+ }
+ if (dublicateUserIds != null && dublicateUserIds.size() > 0) {
+ text += getString(R.string.api_select_pub_keys_dublicates_text);
+ text += "<br/>";
+ text += "<ul>";
+ for (String userId : dublicateUserIds) {
+ text += "<li>" + userId + "</li>";
+ }
+ text += "</ul>";
+ }
+
+ // Inflate a "Done"/"Cancel" custom action bar view
+ ActionBarHelper.setTwoButtonView(getSupportActionBar(),
+ R.string.btn_okay, R.drawable.ic_action_done,
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // add key ids to params Bundle for new request
+ Intent resultData = extras.getParcelable(EXTRA_DATA);
+ resultData.putExtra(OpenPgpApi.EXTRA_KEY_IDS,
+ mSelectFragment.getSelectedMasterKeyIds());
+
+ RemoteServiceActivity.this.setResult(RESULT_OK, resultData);
+ RemoteServiceActivity.this.finish();
+ }
+ }, R.string.btn_do_not_save, R.drawable.ic_action_cancel, new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // cancel
+ RemoteServiceActivity.this.setResult(RESULT_CANCELED);
+ RemoteServiceActivity.this.finish();
+ }
+ }
+ );
+
+ setContentView(R.layout.api_remote_select_pub_keys);
+
+ // set text on view
+ HtmlTextView textView = (HtmlTextView) findViewById(R.id.api_select_pub_keys_text);
+ textView.setHtmlFromString(text);
+
+ /* Load select pub keys fragment */
+ // Check that the activity is using the layout version with
+ // the fragment_container FrameLayout
+ if (findViewById(R.id.api_select_pub_keys_fragment_container) != null) {
+
+ // 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
+ mSelectFragment = SelectPublicKeyFragment.newInstance(selectedMasterKeyIds);
+
+ // Add the fragment to the 'fragment_container' FrameLayout
+ getSupportFragmentManager().beginTransaction()
+ .add(R.id.api_select_pub_keys_fragment_container, mSelectFragment).commit();
+ }
+ } else if (ACTION_ERROR_MESSAGE.equals(action)) {
+ String errorMessage = intent.getStringExtra(EXTRA_ERROR_MESSAGE);
+
+ String text = "<font color=\"red\">" + errorMessage + "</font>";
+
+ // Inflate a "Done" custom action bar view
+ ActionBarHelper.setOneButtonView(getSupportActionBar(),
+ R.string.btn_okay, R.drawable.ic_action_done,
+ new View.OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ RemoteServiceActivity.this.setResult(RESULT_CANCELED);
+ RemoteServiceActivity.this.finish();
+ }
+ });
+
+ setContentView(R.layout.api_remote_error_message);
+
+ // set text on view
+ HtmlTextView textView = (HtmlTextView) findViewById(R.id.api_app_error_message_text);
+ textView.setHtmlFromString(text);
+ } else {
+ Log.e(Constants.TAG, "Action does not exist!");
+ setResult(RESULT_CANCELED);
+ finish();
+ }
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java
new file mode 100644
index 000000000..1c6aa7971
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java
@@ -0,0 +1,905 @@
+/*
+ * 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.IntentService;
+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.os.RemoteException;
+
+import org.spongycastle.bcpg.sig.KeyFlags;
+import org.spongycastle.openpgp.PGPKeyRing;
+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.spongycastle.openpgp.PGPUtil;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.Id;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.helper.FileHelper;
+import org.sufficientlysecure.keychain.helper.OtherHelper;
+import org.sufficientlysecure.keychain.helper.Preferences;
+import org.sufficientlysecure.keychain.pgp.PgpConversionHelper;
+import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify;
+import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyResult;
+import org.sufficientlysecure.keychain.pgp.PgpHelper;
+import org.sufficientlysecure.keychain.pgp.PgpImportExport;
+import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
+import org.sufficientlysecure.keychain.pgp.PgpKeyOperation;
+import org.sufficientlysecure.keychain.pgp.PgpSignEncrypt;
+import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
+import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralMsgIdException;
+import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
+import org.sufficientlysecure.keychain.provider.KeychainDatabase;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry;
+import org.sufficientlysecure.keychain.util.HkpKeyServer;
+import org.sufficientlysecure.keychain.util.InputData;
+import org.sufficientlysecure.keychain.util.KeychainServiceListener;
+import org.sufficientlysecure.keychain.util.Log;
+import org.sufficientlysecure.keychain.util.ProgressDialogUpdater;
+import org.sufficientlysecure.keychain.util.ProgressScaler;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This Service contains all important long lasting operations for APG. 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 ProgressDialogUpdater, KeychainServiceListener {
+
+ /* 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_ENCRYPT_SIGN = Constants.INTENT_PREFIX + "ENCRYPT_SIGN";
+
+ public static final String ACTION_DECRYPT_VERIFY = Constants.INTENT_PREFIX + "DECRYPT_VERIFY";
+
+ public static final String ACTION_SAVE_KEYRING = Constants.INTENT_PREFIX + "SAVE_KEYRING";
+ public static final String ACTION_GENERATE_KEY = Constants.INTENT_PREFIX + "GENERATE_KEY";
+ public static final String ACTION_GENERATE_DEFAULT_RSA_KEYS = Constants.INTENT_PREFIX
+ + "GENERATE_DEFAULT_RSA_KEYS";
+
+ public static final String ACTION_DELETE_FILE_SECURELY = Constants.INTENT_PREFIX
+ + "DELETE_FILE_SECURELY";
+
+ 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_DOWNLOAD_AND_IMPORT_KEYS = Constants.INTENT_PREFIX + "QUERY_KEYRING";
+
+ public static final String ACTION_CERTIFY_KEYRING = Constants.INTENT_PREFIX + "SIGN_KEYRING";
+
+ /* keys for data bundle */
+
+ // encrypt, decrypt, import export
+ public static final String TARGET = "target";
+ // possible targets:
+ public static final int TARGET_BYTES = 1;
+ public static final int TARGET_URI = 2;
+
+ // encrypt
+ public static final String ENCRYPT_SIGNATURE_KEY_ID = "secret_key_id";
+ public static final String ENCRYPT_USE_ASCII_ARMOR = "use_ascii_armor";
+ public static final String ENCRYPT_ENCRYPTION_KEYS_IDS = "encryption_keys_ids";
+ public static final String ENCRYPT_COMPRESSION_ID = "compression_id";
+ public static final String ENCRYPT_MESSAGE_BYTES = "message_bytes";
+ public static final String ENCRYPT_INPUT_FILE = "input_file";
+ public static final String ENCRYPT_OUTPUT_FILE = "output_file";
+ public static final String ENCRYPT_SYMMETRIC_PASSPHRASE = "passphrase";
+
+ // decrypt/verify
+ public static final String DECRYPT_CIPHERTEXT_BYTES = "ciphertext_bytes";
+ public static final String DECRYPT_PASSPHRASE = "passphrase";
+
+ // save keyring
+ public static final String SAVE_KEYRING_PARCEL = "save_parcel";
+ public static final String SAVE_KEYRING_CAN_SIGN = "can_sign";
+
+
+ // generate key
+ public static final String GENERATE_KEY_ALGORITHM = "algorithm";
+ public static final String GENERATE_KEY_KEY_SIZE = "key_size";
+ public static final String GENERATE_KEY_SYMMETRIC_PASSPHRASE = "passphrase";
+ public static final String GENERATE_KEY_MASTER_KEY = "master_key";
+
+ // delete file securely
+ public static final String DELETE_FILE = "deleteFile";
+
+ // import key
+ public static final String IMPORT_KEY_LIST = "import_key_list";
+
+ // export key
+ public static final String EXPORT_OUTPUT_STREAM = "export_output_stream";
+ public static final String EXPORT_FILENAME = "export_filename";
+ 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";
+
+ // query key
+ public static final String DOWNLOAD_KEY_SERVER = "query_key_server";
+ public static final String DOWNLOAD_KEY_LIST = "query_key_id";
+
+ // sign key
+ public static final String CERTIFY_KEY_MASTER_KEY_ID = "sign_key_master_key_id";
+ public static final String CERTIFY_KEY_PUB_KEY_ID = "sign_key_pub_key_id";
+ public static final String CERTIFY_KEY_UIDS = "sign_key_uids";
+
+ /*
+ * possible data keys as result send over messenger
+ */
+ // keys
+ public static final String RESULT_NEW_KEY = "new_key";
+ public static final String RESULT_KEY_USAGES = "new_key_usages";
+
+ // encrypt
+ public static final String RESULT_BYTES = "encrypted_data";
+
+ // decrypt/verify
+ public static final String RESULT_DECRYPTED_BYTES = "decrypted_data";
+ public static final String RESULT_DECRYPT_VERIFY_RESULT = "signature";
+
+ // import
+ public static final String RESULT_IMPORT_ADDED = "added";
+ public static final String RESULT_IMPORT_UPDATED = "updated";
+ public static final String RESULT_IMPORT_BAD = "bad";
+
+ // export
+ public static final String RESULT_EXPORT = "exported";
+
+ Messenger mMessenger;
+
+ private boolean mIsCanceled;
+
+ public KeychainIntentService() {
+ super("KeychainIntentService");
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ this.mIsCanceled = true;
+ }
+
+ /**
+ * 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) {
+ 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);
+
+ OtherHelper.logDebugBundle(data, "EXTRA_DATA");
+
+ String action = intent.getAction();
+
+ // executeServiceMethod action from extra bundle
+ if (ACTION_ENCRYPT_SIGN.equals(action)) {
+ try {
+ /* Input */
+ int target = data.getInt(TARGET);
+
+ long signatureKeyId = data.getLong(ENCRYPT_SIGNATURE_KEY_ID);
+ String symmetricPassphrase = data.getString(ENCRYPT_SYMMETRIC_PASSPHRASE);
+
+ boolean useAsciiArmor = data.getBoolean(ENCRYPT_USE_ASCII_ARMOR);
+ long encryptionKeyIds[] = data.getLongArray(ENCRYPT_ENCRYPTION_KEYS_IDS);
+ int compressionId = data.getInt(ENCRYPT_COMPRESSION_ID);
+ InputStream inStream;
+ long inLength;
+ InputData inputData;
+ OutputStream outStream;
+// String streamFilename = null;
+ switch (target) {
+ case TARGET_BYTES: /* encrypting bytes directly */
+ byte[] bytes = data.getByteArray(ENCRYPT_MESSAGE_BYTES);
+
+ inStream = new ByteArrayInputStream(bytes);
+ inLength = bytes.length;
+
+ inputData = new InputData(inStream, inLength);
+ outStream = new ByteArrayOutputStream();
+
+ break;
+ case TARGET_URI: /* encrypting file */
+ String inputFile = data.getString(ENCRYPT_INPUT_FILE);
+ String outputFile = data.getString(ENCRYPT_OUTPUT_FILE);
+
+ // check if storage is ready
+ if (!FileHelper.isStorageMounted(inputFile)
+ || !FileHelper.isStorageMounted(outputFile)) {
+ throw new PgpGeneralException(
+ getString(R.string.error_external_storage_not_ready));
+ }
+
+ inStream = new FileInputStream(inputFile);
+ File file = new File(inputFile);
+ inLength = file.length();
+ inputData = new InputData(inStream, inLength);
+
+ outStream = new FileOutputStream(outputFile);
+
+ break;
+
+ // TODO: not used currently
+// case TARGET_STREAM: /* Encrypting stream from content uri */
+// Uri providerUri = (Uri) data.getParcelable(ENCRYPT_PROVIDER_URI);
+//
+// // InputStream
+// InputStream in = getContentResolver().openInputStream(providerUri);
+// inLength = PgpHelper.getLengthOfStream(in);
+// inputData = new InputData(in, inLength);
+//
+// // OutputStream
+// try {
+// while (true) {
+// streamFilename = PgpHelper.generateRandomFilename(32);
+// if (streamFilename == null) {
+// throw new PgpGeneralException("couldn't generate random file name");
+// }
+// openFileInput(streamFilename).close();
+// }
+// } catch (FileNotFoundException e) {
+// // found a name that isn't used yet
+// }
+// outStream = openFileOutput(streamFilename, Context.MODE_PRIVATE);
+//
+// break;
+
+ default:
+ throw new PgpGeneralException("No target choosen!");
+
+ }
+
+ /* Operation */
+ PgpSignEncrypt.Builder builder =
+ new PgpSignEncrypt.Builder(this, inputData, outStream);
+ builder.progress(this);
+
+ builder.enableAsciiArmorOutput(useAsciiArmor)
+ .compressionId(compressionId)
+ .symmetricEncryptionAlgorithm(
+ Preferences.getPreferences(this).getDefaultEncryptionAlgorithm())
+ .signatureForceV3(Preferences.getPreferences(this).getForceV3Signatures())
+ .encryptionKeyIds(encryptionKeyIds)
+ .symmetricPassphrase(symmetricPassphrase)
+ .signatureKeyId(signatureKeyId)
+ .signatureHashAlgorithm(
+ Preferences.getPreferences(this).getDefaultHashAlgorithm())
+ .signaturePassphrase(
+ PassphraseCacheService.getCachedPassphrase(this, signatureKeyId));
+
+ builder.build().execute();
+
+ outStream.close();
+
+ /* Output */
+
+ Bundle resultData = new Bundle();
+
+ switch (target) {
+ case TARGET_BYTES:
+ byte output[] = ((ByteArrayOutputStream) outStream).toByteArray();
+
+ resultData.putByteArray(RESULT_BYTES, output);
+
+ break;
+ case TARGET_URI:
+ // nothing, file was written, just send okay
+
+ break;
+// case TARGET_STREAM:
+// String uri = DataStream.buildDataStreamUri(streamFilename).toString();
+// resultData.putString(RESULT_URI, uri);
+//
+// break;
+ }
+
+ OtherHelper.logDebugBundle(resultData, "resultData");
+
+ sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData);
+ } catch (Exception e) {
+ sendErrorToHandler(e);
+ }
+ } else if (ACTION_DECRYPT_VERIFY.equals(action)) {
+ try {
+ /* Input */
+ int target = data.getInt(TARGET);
+
+ byte[] bytes = data.getByteArray(DECRYPT_CIPHERTEXT_BYTES);
+ String passphrase = data.getString(DECRYPT_PASSPHRASE);
+
+ InputStream inStream;
+ long inLength;
+ InputData inputData;
+ OutputStream outStream;
+ String streamFilename = null;
+ switch (target) {
+ case TARGET_BYTES: /* decrypting bytes directly */
+ inStream = new ByteArrayInputStream(bytes);
+ inLength = bytes.length;
+
+ inputData = new InputData(inStream, inLength);
+ outStream = new ByteArrayOutputStream();
+
+ break;
+
+ case TARGET_URI: /* decrypting file */
+ String inputFile = data.getString(ENCRYPT_INPUT_FILE);
+ String outputFile = data.getString(ENCRYPT_OUTPUT_FILE);
+
+ // check if storage is ready
+ if (!FileHelper.isStorageMounted(inputFile)
+ || !FileHelper.isStorageMounted(outputFile)) {
+ throw new PgpGeneralException(
+ getString(R.string.error_external_storage_not_ready));
+ }
+
+ // InputStream
+ inLength = -1;
+ inStream = new FileInputStream(inputFile);
+ File file = new File(inputFile);
+ inLength = file.length();
+ inputData = new InputData(inStream, inLength);
+
+ // OutputStream
+ outStream = new FileOutputStream(outputFile);
+
+ break;
+
+ // TODO: not used, maybe contains code useful for new decrypt method for files?
+// case TARGET_STREAM: /* decrypting stream from content uri */
+// Uri providerUri = (Uri) data.getParcelable(ENCRYPT_PROVIDER_URI);
+//
+// // InputStream
+// InputStream in = getContentResolver().openInputStream(providerUri);
+// inLength = PgpHelper.getLengthOfStream(in);
+// inputData = new InputData(in, inLength);
+//
+// // OutputStream
+// try {
+// while (true) {
+// streamFilename = PgpHelper.generateRandomFilename(32);
+// if (streamFilename == null) {
+// throw new PgpGeneralException("couldn't generate random file name");
+// }
+// openFileInput(streamFilename).close();
+// }
+// } catch (FileNotFoundException e) {
+// // found a name that isn't used yet
+// }
+// outStream = openFileOutput(streamFilename, Context.MODE_PRIVATE);
+//
+// break;
+
+ default:
+ throw new PgpGeneralException("No target choosen!");
+
+ }
+
+ /* Operation */
+
+ Bundle resultData = new Bundle();
+
+ // verifyText and decrypt returning additional resultData values for the
+ // verification of signatures
+ PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder(this, inputData, outStream);
+ builder.progressDialogUpdater(this);
+
+ builder.allowSymmetricDecryption(true)
+ .passphrase(passphrase);
+
+ PgpDecryptVerifyResult decryptVerifyResult = builder.build().execute();
+
+ outStream.close();
+
+ resultData.putParcelable(RESULT_DECRYPT_VERIFY_RESULT, decryptVerifyResult);
+
+ /* Output */
+
+ switch (target) {
+ case TARGET_BYTES:
+ byte output[] = ((ByteArrayOutputStream) outStream).toByteArray();
+ resultData.putByteArray(RESULT_DECRYPTED_BYTES, output);
+ break;
+ case TARGET_URI:
+ // nothing, file was written, just send okay and verification bundle
+
+ break;
+// case TARGET_STREAM:
+// String uri = DataStream.buildDataStreamUri(streamFilename).toString();
+// resultData.putString(RESULT_URI, uri);
+//
+// break;
+ }
+
+ OtherHelper.logDebugBundle(resultData, "resultData");
+
+ sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData);
+ } catch (Exception e) {
+ sendErrorToHandler(e);
+ }
+ } else if (ACTION_SAVE_KEYRING.equals(action)) {
+ try {
+ /* Input */
+ SaveKeyringParcel saveParams = data.getParcelable(SAVE_KEYRING_PARCEL);
+ String oldPassphrase = saveParams.oldPassphrase;
+ String newPassphrase = saveParams.newPassphrase;
+ boolean canSign = true;
+
+ if (data.containsKey(SAVE_KEYRING_CAN_SIGN)) {
+ canSign = data.getBoolean(SAVE_KEYRING_CAN_SIGN);
+ }
+
+ if (newPassphrase == null) {
+ newPassphrase = oldPassphrase;
+ }
+
+ long masterKeyId = saveParams.keys.get(0).getKeyID();
+
+ /* Operation */
+ if (!canSign) {
+ PgpKeyOperation keyOperations = new PgpKeyOperation(new ProgressScaler(this, 0, 50, 100));
+ PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRing(this, masterKeyId);
+ keyRing = keyOperations.changeSecretKeyPassphrase(keyRing,
+ oldPassphrase, newPassphrase);
+ setProgress(R.string.progress_saving_key_ring, 50, 100);
+ ProviderHelper.saveKeyRing(this, keyRing);
+ setProgress(R.string.progress_done, 100, 100);
+ } else {
+ PgpKeyOperation keyOperations = new PgpKeyOperation(new ProgressScaler(this, 0, 90, 100));
+ PGPSecretKeyRing privkey = ProviderHelper.getPGPSecretKeyRing(this, masterKeyId);
+ PGPPublicKeyRing pubkey = ProviderHelper.getPGPPublicKeyRing(this, masterKeyId);
+ PgpKeyOperation.Pair<PGPSecretKeyRing,PGPPublicKeyRing> pair =
+ keyOperations.buildSecretKey(privkey, pubkey, saveParams);
+ setProgress(R.string.progress_saving_key_ring, 90, 100);
+ // save the pair
+ ProviderHelper.saveKeyRing(this, pair.second, pair.first);
+ setProgress(R.string.progress_done, 100, 100);
+ }
+ PassphraseCacheService.addCachedPassphrase(this, masterKeyId, newPassphrase);
+
+ /* Output */
+ sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY);
+ } catch (Exception e) {
+ sendErrorToHandler(e);
+ }
+ } else if (ACTION_GENERATE_KEY.equals(action)) {
+ try {
+ /* Input */
+ int algorithm = data.getInt(GENERATE_KEY_ALGORITHM);
+ String passphrase = data.getString(GENERATE_KEY_SYMMETRIC_PASSPHRASE);
+ int keysize = data.getInt(GENERATE_KEY_KEY_SIZE);
+ boolean masterKey = data.getBoolean(GENERATE_KEY_MASTER_KEY);
+
+ /* Operation */
+ PgpKeyOperation keyOperations = new PgpKeyOperation(new ProgressScaler(this, 0, 100, 100));
+ PGPSecretKey newKey = keyOperations.createKey(algorithm, keysize,
+ passphrase, masterKey);
+
+ /* Output */
+ Bundle resultData = new Bundle();
+ resultData.putByteArray(RESULT_NEW_KEY,
+ PgpConversionHelper.PGPSecretKeyToBytes(newKey));
+
+ OtherHelper.logDebugBundle(resultData, "resultData");
+
+ sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData);
+ } catch (Exception e) {
+ sendErrorToHandler(e);
+ }
+ } else if (ACTION_GENERATE_DEFAULT_RSA_KEYS.equals(action)) {
+ // generate one RSA 4096 key for signing and one subkey for encrypting!
+ try {
+ /* Input */
+ String passphrase = data.getString(GENERATE_KEY_SYMMETRIC_PASSPHRASE);
+ ArrayList<PGPSecretKey> newKeys = new ArrayList<PGPSecretKey>();
+ ArrayList<Integer> keyUsageList = new ArrayList<Integer>();
+
+ /* Operation */
+ int keysTotal = 3;
+ int keysCreated = 0;
+ setProgress(
+ getApplicationContext().getResources().
+ getQuantityString(R.plurals.progress_generating, keysTotal),
+ keysCreated,
+ keysTotal);
+ PgpKeyOperation keyOperations = new PgpKeyOperation(new ProgressScaler(this, 0, 100, 100));
+
+ PGPSecretKey masterKey = keyOperations.createKey(Id.choice.algorithm.rsa,
+ 4096, passphrase, true);
+ newKeys.add(masterKey);
+ keyUsageList.add(KeyFlags.CERTIFY_OTHER);
+ keysCreated++;
+ setProgress(keysCreated, keysTotal);
+
+ PGPSecretKey subKey = keyOperations.createKey(Id.choice.algorithm.rsa,
+ 4096, passphrase, false);
+ newKeys.add(subKey);
+ keyUsageList.add(KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE);
+ keysCreated++;
+ setProgress(keysCreated, keysTotal);
+
+ subKey = keyOperations.createKey(Id.choice.algorithm.rsa,
+ 4096, passphrase, false);
+ newKeys.add(subKey);
+ keyUsageList.add(KeyFlags.SIGN_DATA);
+ keysCreated++;
+ setProgress(keysCreated, keysTotal);
+
+ // TODO: default to one master for cert, one sub for encrypt and one sub
+ // for sign
+
+ /* Output */
+
+ Bundle resultData = new Bundle();
+ resultData.putByteArray(RESULT_NEW_KEY,
+ PgpConversionHelper.PGPSecretKeyArrayListToBytes(newKeys));
+ resultData.putIntegerArrayList(RESULT_KEY_USAGES, keyUsageList);
+
+ OtherHelper.logDebugBundle(resultData, "resultData");
+
+ sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData);
+ } catch (Exception e) {
+ sendErrorToHandler(e);
+ }
+ } else if (ACTION_DELETE_FILE_SECURELY.equals(action)) {
+ try {
+ /* Input */
+ String deleteFile = data.getString(DELETE_FILE);
+
+ /* Operation */
+ try {
+ PgpHelper.deleteFileSecurely(this, this, new File(deleteFile));
+ } catch (FileNotFoundException e) {
+ throw new PgpGeneralException(
+ getString(R.string.error_file_not_found, deleteFile));
+ } catch (IOException e) {
+ throw new PgpGeneralException(getString(R.string.error_file_delete_failed,
+ deleteFile));
+ }
+
+ /* Output */
+ sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY);
+ } catch (Exception e) {
+ sendErrorToHandler(e);
+ }
+ } else if (ACTION_IMPORT_KEYRING.equals(action)) {
+ try {
+ List<ImportKeysListEntry> entries = data.getParcelableArrayList(IMPORT_KEY_LIST);
+
+ Bundle resultData = new Bundle();
+
+ PgpImportExport pgpImportExport = new PgpImportExport(this, this);
+ resultData = pgpImportExport.importKeyRings(entries);
+
+ sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData);
+ } catch (Exception e) {
+ sendErrorToHandler(e);
+ }
+ } else if (ACTION_EXPORT_KEYRING.equals(action)) {
+ try {
+
+ boolean exportSecret = data.getBoolean(EXPORT_SECRET, false);
+ long[] masterKeyIds = data.getLongArray(EXPORT_KEY_RING_MASTER_KEY_ID);
+ String outputFile = data.getString(EXPORT_FILENAME);
+
+ // If not exporting all keys get the masterKeyIds of the keys to export from the intent
+ boolean exportAll = data.getBoolean(EXPORT_ALL);
+
+ // check if storage is ready
+ if (!FileHelper.isStorageMounted(outputFile)) {
+ throw new PgpGeneralException(getString(R.string.error_external_storage_not_ready));
+ }
+
+ ArrayList<Long> publicMasterKeyIds = new ArrayList<Long>();
+ ArrayList<Long> secretMasterKeyIds = new ArrayList<Long>();
+
+ String selection = null;
+ if(!exportAll) {
+ selection = KeychainDatabase.Tables.KEYS + "." + KeyRings.MASTER_KEY_ID + " IN( ";
+ for(long l : masterKeyIds) {
+ selection += Long.toString(l) + ",";
+ }
+ selection = selection.substring(0, selection.length()-1) + " )";
+ }
+
+ Cursor cursor = getContentResolver().query(KeyRings.buildUnifiedKeyRingsUri(),
+ new String[]{ KeyRings.MASTER_KEY_ID, KeyRings.HAS_SECRET },
+ selection, null, null);
+ try {
+ cursor.moveToFirst();
+ do {
+ // export public either way
+ publicMasterKeyIds.add(cursor.getLong(0));
+ // add secret if available (and requested)
+ if(exportSecret && cursor.getInt(1) != 0)
+ secretMasterKeyIds.add(cursor.getLong(0));
+ } while(cursor.moveToNext());
+ } finally {
+ cursor.close();
+ }
+
+ PgpImportExport pgpImportExport = new PgpImportExport(this, this, this);
+ Bundle resultData = pgpImportExport
+ .exportKeyRings(publicMasterKeyIds, secretMasterKeyIds,
+ new FileOutputStream(outputFile));
+
+ if (mIsCanceled) {
+ boolean isDeleted = new File(outputFile).delete();
+ }
+
+ sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData);
+ } catch (Exception e) {
+ sendErrorToHandler(e);
+ }
+ } else if (ACTION_UPLOAD_KEYRING.equals(action)) {
+ try {
+
+ /* Input */
+ String keyServer = data.getString(UPLOAD_KEY_SERVER);
+ // and dataUri!
+
+ /* Operation */
+ HkpKeyServer server = new HkpKeyServer(keyServer);
+
+ PGPPublicKeyRing keyring = (PGPPublicKeyRing) ProviderHelper.getPGPKeyRing(this, dataUri);
+ if (keyring != null) {
+ PgpImportExport pgpImportExport = new PgpImportExport(this, null);
+
+ boolean uploaded = pgpImportExport.uploadKeyRingToServer(server,
+ (PGPPublicKeyRing) keyring);
+ if (!uploaded) {
+ throw new PgpGeneralException("Unable to export key to selected server");
+ }
+ }
+
+ sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY);
+ } catch (Exception e) {
+ sendErrorToHandler(e);
+ }
+ } else if (ACTION_DOWNLOAD_AND_IMPORT_KEYS.equals(action)) {
+ try {
+ ArrayList<ImportKeysListEntry> entries = data.getParcelableArrayList(DOWNLOAD_KEY_LIST);
+ String keyServer = data.getString(DOWNLOAD_KEY_SERVER);
+
+ // this downloads the keys and places them into the ImportKeysListEntry entries
+ HkpKeyServer server = new HkpKeyServer(keyServer);
+
+ for (ImportKeysListEntry entry : entries) {
+ // if available use complete fingerprint for get request
+ byte[] downloadedKeyBytes;
+ if (entry.getFingerPrintHex() != null) {
+ downloadedKeyBytes = server.get("0x" + entry.getFingerPrintHex()).getBytes();
+ } else {
+ downloadedKeyBytes = server.get(entry.getKeyIdHex()).getBytes();
+ }
+
+ // create PGPKeyRing object based on downloaded armored key
+ PGPKeyRing downloadedKey = null;
+ BufferedInputStream bufferedInput =
+ new BufferedInputStream(new ByteArrayInputStream(downloadedKeyBytes));
+ if (bufferedInput.available() > 0) {
+ InputStream in = PGPUtil.getDecoderStream(bufferedInput);
+ PGPObjectFactory objectFactory = new PGPObjectFactory(in);
+
+ // get first object in block
+ Object obj;
+ if ((obj = objectFactory.nextObject()) != null) {
+ Log.d(Constants.TAG, "Found class: " + obj.getClass());
+
+ if (obj instanceof PGPKeyRing) {
+ downloadedKey = (PGPKeyRing) obj;
+ } else {
+ throw new PgpGeneralException("Object not recognized as PGPKeyRing!");
+ }
+ }
+ }
+
+ // verify downloaded key by comparing fingerprints
+ if (entry.getFingerPrintHex() != null) {
+ String downloadedKeyFp = PgpKeyHelper.convertFingerprintToHex(
+ downloadedKey.getPublicKey().getFingerprint());
+ if (downloadedKeyFp.equals(entry.getFingerPrintHex())) {
+ Log.d(Constants.TAG, "fingerprint of downloaded key is the same as " +
+ "the requested fingerprint!");
+ } else {
+ throw new PgpGeneralException("fingerprint of downloaded key is " +
+ "NOT the same as the requested fingerprint!");
+ }
+ }
+
+ // save key bytes in entry object for doing the
+ // actual import afterwards
+ entry.setBytes(downloadedKey.getEncoded());
+ }
+
+
+ Intent importIntent = new Intent(this, KeychainIntentService.class);
+ importIntent.setAction(ACTION_IMPORT_KEYRING);
+ Bundle importData = new Bundle();
+ importData.putParcelableArrayList(IMPORT_KEY_LIST, entries);
+ importIntent.putExtra(EXTRA_DATA, importData);
+ importIntent.putExtra(EXTRA_MESSENGER, mMessenger);
+
+ // now import it with this service
+ onHandleIntent(importIntent);
+
+ // result is handled in ACTION_IMPORT_KEYRING
+ } catch (Exception e) {
+ sendErrorToHandler(e);
+ }
+ } else if (ACTION_CERTIFY_KEYRING.equals(action)) {
+ try {
+
+ /* Input */
+ long masterKeyId = data.getLong(CERTIFY_KEY_MASTER_KEY_ID);
+ long pubKeyId = data.getLong(CERTIFY_KEY_PUB_KEY_ID);
+ ArrayList<String> userIds = data.getStringArrayList(CERTIFY_KEY_UIDS);
+
+ /* Operation */
+ String signaturePassphrase = PassphraseCacheService.getCachedPassphrase(this,
+ masterKeyId);
+ if (signaturePassphrase == null) {
+ throw new PgpGeneralException("Unable to obtain passphrase");
+ }
+
+ PgpKeyOperation keyOperation = new PgpKeyOperation(new ProgressScaler(this, 0, 100, 100));
+ PGPPublicKeyRing publicRing = ProviderHelper.getPGPPublicKeyRing(this, pubKeyId);
+ PGPPublicKey publicKey = publicRing.getPublicKey(pubKeyId);
+ PGPSecretKey certificationKey = PgpKeyHelper.getCertificationKey(this,
+ masterKeyId);
+ publicKey = keyOperation.certifyKey(certificationKey, publicKey,
+ userIds, signaturePassphrase);
+ publicRing = PGPPublicKeyRing.insertPublicKey(publicRing, publicKey);
+
+ // store the signed key in our local cache
+ PgpImportExport pgpImportExport = new PgpImportExport(this, null);
+ int retval = pgpImportExport.storeKeyRingInCache(publicRing);
+ if (retval != Id.return_value.ok && retval != Id.return_value.updated) {
+ throw new PgpGeneralException("Failed to store signed key in local cache");
+ }
+
+ sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY);
+ } catch (Exception e) {
+ sendErrorToHandler(e);
+ }
+ }
+ }
+
+ private void sendErrorToHandler(Exception e) {
+ // Service was canceled. Do not send error to handler.
+ if (this.mIsCanceled) {
+ return;
+ }
+ // contextualize the exception, if necessary
+ if (e instanceof PgpGeneralMsgIdException) {
+ e = ((PgpGeneralMsgIdException) e).getContextualized(this);
+ }
+ Log.e(Constants.TAG, "ApgService Exception: ", e);
+ e.printStackTrace();
+
+ Bundle data = new Bundle();
+ data.putString(KeychainIntentServiceHandler.DATA_ERROR, e.getMessage());
+ sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_EXCEPTION, null, data);
+ }
+
+ private void sendMessageToHandler(Integer arg1, Integer arg2, Bundle data) {
+ // Service was canceled. Do not send message to handler.
+ if (this.mIsCanceled) {
+ return;
+ }
+ Message msg = Message.obtain();
+ msg.arg1 = arg1;
+ 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(Integer arg1, Bundle data) {
+ sendMessageToHandler(arg1, null, data);
+ }
+
+ private void sendMessageToHandler(Integer arg1) {
+ sendMessageToHandler(arg1, null, null);
+ }
+
+ /**
+ * Set progressDialogUpdater 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 progressDialogUpdater=" + progress + ", max="
+ + max);
+
+ Bundle data = new Bundle();
+ if (message != null) {
+ data.putString(KeychainIntentServiceHandler.DATA_MESSAGE, message);
+ }
+ data.putInt(KeychainIntentServiceHandler.DATA_PROGRESS, progress);
+ data.putInt(KeychainIntentServiceHandler.DATA_PROGRESS_MAX, max);
+
+ sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_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 boolean hasServiceStopped() {
+ return mIsCanceled;
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentServiceHandler.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentServiceHandler.java
new file mode 100644
index 000000000..92d012c80
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentServiceHandler.java
@@ -0,0 +1,130 @@
+/*
+ * 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.Activity;
+import android.content.DialogInterface.OnCancelListener;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentManager;
+import android.widget.Toast;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
+
+public class KeychainIntentServiceHandler extends Handler {
+
+ // possible messages send from this service to handler on ui
+ public static final int MESSAGE_OKAY = 1;
+ public static final int MESSAGE_EXCEPTION = 2;
+ public static final int MESSAGE_UPDATE_PROGRESS = 3;
+
+ // possible data keys for messages
+ public static final String DATA_ERROR = "error";
+ public static final String DATA_PROGRESS = "progress";
+ public static final String DATA_PROGRESS_MAX = "max";
+ public static final String DATA_MESSAGE = "message";
+ public static final String DATA_MESSAGE_ID = "message_id";
+
+ Activity mActivity;
+ ProgressDialogFragment mProgressDialogFragment;
+
+ public KeychainIntentServiceHandler(Activity activity) {
+ this.mActivity = activity;
+ }
+
+ public KeychainIntentServiceHandler(Activity activity,
+ ProgressDialogFragment progressDialogFragment) {
+ this.mActivity = activity;
+ this.mProgressDialogFragment = progressDialogFragment;
+ }
+
+ public KeychainIntentServiceHandler(Activity activity, String progressDialogMessage,
+ int progressDialogStyle) {
+ this(activity, progressDialogMessage, progressDialogStyle, false, null);
+ }
+
+ public KeychainIntentServiceHandler(Activity activity, String progressDialogMessage,
+ int progressDialogStyle, boolean cancelable,
+ OnCancelListener onCancelListener) {
+ this.mActivity = activity;
+ this.mProgressDialogFragment = ProgressDialogFragment.newInstance(
+ progressDialogMessage,
+ progressDialogStyle,
+ cancelable,
+ onCancelListener);
+ }
+
+ public void showProgressDialog(FragmentActivity activity) {
+ // TODO: This is a hack!, see
+ // http://stackoverflow.com/questions/10114324/show-dialogfragment-from-onactivityresult
+ final FragmentManager manager = activity.getSupportFragmentManager();
+ Handler handler = new Handler();
+ handler.post(new Runnable() {
+ public void run() {
+ mProgressDialogFragment.show(manager, "progressDialog");
+ }
+ });
+ }
+
+ @Override
+ public void handleMessage(Message message) {
+ Bundle data = message.getData();
+
+ switch (message.arg1) {
+ case MESSAGE_OKAY:
+ mProgressDialogFragment.dismissAllowingStateLoss();
+
+ break;
+
+ case MESSAGE_EXCEPTION:
+ mProgressDialogFragment.dismissAllowingStateLoss();
+
+ // show error from service
+ if (data.containsKey(DATA_ERROR)) {
+ Toast.makeText(mActivity,
+ mActivity.getString(R.string.error_message, data.getString(DATA_ERROR)),
+ Toast.LENGTH_SHORT).show();
+ }
+
+ break;
+
+ case MESSAGE_UPDATE_PROGRESS:
+ if (data.containsKey(DATA_PROGRESS) && data.containsKey(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));
+ } 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));
+ }
+ }
+
+ break;
+
+ default:
+ break;
+ }
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java
new file mode 100644
index 000000000..962b304c7
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java
@@ -0,0 +1,380 @@
+/*
+ * 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.AlarmManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.support.v4.util.LongSparseArray;
+import android.util.Log;
+
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPPrivateKey;
+import org.spongycastle.openpgp.PGPSecretKey;
+import org.spongycastle.openpgp.PGPSecretKeyRing;
+import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
+import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.Id;
+import org.sufficientlysecure.keychain.helper.Preferences;
+import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
+import org.sufficientlysecure.keychain.provider.KeychainContract;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+
+import java.util.Date;
+import java.util.Iterator;
+
+/**
+ * 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
+ * convenience.
+ */
+public class PassphraseCacheService extends Service {
+ public static final String TAG = Constants.TAG + ": PassphraseCacheService";
+
+ public static final String ACTION_PASSPHRASE_CACHE_ADD = Constants.INTENT_PREFIX
+ + "PASSPHRASE_CACHE_ADD";
+ public static final String ACTION_PASSPHRASE_CACHE_GET = Constants.INTENT_PREFIX
+ + "PASSPHRASE_CACHE_GET";
+
+ public static final String BROADCAST_ACTION_PASSPHRASE_CACHE_SERVICE = Constants.INTENT_PREFIX
+ + "PASSPHRASE_CACHE_BROADCAST";
+
+ public static final String EXTRA_TTL = "ttl";
+ public static final String EXTRA_KEY_ID = "key_id";
+ public static final String EXTRA_PASSPHRASE = "passphrase";
+ public static final String EXTRA_MESSENGER = "messenger";
+
+ private static final int REQUEST_ID = 0;
+ private static final long DEFAULT_TTL = 15;
+
+ private BroadcastReceiver mIntentReceiver;
+
+ private LongSparseArray<String> mPassphraseCache = new LongSparseArray<String>();
+
+ Context mContext;
+
+ /**
+ * This caches a new passphrase in memory by sending a new command to the service. An android
+ * service is only run once. Thus, when the service is already started, new commands just add
+ * new events to the alarm manager for new passphrases to let them timeout in the future.
+ *
+ * @param context
+ * @param keyId
+ * @param passphrase
+ */
+ public static void addCachedPassphrase(Context context, long keyId, String passphrase) {
+ Log.d(TAG, "cacheNewPassphrase() for " + keyId);
+
+ Intent intent = new Intent(context, PassphraseCacheService.class);
+ intent.setAction(ACTION_PASSPHRASE_CACHE_ADD);
+ intent.putExtra(EXTRA_TTL, Preferences.getPreferences(context).getPassphraseCacheTtl());
+ intent.putExtra(EXTRA_PASSPHRASE, passphrase);
+ intent.putExtra(EXTRA_KEY_ID, keyId);
+
+ context.startService(intent);
+ }
+
+ /**
+ * Gets a cached passphrase from memory by sending an intent to the service. This method is
+ * designed to wait until the service returns the passphrase.
+ *
+ * @param context
+ * @param keyId
+ * @return passphrase or null (if no passphrase is cached for this keyId)
+ */
+ public static String getCachedPassphrase(Context context, long keyId) {
+ Log.d(TAG, "getCachedPassphrase() get masterKeyId for " + keyId);
+
+ Intent intent = new Intent(context, PassphraseCacheService.class);
+ intent.setAction(ACTION_PASSPHRASE_CACHE_GET);
+
+ final Object mutex = new Object();
+ final Bundle returnBundle = new Bundle();
+
+ HandlerThread handlerThread = new HandlerThread("getPassphraseThread");
+ handlerThread.start();
+ Handler returnHandler = new Handler(handlerThread.getLooper()) {
+ @Override
+ public void handleMessage(Message message) {
+ if (message.obj != null) {
+ String passphrase = ((Bundle) message.obj).getString(EXTRA_PASSPHRASE);
+ returnBundle.putString(EXTRA_PASSPHRASE, passphrase);
+ }
+ synchronized (mutex) {
+ mutex.notify();
+ }
+ // quit handlerThread
+ getLooper().quit();
+ }
+ };
+
+ // Create a new Messenger for the communication back
+ Messenger messenger = new Messenger(returnHandler);
+ intent.putExtra(EXTRA_KEY_ID, keyId);
+ intent.putExtra(EXTRA_MESSENGER, messenger);
+ // send intent to this service
+ context.startService(intent);
+
+ // Wait on mutex until passphrase is returned to handlerThread
+ synchronized (mutex) {
+ try {
+ mutex.wait(3000);
+ } catch (InterruptedException e) {
+ }
+ }
+
+ if (returnBundle.containsKey(EXTRA_PASSPHRASE)) {
+ return returnBundle.getString(EXTRA_PASSPHRASE);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Internal implementation to get cached passphrase.
+ *
+ * @param keyId
+ * @return
+ */
+ private String getCachedPassphraseImpl(long keyId) {
+ Log.d(TAG, "getCachedPassphraseImpl() get masterKeyId for " + keyId);
+
+ // try to get master key id which is used as an identifier for cached passphrases
+ long masterKeyId = keyId;
+ if (masterKeyId != Id.key.symmetric) {
+ masterKeyId = ProviderHelper.getMasterKeyId(this,
+ KeychainContract.KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(Long.toString(keyId)));
+ // Failure
+ if(masterKeyId == 0)
+ return null;
+ }
+ Log.d(TAG, "getCachedPassphraseImpl() for masterKeyId " + masterKeyId);
+
+ // get cached passphrase
+ String cachedPassphrase = mPassphraseCache.get(masterKeyId);
+ if (cachedPassphrase == null) {
+ // if key has no passphrase -> cache and return empty passphrase
+ if (!hasPassphrase(this, masterKeyId)) {
+ Log.d(Constants.TAG, "Key has no passphrase! Caches and returns empty passphrase!");
+
+ addCachedPassphrase(this, masterKeyId, "");
+ return "";
+ } else {
+ return null;
+ }
+ }
+ // set it again to reset the cache life cycle
+ Log.d(TAG, "Cache passphrase again when getting it!");
+ addCachedPassphrase(this, masterKeyId, cachedPassphrase);
+
+ return cachedPassphrase;
+ }
+
+ /**
+ * Checks if key has a passphrase.
+ *
+ * @param secretKeyId
+ * @return true if it has a passphrase
+ */
+ public static boolean hasPassphrase(Context context, long secretKeyId) {
+ // check if the key has no passphrase
+ try {
+ PGPSecretKeyRing secRing = ProviderHelper.getPGPSecretKeyRing(context, secretKeyId);
+ PGPSecretKey secretKey = null;
+ boolean foundValidKey = false;
+ for (Iterator keys = secRing.getSecretKeys(); keys.hasNext(); ) {
+ secretKey = (PGPSecretKey) keys.next();
+ if (!secretKey.isPrivateKeyEmpty()) {
+ foundValidKey = true;
+ break;
+ }
+ }
+
+ if (!foundValidKey) {
+ return false;
+ }
+ PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
+ "SC").build("".toCharArray());
+ PGPPrivateKey testKey = secretKey.extractPrivateKey(keyDecryptor);
+ if (testKey != null) {
+ return false;
+ }
+ } catch (PGPException e) {
+ // silently catch
+ }
+
+ return true;
+ }
+
+ /**
+ * Register BroadcastReceiver that is unregistered when service is destroyed. This
+ * BroadcastReceiver hears on intents with ACTION_PASSPHRASE_CACHE_SERVICE to then timeout
+ * specific passphrases in memory.
+ */
+ private void registerReceiver() {
+ if (mIntentReceiver == null) {
+ mIntentReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+
+ Log.d(TAG, "Received broadcast...");
+
+ if (action.equals(BROADCAST_ACTION_PASSPHRASE_CACHE_SERVICE)) {
+ long keyId = intent.getLongExtra(EXTRA_KEY_ID, -1);
+ timeout(context, keyId);
+ }
+ }
+ };
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(BROADCAST_ACTION_PASSPHRASE_CACHE_SERVICE);
+ registerReceiver(mIntentReceiver, filter);
+ }
+ }
+
+ /**
+ * Build pending intent that is executed by alarm manager to time out a specific passphrase
+ *
+ * @param context
+ * @param keyId
+ * @return
+ */
+ private static PendingIntent buildIntent(Context context, long keyId) {
+ Intent intent = new Intent(BROADCAST_ACTION_PASSPHRASE_CACHE_SERVICE);
+ intent.putExtra(EXTRA_KEY_ID, keyId);
+ PendingIntent sender = PendingIntent.getBroadcast(context, REQUEST_ID, intent,
+ PendingIntent.FLAG_CANCEL_CURRENT);
+
+ return sender;
+ }
+
+ /**
+ * Executed when service is started by intent
+ */
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ Log.d(TAG, "onStartCommand()");
+
+ // register broadcastreceiver
+ registerReceiver();
+
+ if (intent != null && intent.getAction() != null) {
+ if (ACTION_PASSPHRASE_CACHE_ADD.equals(intent.getAction())) {
+ long ttl = intent.getLongExtra(EXTRA_TTL, DEFAULT_TTL);
+ long keyId = intent.getLongExtra(EXTRA_KEY_ID, -1);
+ String passphrase = intent.getStringExtra(EXTRA_PASSPHRASE);
+
+ Log.d(TAG,
+ "Received ACTION_PASSPHRASE_CACHE_ADD intent in onStartCommand() with keyId: "
+ + keyId + ", ttl: " + ttl);
+
+ // add keyId and passphrase to memory
+ mPassphraseCache.put(keyId, passphrase);
+
+ if (ttl > 0) {
+ // register new alarm with keyId for this passphrase
+ long triggerTime = new Date().getTime() + (ttl * 1000);
+ AlarmManager am = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
+ am.set(AlarmManager.RTC_WAKEUP, triggerTime, buildIntent(this, keyId));
+ }
+ } else if (ACTION_PASSPHRASE_CACHE_GET.equals(intent.getAction())) {
+ long keyId = intent.getLongExtra(EXTRA_KEY_ID, -1);
+ Messenger messenger = intent.getParcelableExtra(EXTRA_MESSENGER);
+
+ String passphrase = getCachedPassphraseImpl(keyId);
+
+ Message msg = Message.obtain();
+ Bundle bundle = new Bundle();
+ bundle.putString(EXTRA_PASSPHRASE, passphrase);
+ msg.obj = bundle;
+ try {
+ messenger.send(msg);
+ } catch (RemoteException e) {
+ Log.e(Constants.TAG, "Sending message failed", e);
+ }
+ } else {
+ Log.e(Constants.TAG, "Intent or Intent Action not supported!");
+ }
+ }
+
+ return START_STICKY;
+ }
+
+ /**
+ * Called when one specific passphrase for keyId timed out
+ *
+ * @param context
+ * @param keyId
+ */
+ private void timeout(Context context, long keyId) {
+ // remove passphrase corresponding to keyId from memory
+ mPassphraseCache.remove(keyId);
+
+ Log.d(TAG, "Timeout of keyId " + keyId + ", removed from memory!");
+
+ // stop whole service if no cached passphrases remaining
+ if (mPassphraseCache.size() == 0) {
+ Log.d(TAG, "No passphrases remaining in memory, stopping service!");
+ stopSelf();
+ }
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mContext = this;
+ Log.d(Constants.TAG, "PassphraseCacheService, onCreate()");
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ Log.d(Constants.TAG, "PassphraseCacheService, onDestroy()");
+
+ unregisterReceiver(mIntentReceiver);
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mBinder;
+ }
+
+ public class PassphraseCacheBinder extends Binder {
+ public PassphraseCacheService getService() {
+ return PassphraseCacheService.this;
+ }
+ }
+
+ private final IBinder mBinder = new PassphraseCacheBinder();
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java
new file mode 100644
index 000000000..7c2dcf2c1
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2014 Ash Hughes <ashes-iontach@hotmail.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.spongycastle.openpgp.PGPSecretKey;
+import org.sufficientlysecure.keychain.pgp.PgpConversionHelper;
+
+import java.util.ArrayList;
+import java.util.GregorianCalendar;
+
+public class SaveKeyringParcel implements Parcelable {
+
+ public ArrayList<String> userIDs;
+ public ArrayList<String> originalIDs;
+ public ArrayList<String> deletedIDs;
+ public boolean[] newIDs;
+ public boolean primaryIDChanged;
+ public boolean[] moddedKeys;
+ public ArrayList<PGPSecretKey> deletedKeys;
+ public ArrayList<GregorianCalendar> keysExpiryDates;
+ public ArrayList<Integer> keysUsages;
+ public String newPassphrase;
+ public String oldPassphrase;
+ public boolean[] newKeys;
+ public ArrayList<PGPSecretKey> keys;
+ public String originalPrimaryID;
+
+ public SaveKeyringParcel() {}
+
+ private SaveKeyringParcel(Parcel source) {
+ userIDs = (ArrayList<String>) source.readSerializable();
+ originalIDs = (ArrayList<String>) source.readSerializable();
+ deletedIDs = (ArrayList<String>) source.readSerializable();
+ newIDs = source.createBooleanArray();
+ primaryIDChanged = source.readByte() != 0;
+ moddedKeys = source.createBooleanArray();
+ byte[] tmp = source.createByteArray();
+ if (tmp == null) {
+ deletedKeys = null;
+ } else {
+ deletedKeys = PgpConversionHelper.BytesToPGPSecretKeyList(tmp);
+ }
+ keysExpiryDates = (ArrayList<GregorianCalendar>) source.readSerializable();
+ keysUsages = source.readArrayList(Integer.class.getClassLoader());
+ newPassphrase = source.readString();
+ oldPassphrase = source.readString();
+ newKeys = source.createBooleanArray();
+ keys = PgpConversionHelper.BytesToPGPSecretKeyList(source.createByteArray());
+ originalPrimaryID = source.readString();
+ }
+
+ @Override
+ public void writeToParcel(Parcel destination, int flags) {
+ destination.writeSerializable(userIDs); //might not be the best method to store.
+ destination.writeSerializable(originalIDs);
+ destination.writeSerializable(deletedIDs);
+ destination.writeBooleanArray(newIDs);
+ destination.writeByte((byte) (primaryIDChanged ? 1 : 0));
+ destination.writeBooleanArray(moddedKeys);
+ byte[] tmp = null;
+ if (deletedKeys.size() != 0) {
+ tmp = PgpConversionHelper.PGPSecretKeyArrayListToBytes(deletedKeys);
+ }
+ destination.writeByteArray(tmp);
+ destination.writeSerializable(keysExpiryDates);
+ destination.writeList(keysUsages);
+ destination.writeString(newPassphrase);
+ destination.writeString(oldPassphrase);
+ destination.writeBooleanArray(newKeys);
+ destination.writeByteArray(PgpConversionHelper.PGPSecretKeyArrayListToBytes(keys));
+ destination.writeString(originalPrimaryID);
+ }
+
+ public static final Creator<SaveKeyringParcel> CREATOR = new Creator<SaveKeyringParcel>() {
+ public SaveKeyringParcel createFromParcel(final Parcel source) {
+ return new SaveKeyringParcel(source);
+ }
+
+ public SaveKeyringParcel[] newArray(final int size) {
+ return new SaveKeyringParcel[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java
new file mode 100644
index 000000000..7027c114e
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java
@@ -0,0 +1,388 @@
+/*
+ * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2011 Senecaso
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.sufficientlysecure.keychain.ui;
+
+import android.app.ProgressDialog;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+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.v7.app.ActionBar;
+import android.support.v7.app.ActionBarActivity;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.ArrayAdapter;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+import android.widget.ListView;
+import android.widget.Spinner;
+import android.widget.TextView;
+import android.widget.Toast;
+import com.beardedhen.androidbootstrap.BootstrapButton;
+import org.spongycastle.openpgp.PGPPublicKeyRing;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.helper.Preferences;
+import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
+import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
+import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.service.KeychainIntentService;
+import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
+import org.sufficientlysecure.keychain.service.PassphraseCacheService;
+import org.sufficientlysecure.keychain.ui.adapter.ViewKeyUserIdsAdapter;
+import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
+import org.sufficientlysecure.keychain.util.Log;
+
+import java.util.ArrayList;
+
+/**
+ * Signs the specified public key with the specified secret master key
+ */
+public class CertifyKeyActivity extends ActionBarActivity implements
+ SelectSecretKeyLayoutFragment.SelectSecretKeyCallback, LoaderManager.LoaderCallbacks<Cursor> {
+ private BootstrapButton mSignButton;
+ private CheckBox mUploadKeyCheckbox;
+ private Spinner mSelectKeyserverSpinner;
+
+ private SelectSecretKeyLayoutFragment mSelectKeyFragment;
+
+ private Uri mDataUri;
+ private long mPubKeyId = 0;
+ private long mMasterKeyId = 0;
+
+ private ListView mUserIds;
+ private ViewKeyUserIdsAdapter mUserIdsAdapter;
+
+ private static final int LOADER_ID_KEYRING = 0;
+ private static final int LOADER_ID_USER_IDS = 1;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.certify_key_activity);
+
+ final ActionBar actionBar = getSupportActionBar();
+ actionBar.setDisplayShowTitleEnabled(true);
+ actionBar.setDisplayHomeAsUpEnabled(false);
+ actionBar.setHomeButtonEnabled(false);
+
+ mSelectKeyFragment = (SelectSecretKeyLayoutFragment) getSupportFragmentManager()
+ .findFragmentById(R.id.sign_key_select_key_fragment);
+ mSelectKeyFragment.setCallback(this);
+ mSelectKeyFragment.setFilterCertify(true);
+
+ mSelectKeyserverSpinner = (Spinner) findViewById(R.id.sign_key_keyserver);
+ ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
+ android.R.layout.simple_spinner_item, Preferences.getPreferences(this)
+ .getKeyServers());
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ mSelectKeyserverSpinner.setAdapter(adapter);
+
+ mUploadKeyCheckbox = (CheckBox) findViewById(R.id.sign_key_upload_checkbox);
+ if (!mUploadKeyCheckbox.isChecked()) {
+ mSelectKeyserverSpinner.setEnabled(false);
+ } else {
+ mSelectKeyserverSpinner.setEnabled(true);
+ }
+
+ mUploadKeyCheckbox.setOnCheckedChangeListener(new OnCheckedChangeListener() {
+
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ if (!isChecked) {
+ mSelectKeyserverSpinner.setEnabled(false);
+ } else {
+ mSelectKeyserverSpinner.setEnabled(true);
+ }
+ }
+ });
+
+ mSignButton = (BootstrapButton) findViewById(R.id.sign_key_sign_button);
+ mSignButton.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ if (mPubKeyId != 0) {
+ if (mMasterKeyId == 0) {
+ mSelectKeyFragment.setError(getString(R.string.select_key_to_sign));
+ } else {
+ initiateSigning();
+ }
+ }
+ }
+ });
+
+ mDataUri = getIntent().getData();
+ if (mDataUri == null) {
+ Log.e(Constants.TAG, "Intent data missing. Should be Uri of key!");
+ finish();
+ return;
+ }
+ Log.e(Constants.TAG, "uri: " + mDataUri);
+
+ mUserIds = (ListView) findViewById(R.id.user_ids);
+
+ mUserIdsAdapter = new ViewKeyUserIdsAdapter(this, null, 0, true);
+ mUserIds.setAdapter(mUserIdsAdapter);
+ mUserIds.setOnItemClickListener(mUserIdsAdapter);
+
+ getSupportLoaderManager().initLoader(LOADER_ID_KEYRING, null, this);
+ getSupportLoaderManager().initLoader(LOADER_ID_USER_IDS, null, this);
+
+ }
+
+ static final String USER_IDS_SELECTION = UserIds.IS_REVOKED + " = 0";
+
+ static final String[] KEYRING_PROJECTION =
+ new String[] {
+ KeyRings._ID,
+ KeyRings.MASTER_KEY_ID,
+ KeyRings.FINGERPRINT,
+ KeyRings.USER_ID,
+ };
+ static final int INDEX_MASTER_KEY_ID = 1;
+ static final int INDEX_FINGERPRINT = 2;
+ static final int INDEX_USER_ID = 3;
+
+ @Override
+ public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+ switch(id) {
+ case LOADER_ID_KEYRING: {
+ Uri uri = KeyRings.buildUnifiedKeyRingUri(mDataUri);
+ return new CursorLoader(this, uri, KEYRING_PROJECTION, null, null, null);
+ }
+ case LOADER_ID_USER_IDS: {
+ Uri uri = UserIds.buildUserIdsUri(mDataUri);
+ return new CursorLoader(this, uri,
+ ViewKeyUserIdsAdapter.USER_IDS_PROJECTION, USER_IDS_SELECTION, null, null);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+ switch(loader.getId()) {
+ case LOADER_ID_KEYRING:
+ // the first key here is our master key
+ if (data.moveToFirst()) {
+ // TODO: put findViewById in onCreate!
+ mPubKeyId = data.getLong(INDEX_MASTER_KEY_ID);
+ String keyIdStr = PgpKeyHelper.convertKeyIdToHexShort(mPubKeyId);
+ ((TextView) findViewById(R.id.key_id)).setText(keyIdStr);
+
+ String mainUserId = data.getString(INDEX_USER_ID);
+ ((TextView) findViewById(R.id.main_user_id)).setText(mainUserId);
+
+ byte[] fingerprintBlob = data.getBlob(INDEX_FINGERPRINT);
+ String fingerprint = PgpKeyHelper.convertFingerprintToHex(fingerprintBlob);
+ ((TextView) findViewById(R.id.fingerprint))
+ .setText(PgpKeyHelper.colorizeFingerprint(fingerprint));
+ }
+ break;
+ case LOADER_ID_USER_IDS:
+ mUserIdsAdapter.swapCursor(data);
+ break;
+ }
+ }
+
+ @Override
+ public void onLoaderReset(Loader<Cursor> loader) {
+ switch(loader.getId()) {
+ case LOADER_ID_USER_IDS:
+ mUserIdsAdapter.swapCursor(null);
+ break;
+ }
+ }
+
+ /**
+ * handles the UI bits of the signing process on the UI thread
+ */
+ private void initiateSigning() {
+ PGPPublicKeyRing pubring = ProviderHelper.getPGPPublicKeyRing(this, mPubKeyId);
+ if (pubring != null) {
+ // if we have already signed this key, dont bother doing it again
+ boolean alreadySigned = false;
+
+ /* todo: reconsider this at a later point when certs are in the db
+ @SuppressWarnings("unchecked")
+ Iterator<PGPSignature> itr = pubring.getPublicKey(mPubKeyId).getSignatures();
+ while (itr.hasNext()) {
+ PGPSignature sig = itr.next();
+ if (sig.getKeyID() == mMasterKeyId) {
+ alreadySigned = true;
+ break;
+ }
+ }
+ */
+
+ if (!alreadySigned) {
+ /*
+ * get the user's passphrase for this key (if required)
+ */
+ String passphrase = PassphraseCacheService.getCachedPassphrase(this, mMasterKeyId);
+ if (passphrase == null) {
+ PassphraseDialogFragment.show(this, mMasterKeyId,
+ new Handler() {
+ @Override
+ public void handleMessage(Message message) {
+ if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) {
+ startSigning();
+ }
+ }
+ });
+ // bail out; need to wait until the user has entered the passphrase before trying again
+ return;
+ } else {
+ startSigning();
+ }
+ } else {
+ Toast.makeText(this, R.string.key_has_already_been_signed, Toast.LENGTH_SHORT)
+ .show();
+
+ setResult(RESULT_CANCELED);
+ finish();
+ }
+ }
+ }
+
+ /**
+ * kicks off the actual signing process on a background thread
+ */
+ private void startSigning() {
+
+ // Bail out if there is not at least one user id selected
+ ArrayList<String> userIds = mUserIdsAdapter.getSelectedUserIds();
+ if (userIds.isEmpty()) {
+ Toast.makeText(CertifyKeyActivity.this, "No User IDs to sign selected!",
+ Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ // Send all information needed to service to sign key in other thread
+ Intent intent = new Intent(this, KeychainIntentService.class);
+
+ intent.setAction(KeychainIntentService.ACTION_CERTIFY_KEYRING);
+
+ // fill values for this action
+ Bundle data = new Bundle();
+
+ data.putLong(KeychainIntentService.CERTIFY_KEY_MASTER_KEY_ID, mMasterKeyId);
+ data.putLong(KeychainIntentService.CERTIFY_KEY_PUB_KEY_ID, mPubKeyId);
+ data.putStringArrayList(KeychainIntentService.CERTIFY_KEY_UIDS, userIds);
+
+ intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
+
+ // Message is received after signing is done in KeychainIntentService
+ KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this,
+ getString(R.string.progress_signing), ProgressDialog.STYLE_SPINNER) {
+ public void handleMessage(Message message) {
+ // handle messages by standard KeychainIntentServiceHandler first
+ super.handleMessage(message);
+
+ if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
+
+ Toast.makeText(CertifyKeyActivity.this, R.string.key_sign_success,
+ Toast.LENGTH_SHORT).show();
+
+ // check if we need to send the key to the server or not
+ if (mUploadKeyCheckbox.isChecked()) {
+ // upload the newly signed key to the keyserver
+ uploadKey();
+ } else {
+ setResult(RESULT_OK);
+ 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);
+
+ // start service with intent
+ startService(intent);
+ }
+
+ 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);
+
+ // set data uri as path to keyring
+ intent.setData(mDataUri);
+
+ // fill values for this action
+ Bundle data = new Bundle();
+
+ Spinner keyServer = (Spinner) findViewById(R.id.sign_key_keyserver);
+ String server = (String) keyServer.getSelectedItem();
+ data.putString(KeychainIntentService.UPLOAD_KEY_SERVER, server);
+
+ intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
+
+ // Message is received after uploading is done in KeychainIntentService
+ KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this,
+ getString(R.string.progress_exporting), ProgressDialog.STYLE_HORIZONTAL) {
+ public void handleMessage(Message message) {
+ // handle messages by standard KeychainIntentServiceHandler first
+ super.handleMessage(message);
+
+ if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
+ Toast.makeText(CertifyKeyActivity.this, R.string.key_send_success,
+ Toast.LENGTH_SHORT).show();
+
+ setResult(RESULT_OK);
+ 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);
+
+ // start service with intent
+ startService(intent);
+ }
+
+ /**
+ * callback from select key fragment
+ */
+ @Override
+ public void onKeySelected(long secretKeyId) {
+ mMasterKeyId = secretKeyId;
+ }
+}
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..8533e9072
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
+ *
+ * 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;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v4.view.PagerTabStrip;
+import android.support.v4.view.ViewPager;
+import android.widget.Toast;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.helper.ActionBarHelper;
+import org.sufficientlysecure.keychain.helper.FileHelper;
+import org.sufficientlysecure.keychain.pgp.PgpHelper;
+import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter;
+import org.sufficientlysecure.keychain.util.Log;
+
+import java.util.regex.Matcher;
+
+public class DecryptActivity extends DrawerActivity {
+
+ /* Intents */
+ public static final String ACTION_DECRYPT = Constants.INTENT_PREFIX + "DECRYPT";
+
+ /* EXTRA keys for input */
+ public static final String EXTRA_TEXT = "text";
+
+ ViewPager mViewPager;
+ PagerTabStrip mPagerTabStrip;
+ PagerTabStripAdapter mTabsAdapter;
+
+ Bundle mMessageFragmentBundle = new Bundle();
+ Bundle mFileFragmentBundle = new Bundle();
+ int mSwitchToTab = PAGER_TAB_MESSAGE;
+
+ private static final int PAGER_TAB_MESSAGE = 0;
+ private static final int PAGER_TAB_FILE = 1;
+
+ private void initView() {
+ mViewPager = (ViewPager) findViewById(R.id.decrypt_pager);
+ mPagerTabStrip = (PagerTabStrip) findViewById(R.id.decrypt_pager_tab_strip);
+
+ mTabsAdapter = new PagerTabStripAdapter(this);
+ mViewPager.setAdapter(mTabsAdapter);
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.decrypt_activity);
+
+ // set actionbar without home button if called from another app
+ ActionBarHelper.setBackButton(this);
+
+ initView();
+
+ setupDrawerNavigation(savedInstanceState);
+
+ // Handle intent actions, maybe changes the bundles
+ handleActions(getIntent());
+
+ mTabsAdapter.addTab(DecryptMessageFragment.class,
+ mMessageFragmentBundle, getString(R.string.label_message));
+ mTabsAdapter.addTab(DecryptFileFragment.class,
+ mFileFragmentBundle, getString(R.string.label_file));
+ mViewPager.setCurrentItem(mSwitchToTab);
+ }
+
+
+ /**
+ * Handles all actions with this intent
+ *
+ * @param intent
+ */
+ private void handleActions(Intent intent) {
+ String action = intent.getAction();
+ Bundle extras = intent.getExtras();
+ String type = intent.getType();
+ Uri uri = intent.getData();
+
+ if (extras == null) {
+ extras = new Bundle();
+ }
+
+ /*
+ * Android's Action
+ */
+ if (Intent.ACTION_SEND.equals(action) && type != null) {
+ // When sending to Keychain Decrypt via share menu
+ if ("text/plain".equals(type)) {
+ // Plain text
+ String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);
+ if (sharedText != null) {
+ // handle like normal text decryption, override action and extras to later
+ // executeServiceMethod ACTION_DECRYPT in main actions
+ extras.putString(EXTRA_TEXT, sharedText);
+ action = ACTION_DECRYPT;
+ }
+ } else {
+ // Binary via content provider (could also be files)
+ // override uri to get stream from send
+ uri = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM);
+ action = ACTION_DECRYPT;
+ }
+ } else if (Intent.ACTION_VIEW.equals(action)) {
+ // Android's Action when opening file associated to Keychain (see AndroidManifest.xml)
+
+ // override action
+ action = ACTION_DECRYPT;
+ }
+
+ String textData = extras.getString(EXTRA_TEXT);
+
+ /**
+ * Main Actions
+ */
+ if (ACTION_DECRYPT.equals(action) && textData != null) {
+ Log.d(Constants.TAG, "textData not null, matching text ...");
+ Matcher matcher = PgpHelper.PGP_MESSAGE.matcher(textData);
+ if (matcher.matches()) {
+ Log.d(Constants.TAG, "PGP_MESSAGE matched");
+ textData = matcher.group(1);
+ // replace non breakable spaces
+ textData = textData.replaceAll("\\xa0", " ");
+
+ mMessageFragmentBundle.putString(DecryptMessageFragment.ARG_CIPHERTEXT, textData);
+ mSwitchToTab = PAGER_TAB_MESSAGE;
+ } else {
+ matcher = PgpHelper.PGP_CLEARTEXT_SIGNATURE.matcher(textData);
+ if (matcher.matches()) {
+ Log.d(Constants.TAG, "PGP_CLEARTEXT_SIGNATURE matched");
+ textData = matcher.group(1);
+ // replace non breakable spaces
+ textData = textData.replaceAll("\\xa0", " ");
+
+ mMessageFragmentBundle.putString(DecryptMessageFragment.ARG_CIPHERTEXT, textData);
+ mSwitchToTab = PAGER_TAB_MESSAGE;
+ } else {
+ Log.d(Constants.TAG, "Nothing matched!");
+ }
+ }
+ } else if (ACTION_DECRYPT.equals(action) && uri != null) {
+ // get file path from uri
+ String path = FileHelper.getPath(this, uri);
+
+ if (path != null) {
+ mFileFragmentBundle.putString(DecryptFileFragment.ARG_FILENAME, path);
+ mSwitchToTab = PAGER_TAB_FILE;
+ } else {
+ Log.e(Constants.TAG,
+ "Direct binary data without actual file in filesystem is not supported. " +
+ "Please use the Remote Service API!");
+ Toast.makeText(this, R.string.error_only_files_are_supported, Toast.LENGTH_LONG)
+ .show();
+ // end activity
+ finish();
+ }
+ } else {
+ Log.e(Constants.TAG,
+ "Include the extra 'text' or an Uri with setData() in your Intent!");
+ }
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFileFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFileFragment.java
new file mode 100644
index 000000000..492c0cf29
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFileFragment.java
@@ -0,0 +1,261 @@
+/*
+ * 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.app.ProgressDialog;
+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.ViewGroup;
+import android.widget.CheckBox;
+import android.widget.EditText;
+
+import com.beardedhen.androidbootstrap.BootstrapButton;
+import com.devspark.appmsg.AppMsg;
+
+import org.openintents.openpgp.OpenPgpSignatureResult;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.Id;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.helper.FileHelper;
+import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyResult;
+import org.sufficientlysecure.keychain.service.KeychainIntentService;
+import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
+import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment;
+import org.sufficientlysecure.keychain.ui.dialog.FileDialogFragment;
+import org.sufficientlysecure.keychain.util.Log;
+
+import java.io.File;
+
+public class DecryptFileFragment extends DecryptFragment {
+ public static final String ARG_FILENAME = "filename";
+
+ private static final int RESULT_CODE_FILE = 0x00007003;
+
+ // view
+ private EditText mFilename;
+ private CheckBox mDeleteAfter;
+ private BootstrapButton mBrowse;
+ private BootstrapButton mDecryptButton;
+
+ private String mInputFilename = null;
+ private String mOutputFilename = null;
+
+ private FileDialogFragment mFileDialog;
+
+ /**
+ * Inflate the layout for this fragment
+ */
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.decrypt_file_fragment, container, false);
+
+ mFilename = (EditText) view.findViewById(R.id.decrypt_file_filename);
+ mBrowse = (BootstrapButton) view.findViewById(R.id.decrypt_file_browse);
+ mDeleteAfter = (CheckBox) view.findViewById(R.id.decrypt_file_delete_after_decryption);
+ mDecryptButton = (BootstrapButton) view.findViewById(R.id.decrypt_file_action_decrypt);
+ mBrowse.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ FileHelper.openFile(DecryptFileFragment.this, mFilename.getText().toString(), "*/*",
+ RESULT_CODE_FILE);
+ }
+ });
+ mDecryptButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ decryptAction();
+ }
+ });
+
+ return view;
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ String filename = getArguments().getString(ARG_FILENAME);
+ if (filename != null) {
+ mFilename.setText(filename);
+ }
+ }
+
+ private void guessOutputFilename() {
+ mInputFilename = mFilename.getText().toString();
+ File file = new File(mInputFilename);
+ String filename = file.getName();
+ if (filename.endsWith(".asc") || filename.endsWith(".gpg") || filename.endsWith(".pgp")) {
+ filename = filename.substring(0, filename.length() - 4);
+ }
+ mOutputFilename = Constants.Path.APP_DIR + "/" + filename;
+ }
+
+ private void decryptAction() {
+ String currentFilename = mFilename.getText().toString();
+ if (mInputFilename == null || !mInputFilename.equals(currentFilename)) {
+ guessOutputFilename();
+ }
+
+ if (mInputFilename.equals("")) {
+ AppMsg.makeText(getActivity(), R.string.no_file_selected, AppMsg.STYLE_ALERT).show();
+ return;
+ }
+
+ if (mInputFilename.startsWith("file")) {
+ File file = new File(mInputFilename);
+ if (!file.exists() || !file.isFile()) {
+ AppMsg.makeText(
+ getActivity(),
+ getString(R.string.error_message,
+ getString(R.string.error_file_not_found)), AppMsg.STYLE_ALERT)
+ .show();
+ return;
+ }
+ }
+
+ askForOutputFilename();
+ }
+
+ private void askForOutputFilename() {
+ // Message is received after passphrase is cached
+ Handler returnHandler = new Handler() {
+ @Override
+ public void handleMessage(Message message) {
+ if (message.what == FileDialogFragment.MESSAGE_OKAY) {
+ Bundle data = message.getData();
+ mOutputFilename = data.getString(FileDialogFragment.MESSAGE_DATA_FILENAME);
+ decryptStart(null);
+ }
+ }
+ };
+
+ // Create a new Messenger for the communication back
+ Messenger messenger = new Messenger(returnHandler);
+
+ mFileDialog = FileDialogFragment.newInstance(messenger,
+ getString(R.string.title_decrypt_to_file),
+ getString(R.string.specify_file_to_decrypt_to), mOutputFilename, null);
+
+ mFileDialog.show(getActivity().getSupportFragmentManager(), "fileDialog");
+ }
+
+ @Override
+ protected void decryptStart(String passphrase) {
+ Log.d(Constants.TAG, "decryptStart");
+
+ // 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.putInt(KeychainIntentService.TARGET, KeychainIntentService.TARGET_URI);
+
+ Log.d(Constants.TAG, "mInputFilename=" + mInputFilename + ", mOutputFilename="
+ + mOutputFilename);
+
+ data.putString(KeychainIntentService.ENCRYPT_INPUT_FILE, mInputFilename);
+ data.putString(KeychainIntentService.ENCRYPT_OUTPUT_FILE, mOutputFilename);
+
+ data.putString(KeychainIntentService.DECRYPT_PASSPHRASE, passphrase);
+
+ intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
+
+ // Message is received after encrypting is done in KeychainIntentService
+ KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(getActivity(),
+ getString(R.string.progress_decrypting), ProgressDialog.STYLE_HORIZONTAL) {
+ public void handleMessage(Message message) {
+ // handle messages by standard KeychainIntentServiceHandler first
+ super.handleMessage(message);
+
+ if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
+ // get returned data bundle
+ Bundle returnData = message.getData();
+
+ PgpDecryptVerifyResult decryptVerifyResult =
+ returnData.getParcelable(KeychainIntentService.RESULT_DECRYPT_VERIFY_RESULT);
+
+ if (PgpDecryptVerifyResult.KEY_PASSHRASE_NEEDED == decryptVerifyResult.getStatus()) {
+ showPassphraseDialog(decryptVerifyResult.getKeyIdPassphraseNeeded());
+ } else if (PgpDecryptVerifyResult.SYMMETRIC_PASSHRASE_NEEDED ==
+ decryptVerifyResult.getStatus()) {
+ showPassphraseDialog(Id.key.symmetric);
+ } else {
+ AppMsg.makeText(getActivity(), R.string.decryption_successful,
+ AppMsg.STYLE_INFO).show();
+
+ if (mDeleteAfter.isChecked()) {
+ // Create and show dialog to delete original file
+ DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment
+ .newInstance(mInputFilename);
+ deleteFileDialog.show(getActivity().getSupportFragmentManager(), "deleteDialog");
+ }
+
+ OpenPgpSignatureResult signatureResult = decryptVerifyResult.getSignatureResult();
+
+ // display signature result in activity
+ onSignatureResult(signatureResult);
+ }
+ }
+ }
+ };
+
+ // 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 RESULT_CODE_FILE: {
+ if (resultCode == Activity.RESULT_OK && data != null) {
+ try {
+ String path = FileHelper.getPath(getActivity(), data.getData());
+ Log.d(Constants.TAG, "path=" + path);
+
+ mFilename.setText(path);
+ } catch (NullPointerException e) {
+ Log.e(Constants.TAG, "Nullpointer while retrieving path!");
+ }
+ }
+ return;
+ }
+
+ default: {
+ super.onActivityResult(requestCode, resultCode, data);
+
+ break;
+ }
+ }
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java
new file mode 100644
index 000000000..1c465f55c
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java
@@ -0,0 +1,180 @@
+/*
+ * 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.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.support.v4.app.Fragment;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import com.beardedhen.androidbootstrap.BootstrapButton;
+import com.devspark.appmsg.AppMsg;
+
+import org.openintents.openpgp.OpenPgpSignatureResult;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
+import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
+import org.sufficientlysecure.keychain.provider.KeychainContract;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
+
+public class DecryptFragment extends Fragment {
+ private static final int RESULT_CODE_LOOKUP_KEY = 0x00007006;
+
+ protected long mSignatureKeyId = 0;
+
+ protected RelativeLayout mSignatureLayout = null;
+ protected ImageView mSignatureStatusImage = null;
+ protected TextView mUserId = null;
+ protected TextView mUserIdRest = null;
+
+ protected BootstrapButton mLookupKey = null;
+
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ mSignatureLayout = (RelativeLayout) getView().findViewById(R.id.signature);
+ mSignatureStatusImage = (ImageView) getView().findViewById(R.id.ic_signature_status);
+ mUserId = (TextView) getView().findViewById(R.id.mainUserId);
+ mUserIdRest = (TextView) getView().findViewById(R.id.mainUserIdRest);
+ mLookupKey = (BootstrapButton) getView().findViewById(R.id.lookup_key);
+ mLookupKey.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ lookupUnknownKey(mSignatureKeyId);
+ }
+ });
+ mSignatureLayout.setVisibility(View.GONE);
+ mSignatureLayout.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ lookupUnknownKey(mSignatureKeyId);
+ }
+ });
+ }
+
+ private void lookupUnknownKey(long unknownKeyId) {
+ Intent intent = new Intent(getActivity(), ImportKeysActivity.class);
+ intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER);
+ intent.putExtra(ImportKeysActivity.EXTRA_KEY_ID, unknownKeyId);
+ startActivityForResult(intent, RESULT_CODE_LOOKUP_KEY);
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ switch (requestCode) {
+
+ case RESULT_CODE_LOOKUP_KEY: {
+ if (resultCode == Activity.RESULT_OK) {
+ // TODO: generate new OpenPgpSignatureResult and display it
+ }
+ return;
+ }
+
+ default: {
+ super.onActivityResult(requestCode, resultCode, data);
+
+ break;
+ }
+ }
+ }
+
+ protected void onSignatureResult(OpenPgpSignatureResult signatureResult) {
+ mSignatureKeyId = 0;
+ mSignatureLayout.setVisibility(View.GONE);
+ if (signatureResult != null) {
+
+ mSignatureKeyId = signatureResult.getKeyId();
+
+ String userId = signatureResult.getUserId();
+ String[] userIdSplit = PgpKeyHelper.splitUserId(userId);
+ if (userIdSplit[0] != null) {
+ mUserId.setText(userId);
+ } else {
+ mUserId.setText(R.string.user_id_no_name);
+ }
+ if (userIdSplit[1] != null) {
+ mUserIdRest.setText(userIdSplit[1]);
+ } else {
+ mUserIdRest.setText(getString(R.string.label_key_id) + ": "
+ + PgpKeyHelper.convertKeyIdToHex(mSignatureKeyId));
+ }
+
+ switch (signatureResult.getStatus()) {
+ case OpenPgpSignatureResult.SIGNATURE_SUCCESS_UNCERTIFIED: {
+ mSignatureStatusImage.setImageResource(R.drawable.overlay_ok);
+ mLookupKey.setVisibility(View.GONE);
+ break;
+ }
+
+ // TODO!
+// case OpenPgpSignatureResult.SIGNATURE_SUCCESS_CERTIFIED: {
+// break;
+// }
+
+ case OpenPgpSignatureResult.SIGNATURE_UNKNOWN_PUB_KEY: {
+ mSignatureStatusImage.setImageResource(R.drawable.overlay_error);
+ mLookupKey.setVisibility(View.VISIBLE);
+ AppMsg.makeText(getActivity(),
+ R.string.unknown_signature,
+ AppMsg.STYLE_ALERT).show();
+ break;
+ }
+
+ default: {
+ mSignatureStatusImage.setImageResource(R.drawable.overlay_error);
+ mLookupKey.setVisibility(View.GONE);
+ break;
+ }
+ }
+ mSignatureLayout.setVisibility(View.VISIBLE);
+ }
+ }
+
+ protected void showPassphraseDialog(long keyId) {
+ PassphraseDialogFragment.show(getActivity(), keyId,
+ new Handler() {
+ @Override
+ public void handleMessage(Message message) {
+ if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) {
+ String passphrase =
+ message.getData().getString(PassphraseDialogFragment.MESSAGE_DATA_PASSPHRASE);
+ decryptStart(passphrase);
+ }
+ }
+ });
+ }
+
+ /**
+ * Should be overridden by MessageFragment and FileFragment to start actual decryption
+ *
+ * @param passphrase
+ */
+ protected void decryptStart(String passphrase) {
+
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptMessageFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptMessageFragment.java
new file mode 100644
index 000000000..2169bbd77
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptMessageFragment.java
@@ -0,0 +1,189 @@
+/*
+ * 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.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.EditText;
+
+import com.beardedhen.androidbootstrap.BootstrapButton;
+import com.devspark.appmsg.AppMsg;
+
+import org.openintents.openpgp.OpenPgpSignatureResult;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.Id;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
+import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyResult;
+import org.sufficientlysecure.keychain.pgp.PgpHelper;
+import org.sufficientlysecure.keychain.service.KeychainIntentService;
+import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
+import org.sufficientlysecure.keychain.util.Log;
+
+import java.util.regex.Matcher;
+
+public class DecryptMessageFragment extends DecryptFragment {
+ public static final String ARG_CIPHERTEXT = "ciphertext";
+
+ // view
+ private EditText mMessage;
+ private BootstrapButton mDecryptButton;
+ private BootstrapButton mDecryptFromCLipboardButton;
+
+ // model
+ private String mCiphertext;
+
+ /**
+ * Inflate the layout for this fragment
+ */
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.decrypt_message_fragment, container, false);
+
+ mMessage = (EditText) view.findViewById(R.id.message);
+ mDecryptButton = (BootstrapButton) view.findViewById(R.id.action_decrypt);
+ mDecryptFromCLipboardButton = (BootstrapButton) view.findViewById(R.id.action_decrypt_from_clipboard);
+ mDecryptButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ decryptClicked();
+ }
+ });
+ mDecryptFromCLipboardButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ decryptFromClipboardClicked();
+ }
+ });
+
+ return view;
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ String ciphertext = getArguments().getString(ARG_CIPHERTEXT);
+ if (ciphertext != null) {
+ mMessage.setText(ciphertext);
+ decryptStart(null);
+ }
+ }
+
+ private void decryptClicked() {
+ mCiphertext = mMessage.getText().toString();
+ decryptStart(null);
+ }
+
+ private void decryptFromClipboardClicked() {
+ CharSequence clipboardText = ClipboardReflection.getClipboardText(getActivity());
+
+ // only decrypt if clipboard content is available and a pgp message or cleartext signature
+ if (clipboardText != null) {
+ Matcher matcher = PgpHelper.PGP_MESSAGE.matcher(clipboardText);
+ if (!matcher.matches()) {
+ matcher = PgpHelper.PGP_CLEARTEXT_SIGNATURE.matcher(clipboardText);
+ }
+ if (matcher.matches()) {
+ mCiphertext = matcher.group(1);
+ decryptStart(null);
+ } else {
+ AppMsg.makeText(getActivity(), R.string.error_invalid_data, AppMsg.STYLE_INFO)
+ .show();
+ }
+ } else {
+ AppMsg.makeText(getActivity(), R.string.error_invalid_data, AppMsg.STYLE_INFO)
+ .show();
+ }
+ }
+
+ @Override
+ protected void decryptStart(String passphrase) {
+ Log.d(Constants.TAG, "decryptStart");
+
+ // 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.putInt(KeychainIntentService.TARGET, KeychainIntentService.TARGET_BYTES);
+ data.putByteArray(KeychainIntentService.DECRYPT_CIPHERTEXT_BYTES, mCiphertext.getBytes());
+ data.putString(KeychainIntentService.DECRYPT_PASSPHRASE, passphrase);
+
+ intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
+
+ // Message is received after encrypting is done in KeychainIntentService
+ KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(getActivity(),
+ getString(R.string.progress_decrypting), ProgressDialog.STYLE_HORIZONTAL) {
+ public void handleMessage(Message message) {
+ // handle messages by standard KeychainIntentServiceHandler first
+ super.handleMessage(message);
+
+ if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
+ // get returned data bundle
+ Bundle returnData = message.getData();
+
+ PgpDecryptVerifyResult decryptVerifyResult =
+ returnData.getParcelable(KeychainIntentService.RESULT_DECRYPT_VERIFY_RESULT);
+
+ if (PgpDecryptVerifyResult.KEY_PASSHRASE_NEEDED == decryptVerifyResult.getStatus()) {
+ showPassphraseDialog(decryptVerifyResult.getKeyIdPassphraseNeeded());
+ } else if (PgpDecryptVerifyResult.SYMMETRIC_PASSHRASE_NEEDED ==
+ decryptVerifyResult.getStatus()) {
+ showPassphraseDialog(Id.key.symmetric);
+ } else {
+ AppMsg.makeText(getActivity(), R.string.decryption_successful,
+ AppMsg.STYLE_INFO).show();
+
+ byte[] decryptedMessage = returnData
+ .getByteArray(KeychainIntentService.RESULT_DECRYPTED_BYTES);
+ mMessage.setText(new String(decryptedMessage));
+ mMessage.setHorizontallyScrolling(false);
+
+ OpenPgpSignatureResult signatureResult = decryptVerifyResult.getSignatureResult();
+
+ // display signature result in activity
+ onSignatureResult(signatureResult);
+ }
+ }
+ }
+ };
+
+ // 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);
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DrawerActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DrawerActivity.java
new file mode 100644
index 000000000..c875818e3
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DrawerActivity.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.ui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.support.v4.app.ActionBarDrawerToggle;
+import android.support.v4.view.GravityCompat;
+import android.support.v4.widget.DrawerLayout;
+import android.support.v7.app.ActionBarActivity;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import com.beardedhen.androidbootstrap.FontAwesomeText;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+
+public class DrawerActivity extends ActionBarActivity {
+ private DrawerLayout mDrawerLayout;
+ private ListView mDrawerList;
+ private ActionBarDrawerToggle mDrawerToggle;
+
+ private CharSequence mDrawerTitle;
+ private CharSequence mTitle;
+ private boolean mIsDrawerLocked = false;
+
+ private Class mSelectedItem;
+
+ private static final int MENU_ID_PREFERENCE = 222;
+ private static final int MENU_ID_HELP = 223;
+
+ protected void setupDrawerNavigation(Bundle savedInstanceState) {
+ mDrawerTitle = getString(R.string.app_name);
+ mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
+ mDrawerList = (ListView) findViewById(R.id.left_drawer);
+ ViewGroup viewGroup = (ViewGroup) findViewById(R.id.content_frame);
+ int leftMarginLoaded = ((ViewGroup.MarginLayoutParams) viewGroup.getLayoutParams()).leftMargin;
+ int leftMarginInTablets = (int) getResources().getDimension(R.dimen.drawer_size);
+ int errorInMarginAllowed = 5;
+
+ // if the left margin of the loaded layout is close to the
+ // one used in tablets then set drawer as open and locked
+ if (Math.abs(leftMarginLoaded - leftMarginInTablets) < errorInMarginAllowed) {
+ mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_OPEN, mDrawerList);
+ mDrawerLayout.setScrimColor(Color.TRANSPARENT);
+ mIsDrawerLocked = true;
+ } else {
+ // set a custom shadow that overlays the main content when the drawer opens
+ mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);
+ mIsDrawerLocked = false;
+ }
+
+ NavItem mItemIconTexts[] = new NavItem[]{
+ new NavItem("fa-user", getString(R.string.nav_contacts)),
+ new NavItem("fa-lock", getString(R.string.nav_encrypt)),
+ new NavItem("fa-unlock", getString(R.string.nav_decrypt)),
+ new NavItem("fa-android", getString(R.string.nav_apps))};
+
+ mDrawerList.setAdapter(new NavigationDrawerAdapter(this, R.layout.drawer_list_item,
+ mItemIconTexts));
+
+ mDrawerList.setOnItemClickListener(new DrawerItemClickListener());
+
+ // enable ActionBar app icon to behave as action to toggle nav drawer
+ // if the drawer is not locked
+ if (!mIsDrawerLocked) {
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ getSupportActionBar().setHomeButtonEnabled(true);
+ }
+
+ // ActionBarDrawerToggle ties together the the proper interactions
+ // between the sliding drawer and the action bar app icon
+ mDrawerToggle = new ActionBarDrawerToggle(this, /* host Activity */
+ mDrawerLayout, /* DrawerLayout object */
+ R.drawable.ic_drawer, /* nav drawer image to replace 'Up' caret */
+ R.string.drawer_open, /* "open drawer" description for accessibility */
+ R.string.drawer_close /* "close drawer" description for accessibility */
+ ) {
+ public void onDrawerClosed(View view) {
+ getSupportActionBar().setTitle(mTitle);
+
+ callIntentForDrawerItem(mSelectedItem);
+ }
+
+ public void onDrawerOpened(View drawerView) {
+ mTitle = getSupportActionBar().getTitle();
+ getSupportActionBar().setTitle(mDrawerTitle);
+ // creates call to onPrepareOptionsMenu()
+ supportInvalidateOptionsMenu();
+ }
+ };
+
+ if (!mIsDrawerLocked) {
+ mDrawerLayout.setDrawerListener(mDrawerToggle);
+ } else {
+ // If the drawer is locked open make it un-focusable
+ // so that it doesn't consume all the Back button presses
+ mDrawerLayout.setFocusableInTouchMode(false);
+ }
+ }
+
+ /**
+ * Uses startActivity to call the Intent of the given class
+ *
+ * @param drawerItem the class of the drawer item you want to load. Based on Constants.DrawerItems.*
+ */
+ public void callIntentForDrawerItem(Class drawerItem) {
+ // creates call to onPrepareOptionsMenu()
+ supportInvalidateOptionsMenu();
+
+ // call intent activity if selected
+ if (drawerItem != null) {
+ finish();
+ overridePendingTransition(0, 0);
+
+ Intent intent = new Intent(this, drawerItem);
+ startActivity(intent);
+
+ // disable animation of activity start
+ overridePendingTransition(0, 0);
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ if (mDrawerToggle == null) {
+ return super.onCreateOptionsMenu(menu);
+ }
+
+ menu.add(42, MENU_ID_PREFERENCE, 100, R.string.menu_preferences);
+ menu.add(42, MENU_ID_HELP, 101, R.string.menu_help);
+
+ return super.onCreateOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (mDrawerToggle == null) {
+ return super.onOptionsItemSelected(item);
+ }
+
+ // The action bar home/up action should open or close the drawer.
+ // ActionBarDrawerToggle will take care of this.
+ if (mDrawerToggle.onOptionsItemSelected(item)) {
+ return true;
+ }
+
+ switch (item.getItemId()) {
+ case MENU_ID_PREFERENCE: {
+ Intent intent = new Intent(this, PreferencesActivity.class);
+ startActivity(intent);
+ return true;
+ }
+ case MENU_ID_HELP: {
+ Intent intent = new Intent(this, HelpActivity.class);
+ startActivity(intent);
+ return true;
+ }
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ /**
+ * The click listener for ListView in the navigation drawer
+ */
+ private class DrawerItemClickListener implements ListView.OnItemClickListener {
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ selectItem(position);
+ }
+ }
+
+ private void selectItem(int position) {
+ // update selected item and title, then close the drawer
+ mDrawerList.setItemChecked(position, true);
+ // set selected class
+ mSelectedItem = Constants.DrawerItems.ARRAY[position];
+
+ // setTitle(mDrawerTitles[position]);
+ // If drawer isn't locked just close the drawer and
+ // it will move to the selected item by itself (via drawer toggle listener)
+ if (!mIsDrawerLocked) {
+ mDrawerLayout.closeDrawer(mDrawerList);
+ // else move to the selected item yourself
+ } else {
+ callIntentForDrawerItem(mSelectedItem);
+ }
+ }
+
+ /**
+ * When using the ActionBarDrawerToggle, you must call it during onPostCreate() and
+ * onConfigurationChanged()...
+ */
+ @Override
+ protected void onPostCreate(Bundle savedInstanceState) {
+ super.onPostCreate(savedInstanceState);
+ // Sync the toggle state after onRestoreInstanceState has occurred.
+ if (mDrawerToggle != null) {
+ mDrawerToggle.syncState();
+ }
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ // Pass any configuration change to the drawer toggles
+ if (mDrawerToggle != null) {
+ mDrawerToggle.onConfigurationChanged(newConfig);
+ }
+ }
+
+ private class NavItem {
+ public String icon;
+ public String title;
+
+ public NavItem(String icon, String title) {
+ super();
+ this.icon = icon;
+ this.title = title;
+ }
+ }
+
+ private class NavigationDrawerAdapter extends ArrayAdapter<NavItem> {
+ Context mContext;
+ int mLayoutResourceId;
+ NavItem mData[] = null;
+
+ public NavigationDrawerAdapter(Context context, int layoutResourceId, NavItem[] data) {
+ super(context, layoutResourceId, data);
+ this.mLayoutResourceId = layoutResourceId;
+ this.mContext = context;
+ this.mData = data;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View row = convertView;
+ NavItemHolder holder;
+
+ if (row == null) {
+ LayoutInflater inflater = ((Activity) mContext).getLayoutInflater();
+ row = inflater.inflate(mLayoutResourceId, parent, false);
+
+ holder = new NavItemHolder();
+ holder.mImg = (FontAwesomeText) row.findViewById(R.id.drawer_item_icon);
+ holder.mTxtTitle = (TextView) row.findViewById(R.id.drawer_item_text);
+
+ row.setTag(holder);
+ } else {
+ holder = (NavItemHolder) row.getTag();
+ }
+
+ NavItem item = mData[position];
+ holder.mTxtTitle.setText(item.title);
+ holder.mImg.setIcon(item.icon);
+
+ return row;
+ }
+
+ }
+
+ static class NavItemHolder {
+ FontAwesomeText mImg;
+ TextView mTxtTitle;
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java
new file mode 100644
index 000000000..93d5688b9
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java
@@ -0,0 +1,760 @@
+/*
+ * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
+ *
+ * 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;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Messenger;
+import android.support.v4.app.ActivityCompat;
+import android.support.v7.app.ActionBarActivity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+import android.widget.LinearLayout;
+
+import com.beardedhen.androidbootstrap.BootstrapButton;
+import com.devspark.appmsg.AppMsg;
+
+import org.spongycastle.openpgp.PGPSecretKey;
+import org.spongycastle.openpgp.PGPSecretKeyRing;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.Id;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.helper.ActionBarHelper;
+import org.sufficientlysecure.keychain.helper.ExportHelper;
+import org.sufficientlysecure.keychain.pgp.PgpConversionHelper;
+import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
+import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
+import org.sufficientlysecure.keychain.provider.KeychainContract;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.service.KeychainIntentService;
+import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
+import org.sufficientlysecure.keychain.service.PassphraseCacheService;
+import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
+import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
+import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment;
+import org.sufficientlysecure.keychain.ui.widget.Editor;
+import org.sufficientlysecure.keychain.ui.widget.Editor.EditorListener;
+import org.sufficientlysecure.keychain.ui.widget.KeyEditor;
+import org.sufficientlysecure.keychain.ui.widget.SectionView;
+import org.sufficientlysecure.keychain.ui.widget.UserIdEditor;
+import org.sufficientlysecure.keychain.util.IterableIterator;
+import org.sufficientlysecure.keychain.util.Log;
+
+import java.util.ArrayList;
+import java.util.GregorianCalendar;
+import java.util.List;
+import java.util.Vector;
+
+public class EditKeyActivity extends ActionBarActivity implements EditorListener {
+
+ // Actions for internal use only:
+ public static final String ACTION_CREATE_KEY = Constants.INTENT_PREFIX + "CREATE_KEY";
+ public static final String ACTION_EDIT_KEY = Constants.INTENT_PREFIX + "EDIT_KEY";
+
+ // possible extra keys
+ public static final String EXTRA_USER_IDS = "user_ids";
+ public static final String EXTRA_NO_PASSPHRASE = "no_passphrase";
+ public static final String EXTRA_GENERATE_DEFAULT_KEYS = "generate_default_keys";
+
+ // EDIT
+ private Uri mDataUri;
+
+ private PGPSecretKeyRing mKeyRing = null;
+
+ private SectionView mUserIdsView;
+ private SectionView mKeysView;
+
+ private String mCurrentPassphrase = null;
+ private String mNewPassphrase = null;
+ private String mSavedNewPassphrase = null;
+ private boolean mIsPassphraseSet;
+ private boolean mNeedsSaving;
+ private boolean mIsBrandNewKeyring = false;
+
+ private BootstrapButton mChangePassphrase;
+
+ private CheckBox mNoPassphrase;
+
+ Vector<String> mUserIds;
+ Vector<PGPSecretKey> mKeys;
+ Vector<Integer> mKeysUsages;
+ boolean mMasterCanSign = true;
+
+ ExportHelper mExportHelper;
+
+ public boolean needsSaving() {
+ mNeedsSaving = (mUserIdsView == null) ? false : mUserIdsView.needsSaving();
+ mNeedsSaving |= (mKeysView == null) ? false : mKeysView.needsSaving();
+ mNeedsSaving |= hasPassphraseChanged();
+ mNeedsSaving |= mIsBrandNewKeyring;
+ return mNeedsSaving;
+ }
+
+
+ public void somethingChanged() {
+ ActivityCompat.invalidateOptionsMenu(this);
+ }
+
+ public void onDeleted(Editor e, boolean wasNewItem) {
+ somethingChanged();
+ }
+
+ public void onEdited() {
+ somethingChanged();
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mExportHelper = new ExportHelper(this);
+
+ // Inflate a "Done"/"Cancel" custom action bar view
+ ActionBarHelper.setTwoButtonView(getSupportActionBar(),
+ R.string.btn_save, R.drawable.ic_action_save,
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // Save
+ saveClicked();
+ }
+ }, R.string.menu_key_edit_cancel, R.drawable.ic_action_cancel,
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // Cancel
+ cancelClicked();
+ }
+ }
+ );
+
+ mUserIds = new Vector<String>();
+ mKeys = new Vector<PGPSecretKey>();
+ mKeysUsages = new Vector<Integer>();
+
+ // Catch Intents opened from other apps
+ Intent intent = getIntent();
+ String action = intent.getAction();
+ if (ACTION_CREATE_KEY.equals(action)) {
+ handleActionCreateKey(intent);
+ } else if (ACTION_EDIT_KEY.equals(action)) {
+ handleActionEditKey(intent);
+ }
+ }
+
+ /**
+ * Handle intent action to create new key
+ *
+ * @param intent
+ */
+ private void handleActionCreateKey(Intent intent) {
+ Bundle extras = intent.getExtras();
+
+ mCurrentPassphrase = "";
+ mIsBrandNewKeyring = true;
+
+ if (extras != null) {
+ // if userId is given, prefill the fields
+ if (extras.containsKey(EXTRA_USER_IDS)) {
+ Log.d(Constants.TAG, "UserIds are given!");
+ mUserIds.add(extras.getString(EXTRA_USER_IDS));
+ }
+
+ // if no passphrase is given
+ if (extras.containsKey(EXTRA_NO_PASSPHRASE)) {
+ boolean noPassphrase = extras.getBoolean(EXTRA_NO_PASSPHRASE);
+ if (noPassphrase) {
+ // check "no passphrase" checkbox and remove button
+ mNoPassphrase.setChecked(true);
+ mChangePassphrase.setVisibility(View.GONE);
+ }
+ }
+
+ // generate key
+ if (extras.containsKey(EXTRA_GENERATE_DEFAULT_KEYS)) {
+ boolean generateDefaultKeys = extras.getBoolean(EXTRA_GENERATE_DEFAULT_KEYS);
+ if (generateDefaultKeys) {
+
+ // Send all information needed to service generate keys in other thread
+ final Intent serviceIntent = new Intent(this, KeychainIntentService.class);
+ serviceIntent.setAction(KeychainIntentService.ACTION_GENERATE_DEFAULT_RSA_KEYS);
+
+ // fill values for this action
+ Bundle data = new Bundle();
+ data.putString(KeychainIntentService.GENERATE_KEY_SYMMETRIC_PASSPHRASE,
+ mCurrentPassphrase);
+
+ serviceIntent.putExtra(KeychainIntentService.EXTRA_DATA, data);
+
+ // Message is received after generating is done in KeychainIntentService
+ KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(
+ this, getResources().getQuantityString(R.plurals.progress_generating, 1),
+ ProgressDialog.STYLE_HORIZONTAL, true,
+
+ new DialogInterface.OnCancelListener() {
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ // Stop key generation on cancel
+ stopService(serviceIntent);
+ EditKeyActivity.this.setResult(Activity.RESULT_CANCELED);
+ EditKeyActivity.this.finish();
+ }
+ }) {
+
+ @Override
+ public void handleMessage(Message message) {
+ // handle messages by standard KeychainIntentServiceHandler first
+ super.handleMessage(message);
+
+ if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
+ // get new key from data bundle returned from service
+ Bundle data = message.getData();
+
+ ArrayList<PGPSecretKey> newKeys =
+ PgpConversionHelper.BytesToPGPSecretKeyList(data
+ .getByteArray(KeychainIntentService.RESULT_NEW_KEY));
+
+ ArrayList<Integer> keyUsageFlags = data.getIntegerArrayList(
+ KeychainIntentService.RESULT_KEY_USAGES);
+
+ if (newKeys.size() == keyUsageFlags.size()) {
+ for (int i = 0; i < newKeys.size(); ++i) {
+ mKeys.add(newKeys.get(i));
+ mKeysUsages.add(keyUsageFlags.get(i));
+ }
+ }
+
+ buildLayout(true);
+ }
+ }
+ };
+
+ // Create a new Messenger for the communication back
+ Messenger messenger = new Messenger(saveHandler);
+ serviceIntent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
+
+ saveHandler.showProgressDialog(this);
+
+ // start service with intent
+ startService(serviceIntent);
+ }
+ }
+ } else {
+ buildLayout(false);
+ }
+ }
+
+ /**
+ * Handle intent action to edit existing key
+ *
+ * @param intent
+ */
+ private void handleActionEditKey(Intent intent) {
+ mDataUri = intent.getData();
+ if (mDataUri == null) {
+ Log.e(Constants.TAG, "Intent data missing. Should be Uri of key!");
+ finish();
+ } else {
+ Log.d(Constants.TAG, "uri: " + mDataUri);
+
+ // get master key id using row id
+ long masterKeyId = ProviderHelper.getMasterKeyId(this, mDataUri);
+ finallyEdit(masterKeyId);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private void finallyEdit(final long masterKeyId) {
+ if (masterKeyId != 0) {
+ PGPSecretKey masterKey = null;
+ mKeyRing = ProviderHelper.getPGPSecretKeyRing(this, masterKeyId);
+ if (mKeyRing != null) {
+ masterKey = mKeyRing.getSecretKey();
+ mMasterCanSign = PgpKeyHelper.isCertificationKey(mKeyRing.getSecretKey());
+ for (PGPSecretKey key : new IterableIterator<PGPSecretKey>(mKeyRing.getSecretKeys())) {
+ mKeys.add(key);
+ mKeysUsages.add(-1); // get usage when view is created
+ }
+ } else {
+ Log.e(Constants.TAG, "Keyring not found with masterKeyId: " + masterKeyId);
+ AppMsg.makeText(this, R.string.error_no_secret_key_found, AppMsg.STYLE_ALERT).show();
+ // TODO
+ }
+ if (masterKey != null) {
+ boolean isSet = false;
+ for (String userId : new IterableIterator<String>(masterKey.getUserIDs())) {
+ Log.d(Constants.TAG, "Added userId " + userId);
+ if (!isSet) {
+ isSet = true;
+ String[] parts = PgpKeyHelper.splitUserId(userId);
+ if (parts[0] != null) {
+ setTitle(parts[0]);
+ }
+ }
+ mUserIds.add(userId);
+ }
+ }
+ }
+
+ mCurrentPassphrase = "";
+ buildLayout(false);
+
+ mIsPassphraseSet = PassphraseCacheService.hasPassphrase(this, masterKeyId);
+ if (!mIsPassphraseSet) {
+ // check "no passphrase" checkbox and remove button
+ mNoPassphrase.setChecked(true);
+ mChangePassphrase.setVisibility(View.GONE);
+ }
+ }
+
+ /**
+ * Shows the dialog to set a new passphrase
+ */
+ private void showSetPassphraseDialog() {
+ // Message is received after passphrase is cached
+ Handler returnHandler = new Handler() {
+ @Override
+ public void handleMessage(Message message) {
+ if (message.what == SetPassphraseDialogFragment.MESSAGE_OKAY) {
+ Bundle data = message.getData();
+
+ // set new returned passphrase!
+ mNewPassphrase = data
+ .getString(SetPassphraseDialogFragment.MESSAGE_NEW_PASSPHRASE);
+
+ updatePassphraseButtonText();
+ somethingChanged();
+ }
+ }
+ };
+
+ // Create a new Messenger for the communication back
+ Messenger messenger = new Messenger(returnHandler);
+
+ // set title based on isPassphraseSet()
+ int title;
+ if (isPassphraseSet()) {
+ title = R.string.title_change_passphrase;
+ } else {
+ title = R.string.title_set_passphrase;
+ }
+
+ SetPassphraseDialogFragment setPassphraseDialog = SetPassphraseDialogFragment.newInstance(
+ messenger, title);
+
+ setPassphraseDialog.show(getSupportFragmentManager(), "setPassphraseDialog");
+ }
+
+ /**
+ * Build layout based on mUserId, mKeys and mKeysUsages Vectors. It creates Views for every user
+ * id and key.
+ *
+ * @param newKeys
+ */
+ private void buildLayout(boolean newKeys) {
+ setContentView(R.layout.edit_key_activity);
+
+ // find views
+ mChangePassphrase = (BootstrapButton) findViewById(R.id.edit_key_btn_change_passphrase);
+ mNoPassphrase = (CheckBox) findViewById(R.id.edit_key_no_passphrase);
+ // Build layout based on given userIds and keys
+
+ LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+ LinearLayout container = (LinearLayout) findViewById(R.id.edit_key_container);
+ if (mIsPassphraseSet) {
+ mChangePassphrase.setText(getString(R.string.btn_change_passphrase));
+ }
+ mUserIdsView = (SectionView) inflater.inflate(R.layout.edit_key_section, container, false);
+ mUserIdsView.setType(Id.type.user_id);
+ mUserIdsView.setCanBeEdited(mMasterCanSign);
+ mUserIdsView.setUserIds(mUserIds);
+ mUserIdsView.setEditorListener(this);
+ container.addView(mUserIdsView);
+ mKeysView = (SectionView) inflater.inflate(R.layout.edit_key_section, container, false);
+ mKeysView.setType(Id.type.key);
+ mKeysView.setCanBeEdited(mMasterCanSign);
+ mKeysView.setKeys(mKeys, mKeysUsages, newKeys);
+ mKeysView.setEditorListener(this);
+ container.addView(mKeysView);
+
+ updatePassphraseButtonText();
+
+ mChangePassphrase.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ showSetPassphraseDialog();
+ }
+ });
+
+ // disable passphrase when no passphrase checkbox is checked!
+ mNoPassphrase.setOnCheckedChangeListener(new OnCheckedChangeListener() {
+
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ if (isChecked) {
+ // remove passphrase
+ mSavedNewPassphrase = mNewPassphrase;
+ mNewPassphrase = "";
+ mChangePassphrase.setVisibility(View.GONE);
+ } else {
+ mNewPassphrase = mSavedNewPassphrase;
+ mChangePassphrase.setVisibility(View.VISIBLE);
+ }
+ somethingChanged();
+ }
+ });
+ }
+
+ private long getMasterKeyId() {
+ if (mKeysView.getEditors().getChildCount() == 0) {
+ return 0;
+ }
+ return ((KeyEditor) mKeysView.getEditors().getChildAt(0)).getValue().getKeyID();
+ }
+
+ public boolean isPassphraseSet() {
+ if (mNoPassphrase.isChecked()) {
+ return true;
+ } else if ((mIsPassphraseSet)
+ || (mNewPassphrase != null && !mNewPassphrase.equals(""))) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public boolean hasPassphraseChanged() {
+ if (mNoPassphrase != null) {
+ if (mNoPassphrase.isChecked()) {
+ return mIsPassphraseSet;
+ } else {
+ return (mNewPassphrase != null && !mNewPassphrase.equals(""));
+ }
+ } else {
+ return false;
+ }
+ }
+
+ private void saveClicked() {
+ final long masterKeyId = getMasterKeyId();
+ if (needsSaving()) { //make sure, as some versions don't support invalidateOptionsMenu
+ try {
+ if (!isPassphraseSet()) {
+ throw new PgpGeneralException(this.getString(R.string.set_a_passphrase));
+ }
+
+ String passphrase;
+ if (mIsPassphraseSet) {
+ passphrase = PassphraseCacheService.getCachedPassphrase(this, masterKeyId);
+ } else {
+ passphrase = "";
+ }
+ if (passphrase == null) {
+ PassphraseDialogFragment.show(this, masterKeyId,
+ new Handler() {
+ @Override
+ public void handleMessage(Message message) {
+ if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) {
+ mCurrentPassphrase = PassphraseCacheService.getCachedPassphrase(
+ EditKeyActivity.this, masterKeyId);
+ checkEmptyIDsWanted();
+ }
+ }
+ });
+ } else {
+ mCurrentPassphrase = passphrase;
+ checkEmptyIDsWanted();
+ }
+ } catch (PgpGeneralException e) {
+ AppMsg.makeText(this, getString(R.string.error_message, e.getMessage()),
+ AppMsg.STYLE_ALERT).show();
+ }
+ } else {
+ AppMsg.makeText(this, R.string.error_change_something_first, AppMsg.STYLE_ALERT).show();
+ }
+ }
+
+ private void checkEmptyIDsWanted() {
+ try {
+ ArrayList<String> userIDs = getUserIds(mUserIdsView);
+ List<Boolean> newIDs = mUserIdsView.getNewIDFlags();
+ ArrayList<String> originalIDs = mUserIdsView.getOriginalIDs();
+ int curID = 0;
+ for (String userID : userIDs) {
+ if (userID.equals("") && (!userID.equals(originalIDs.get(curID)) || newIDs.get(curID))) {
+ AlertDialog.Builder alert = new AlertDialog.Builder(
+ EditKeyActivity.this);
+
+ alert.setIcon(R.drawable.ic_dialog_alert_holo_light);
+ alert.setTitle(R.string.warning);
+ alert.setMessage(EditKeyActivity.this.getString(R.string.ask_empty_id_ok));
+
+ alert.setPositiveButton(EditKeyActivity.this.getString(android.R.string.yes),
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ dialog.dismiss();
+ finallySaveClicked();
+ }
+ }
+ );
+ alert.setNegativeButton(this.getString(android.R.string.no),
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ dialog.dismiss();
+ }
+ }
+ );
+ alert.setCancelable(false);
+ alert.create().show();
+ return;
+ }
+ curID++;
+ }
+ } catch (PgpGeneralException e) {
+ Log.e(Constants.TAG, getString(R.string.error_message, e.getMessage()));
+ AppMsg.makeText(this, getString(R.string.error_message, e.getMessage()), AppMsg.STYLE_ALERT).show();
+ }
+ finallySaveClicked();
+ }
+
+ private boolean[] toPrimitiveArray(final List<Boolean> booleanList) {
+ final boolean[] primitives = new boolean[booleanList.size()];
+ int index = 0;
+ for (Boolean object : booleanList) {
+ primitives[index++] = object;
+ }
+ return primitives;
+ }
+
+ private void finallySaveClicked() {
+ try {
+ // Send all information needed to service to edit key in other thread
+ Intent intent = new Intent(this, KeychainIntentService.class);
+
+ intent.setAction(KeychainIntentService.ACTION_SAVE_KEYRING);
+
+ SaveKeyringParcel saveParams = new SaveKeyringParcel();
+ saveParams.userIDs = getUserIds(mUserIdsView);
+ saveParams.originalIDs = mUserIdsView.getOriginalIDs();
+ saveParams.deletedIDs = mUserIdsView.getDeletedIDs();
+ saveParams.newIDs = toPrimitiveArray(mUserIdsView.getNewIDFlags());
+ saveParams.primaryIDChanged = mUserIdsView.primaryChanged();
+ saveParams.moddedKeys = toPrimitiveArray(mKeysView.getNeedsSavingArray());
+ saveParams.deletedKeys = mKeysView.getDeletedKeys();
+ saveParams.keysExpiryDates = getKeysExpiryDates(mKeysView);
+ saveParams.keysUsages = getKeysUsages(mKeysView);
+ saveParams.newPassphrase = mNewPassphrase;
+ saveParams.oldPassphrase = mCurrentPassphrase;
+ saveParams.newKeys = toPrimitiveArray(mKeysView.getNewKeysArray());
+ saveParams.keys = getKeys(mKeysView);
+ saveParams.originalPrimaryID = mUserIdsView.getOriginalPrimaryID();
+
+
+ // fill values for this action
+ Bundle data = new Bundle();
+ data.putBoolean(KeychainIntentService.SAVE_KEYRING_CAN_SIGN, mMasterCanSign);
+ data.putParcelable(KeychainIntentService.SAVE_KEYRING_PARCEL, saveParams);
+
+ intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
+
+ // Message is received after saving is done in KeychainIntentService
+ KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this,
+ getString(R.string.progress_saving), ProgressDialog.STYLE_HORIZONTAL) {
+ public void handleMessage(Message message) {
+ // handle messages by standard KeychainIntentServiceHandler first
+ super.handleMessage(message);
+
+ if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
+ Intent data = new Intent();
+
+ // return uri pointing to new created key
+ Uri uri = KeychainContract.KeyRings.buildGenericKeyRingUri(
+ String.valueOf(getMasterKeyId()));
+ data.setData(uri);
+
+ setResult(RESULT_OK, data);
+ finish();
+ }
+ }
+ };
+
+ // Create a new Messenger for the communication back
+ Messenger messenger = new Messenger(saveHandler);
+ intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
+
+ saveHandler.showProgressDialog(this);
+
+ // start service with intent
+ startService(intent);
+ } catch (PgpGeneralException e) {
+ Log.e(Constants.TAG, getString(R.string.error_message, e.getMessage()));
+ AppMsg.makeText(this, getString(R.string.error_message, e.getMessage()),
+ AppMsg.STYLE_ALERT).show();
+ }
+ }
+
+ private void cancelClicked() {
+ if (needsSaving()) { //ask if we want to save
+ AlertDialog.Builder alert = new AlertDialog.Builder(
+ EditKeyActivity.this);
+
+ alert.setIcon(R.drawable.ic_dialog_alert_holo_light);
+ alert.setTitle(R.string.warning);
+ alert.setMessage(EditKeyActivity.this.getString(R.string.ask_save_changed_key));
+
+ alert.setPositiveButton(EditKeyActivity.this.getString(android.R.string.yes),
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ dialog.dismiss();
+ saveClicked();
+ }
+ });
+ alert.setNegativeButton(this.getString(android.R.string.no),
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ dialog.dismiss();
+ setResult(RESULT_CANCELED);
+ finish();
+ }
+ });
+ alert.setCancelable(false);
+ alert.create().show();
+ } else {
+ setResult(RESULT_CANCELED);
+ finish();
+ }
+ }
+
+ /**
+ * Returns user ids from the SectionView
+ *
+ * @param userIdsView
+ * @return
+ */
+ private ArrayList<String> getUserIds(SectionView userIdsView) throws PgpGeneralException {
+ ArrayList<String> userIds = new ArrayList<String>();
+
+ ViewGroup userIdEditors = userIdsView.getEditors();
+
+ boolean gotMainUserId = false;
+ for (int i = 0; i < userIdEditors.getChildCount(); ++i) {
+ UserIdEditor editor = (UserIdEditor) userIdEditors.getChildAt(i);
+ String userId;
+ userId = editor.getValue();
+
+ if (editor.isMainUserId()) {
+ userIds.add(0, userId);
+ gotMainUserId = true;
+ } else {
+ userIds.add(userId);
+ }
+ }
+
+ if (userIds.size() == 0) {
+ throw new PgpGeneralException(getString(R.string.error_key_needs_a_user_id));
+ }
+
+ if (!gotMainUserId) {
+ throw new PgpGeneralException(getString(R.string.error_main_user_id_must_not_be_empty));
+ }
+
+ return userIds;
+ }
+
+ /**
+ * Returns keys from the SectionView
+ *
+ * @param keysView
+ * @return
+ */
+ private ArrayList<PGPSecretKey> getKeys(SectionView keysView) throws PgpGeneralException {
+ ArrayList<PGPSecretKey> keys = new ArrayList<PGPSecretKey>();
+
+ ViewGroup keyEditors = keysView.getEditors();
+
+ if (keyEditors.getChildCount() == 0) {
+ throw new PgpGeneralException(getString(R.string.error_key_needs_master_key));
+ }
+
+ for (int i = 0; i < keyEditors.getChildCount(); ++i) {
+ KeyEditor editor = (KeyEditor) keyEditors.getChildAt(i);
+ keys.add(editor.getValue());
+ }
+
+ return keys;
+ }
+
+ /**
+ * Returns usage selections of keys from the SectionView
+ *
+ * @param keysView
+ * @return
+ */
+ private ArrayList<Integer> getKeysUsages(SectionView keysView) throws PgpGeneralException {
+ ArrayList<Integer> keysUsages = new ArrayList<Integer>();
+
+ ViewGroup keyEditors = keysView.getEditors();
+
+ if (keyEditors.getChildCount() == 0) {
+ throw new PgpGeneralException(getString(R.string.error_key_needs_master_key));
+ }
+
+ for (int i = 0; i < keyEditors.getChildCount(); ++i) {
+ KeyEditor editor = (KeyEditor) keyEditors.getChildAt(i);
+ keysUsages.add(editor.getUsage());
+ }
+
+ return keysUsages;
+ }
+
+ private ArrayList<GregorianCalendar> getKeysExpiryDates(SectionView keysView) throws PgpGeneralException {
+ ArrayList<GregorianCalendar> keysExpiryDates = new ArrayList<GregorianCalendar>();
+
+ ViewGroup keyEditors = keysView.getEditors();
+
+ if (keyEditors.getChildCount() == 0) {
+ throw new PgpGeneralException(getString(R.string.error_key_needs_master_key));
+ }
+
+ for (int i = 0; i < keyEditors.getChildCount(); ++i) {
+ KeyEditor editor = (KeyEditor) keyEditors.getChildAt(i);
+ keysExpiryDates.add(editor.getExpiryDate());
+ }
+
+ return keysExpiryDates;
+ }
+
+ private void updatePassphraseButtonText() {
+ mChangePassphrase.setText(isPassphraseSet() ? getString(R.string.btn_change_passphrase)
+ : getString(R.string.btn_set_passphrase));
+ }
+
+}
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..a03c7d797
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
+ *
+ * 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;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v4.view.PagerTabStrip;
+import android.support.v4.view.ViewPager;
+import android.widget.Toast;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.Id;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.helper.ActionBarHelper;
+import org.sufficientlysecure.keychain.helper.FileHelper;
+import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter;
+import org.sufficientlysecure.keychain.util.Log;
+
+public class EncryptActivity extends DrawerActivity implements
+ EncryptSymmetricFragment.OnSymmetricKeySelection,
+ EncryptAsymmetricFragment.OnAsymmetricKeySelection,
+ EncryptActivityInterface {
+
+ /* Intents */
+ public static final String ACTION_ENCRYPT = Constants.INTENT_PREFIX + "ENCRYPT";
+
+ /* EXTRA keys for input */
+ public static final String EXTRA_TEXT = "text";
+
+ // enables ASCII Armor for file encryption when uri is given
+ public static final String EXTRA_ASCII_ARMOR = "ascii_armor";
+
+ // preselect ids, for internal use
+ public static final String EXTRA_SIGNATURE_KEY_ID = "signature_key_id";
+ public static final String EXTRA_ENCRYPTION_KEY_IDS = "encryption_key_ids";
+
+ // view
+ ViewPager mViewPagerMode;
+ PagerTabStrip mPagerTabStripMode;
+ PagerTabStripAdapter mTabsAdapterMode;
+ ViewPager mViewPagerContent;
+ PagerTabStrip mPagerTabStripContent;
+ PagerTabStripAdapter mTabsAdapterContent;
+
+ // tabs
+ Bundle mAsymmetricFragmentBundle = new Bundle();
+ Bundle mSymmetricFragmentBundle = new Bundle();
+ Bundle mMessageFragmentBundle = new Bundle();
+ Bundle mFileFragmentBundle = new Bundle();
+ int mSwitchToMode = PAGER_MODE_ASYMMETRIC;
+ int mSwitchToContent = PAGER_CONTENT_MESSAGE;
+
+ private static final int PAGER_MODE_ASYMMETRIC = 0;
+ private static final int PAGER_MODE_SYMMETRIC = 1;
+ private static final int PAGER_CONTENT_MESSAGE = 0;
+ private static final int PAGER_CONTENT_FILE = 1;
+
+ // model useb by message and file fragment
+ private long mEncryptionKeyIds[] = null;
+ private long mSigningKeyId = Id.key.none;
+ private String mPassphrase;
+ private String mPassphraseAgain;
+
+ @Override
+ public void onSigningKeySelected(long signingKeyId) {
+ mSigningKeyId = signingKeyId;
+ }
+
+ @Override
+ public void onEncryptionKeysSelected(long[] encryptionKeyIds) {
+ mEncryptionKeyIds = encryptionKeyIds;
+ }
+
+ @Override
+ public void onPassphraseUpdate(String passphrase) {
+ mPassphrase = passphrase;
+ }
+
+ @Override
+ public void onPassphraseAgainUpdate(String passphrase) {
+ mPassphraseAgain = passphrase;
+ }
+
+ @Override
+ public boolean isModeSymmetric() {
+ if (PAGER_MODE_SYMMETRIC == mViewPagerMode.getCurrentItem()) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public long getSignatureKey() {
+ return mSigningKeyId;
+ }
+
+ @Override
+ public long[] getEncryptionKeys() {
+ return mEncryptionKeyIds;
+ }
+
+ @Override
+ public String getPassphrase() {
+ return mPassphrase;
+ }
+
+ @Override
+ public String getPassphraseAgain() {
+ return mPassphraseAgain;
+ }
+
+
+ private void initView() {
+ mViewPagerMode = (ViewPager) findViewById(R.id.encrypt_pager_mode);
+ mPagerTabStripMode = (PagerTabStrip) findViewById(R.id.encrypt_pager_tab_strip_mode);
+ mViewPagerContent = (ViewPager) findViewById(R.id.encrypt_pager_content);
+ mPagerTabStripContent = (PagerTabStrip) findViewById(R.id.encrypt_pager_tab_strip_content);
+
+ mTabsAdapterMode = new PagerTabStripAdapter(this);
+ mViewPagerMode.setAdapter(mTabsAdapterMode);
+ mTabsAdapterContent = new PagerTabStripAdapter(this);
+ mViewPagerContent.setAdapter(mTabsAdapterContent);
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.encrypt_activity);
+
+ // set actionbar without home button if called from another app
+ ActionBarHelper.setBackButton(this);
+
+ initView();
+
+ setupDrawerNavigation(savedInstanceState);
+
+ // Handle intent actions
+ handleActions(getIntent());
+
+ mTabsAdapterMode.addTab(EncryptAsymmetricFragment.class,
+ mAsymmetricFragmentBundle, getString(R.string.label_asymmetric));
+ mTabsAdapterMode.addTab(EncryptSymmetricFragment.class,
+ mSymmetricFragmentBundle, getString(R.string.label_symmetric));
+ mViewPagerMode.setCurrentItem(mSwitchToMode);
+
+ mTabsAdapterContent.addTab(EncryptMessageFragment.class,
+ mMessageFragmentBundle, getString(R.string.label_message));
+ mTabsAdapterContent.addTab(EncryptFileFragment.class,
+ mFileFragmentBundle, getString(R.string.label_file));
+ mViewPagerContent.setCurrentItem(mSwitchToContent);
+ }
+
+ /**
+ * Handles all actions with this intent
+ *
+ * @param intent
+ */
+ private void handleActions(Intent intent) {
+ String action = intent.getAction();
+ Bundle extras = intent.getExtras();
+ String type = intent.getType();
+ Uri uri = intent.getData();
+
+ if (extras == null) {
+ extras = new Bundle();
+ }
+
+ /*
+ * Android's Action
+ */
+ if (Intent.ACTION_SEND.equals(action) && type != null) {
+ // When sending to APG Encrypt via share menu
+ if ("text/plain".equals(type)) {
+ // Plain text
+ String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);
+ if (sharedText != null) {
+ // handle like normal text encryption, override action and extras to later
+ // executeServiceMethod ACTION_ENCRYPT in main actions
+ extras.putString(EXTRA_TEXT, sharedText);
+ extras.putBoolean(EXTRA_ASCII_ARMOR, true);
+ action = ACTION_ENCRYPT;
+ }
+ } else {
+ // Files via content provider, override uri and action
+ uri = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM);
+ action = ACTION_ENCRYPT;
+ }
+ }
+
+ if (extras.containsKey(EXTRA_ASCII_ARMOR)) {
+ boolean requestAsciiArmor = extras.getBoolean(EXTRA_ASCII_ARMOR, true);
+ mFileFragmentBundle.putBoolean(EncryptFileFragment.ARG_ASCII_ARMOR, requestAsciiArmor);
+ }
+
+ String textData = extras.getString(EXTRA_TEXT);
+
+ long signatureKeyId = extras.getLong(EXTRA_SIGNATURE_KEY_ID);
+ long[] encryptionKeyIds = extras.getLongArray(EXTRA_ENCRYPTION_KEY_IDS);
+
+ // preselect keys given by intent
+ mAsymmetricFragmentBundle.putLongArray(EncryptAsymmetricFragment.ARG_ENCRYPTION_KEY_IDS,
+ encryptionKeyIds);
+ mAsymmetricFragmentBundle.putLong(EncryptAsymmetricFragment.ARG_SIGNATURE_KEY_ID,
+ signatureKeyId);
+ mSwitchToMode = PAGER_MODE_ASYMMETRIC;
+
+ /**
+ * Main Actions
+ */
+ if (ACTION_ENCRYPT.equals(action) && textData != null) {
+ // encrypt text based on given extra
+ mMessageFragmentBundle.putString(EncryptMessageFragment.ARG_TEXT, textData);
+ mSwitchToContent = PAGER_CONTENT_MESSAGE;
+ } else if (ACTION_ENCRYPT.equals(action) && uri != null) {
+ // encrypt file based on Uri
+
+ // get file path from uri
+ String path = FileHelper.getPath(this, uri);
+
+ if (path != null) {
+ mFileFragmentBundle.putString(EncryptFileFragment.ARG_FILENAME, path);
+ mSwitchToContent = PAGER_CONTENT_FILE;
+ } else {
+ Log.e(Constants.TAG,
+ "Direct binary data without actual file in filesystem is not supported " +
+ "by Intents. Please use the Remote Service API!");
+ Toast.makeText(this, R.string.error_only_files_are_supported, Toast.LENGTH_LONG)
+ .show();
+ // end activity
+ finish();
+ }
+ } else {
+ Log.e(Constants.TAG,
+ "Include the extra 'text' or an Uri with setData() in your Intent!");
+ }
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivityInterface.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivityInterface.java
new file mode 100644
index 000000000..0786b3a16
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivityInterface.java
@@ -0,0 +1,30 @@
+/*
+ * 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;
+
+public interface EncryptActivityInterface {
+
+ public boolean isModeSymmetric();
+
+ public long getSignatureKey();
+ public long[] getEncryptionKeys();
+
+ public String getPassphrase();
+ public String getPassphraseAgain();
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java
new file mode 100644
index 000000000..8400cf397
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java
@@ -0,0 +1,268 @@
+/*
+ * 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.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CheckBox;
+import android.widget.TextView;
+
+import com.beardedhen.androidbootstrap.BootstrapButton;
+
+import org.spongycastle.openpgp.PGPPublicKey;
+import org.spongycastle.openpgp.PGPPublicKeyRing;
+import org.spongycastle.openpgp.PGPSecretKey;
+import org.spongycastle.openpgp.PGPSecretKeyRing;
+import org.sufficientlysecure.keychain.Id;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
+import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+
+import java.util.HashMap;
+import java.util.Vector;
+
+public class EncryptAsymmetricFragment extends Fragment {
+ public static final String ARG_SIGNATURE_KEY_ID = "signature_key_id";
+ public static final String ARG_ENCRYPTION_KEY_IDS = "encryption_key_ids";
+
+ public static final int RESULT_CODE_PUBLIC_KEYS = 0x00007001;
+ public static final int RESULT_CODE_SECRET_KEYS = 0x00007002;
+
+ OnAsymmetricKeySelection mKeySelectionListener;
+
+ // view
+ private BootstrapButton mSelectKeysButton;
+ private CheckBox mSign;
+ private TextView mMainUserId;
+ private TextView mMainUserIdRest;
+
+ // model
+ private long mSecretKeyId = Id.key.none;
+ private long mEncryptionKeyIds[] = null;
+
+ // Container Activity must implement this interface
+ public interface OnAsymmetricKeySelection {
+ public void onSigningKeySelected(long signingKeyId);
+
+ public void onEncryptionKeysSelected(long[] encryptionKeyIds);
+ }
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+ try {
+ mKeySelectionListener = (OnAsymmetricKeySelection) activity;
+ } catch (ClassCastException e) {
+ throw new ClassCastException(activity.toString() + " must implement OnAsymmetricKeySelection");
+ }
+ }
+
+ private void setSignatureKeyId(long signatureKeyId) {
+ mSecretKeyId = signatureKeyId;
+ // update key selection in EncryptActivity
+ mKeySelectionListener.onSigningKeySelected(signatureKeyId);
+ updateView();
+ }
+
+ private void setEncryptionKeyIds(long[] encryptionKeyIds) {
+ mEncryptionKeyIds = encryptionKeyIds;
+ // update key selection in EncryptActivity
+ mKeySelectionListener.onEncryptionKeysSelected(encryptionKeyIds);
+ updateView();
+ }
+
+ /**
+ * Inflate the layout for this fragment
+ */
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.encrypt_asymmetric_fragment, container, false);
+
+ mSelectKeysButton = (BootstrapButton) view.findViewById(R.id.btn_selectEncryptKeys);
+ mSign = (CheckBox) view.findViewById(R.id.sign);
+ mMainUserId = (TextView) view.findViewById(R.id.mainUserId);
+ mMainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest);
+ mSelectKeysButton.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ selectPublicKeys();
+ }
+ });
+ mSign.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ CheckBox checkBox = (CheckBox) v;
+ if (checkBox.isChecked()) {
+ selectSecretKey();
+ } else {
+ setSignatureKeyId(Id.key.none);
+ }
+ }
+ });
+
+ return view;
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ long signatureKeyId = getArguments().getLong(ARG_SIGNATURE_KEY_ID);
+ long[] encryptionKeyIds = getArguments().getLongArray(ARG_ENCRYPTION_KEY_IDS);
+
+ // preselect keys given by arguments (given by Intent to EncryptActivity)
+ preselectKeys(signatureKeyId, encryptionKeyIds);
+ }
+
+ /**
+ * If an Intent gives a signatureKeyId and/or encryptionKeyIds, preselect those!
+ *
+ * @param preselectedSignatureKeyId
+ * @param preselectedEncryptionKeyIds
+ */
+ private void preselectKeys(long preselectedSignatureKeyId, long[] preselectedEncryptionKeyIds) {
+ if (preselectedSignatureKeyId != 0) {
+ // TODO: don't use bouncy castle objects!
+ PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRingWithKeyId(getActivity(),
+ preselectedSignatureKeyId);
+ PGPSecretKey masterKey;
+ if (keyRing != null) {
+ masterKey = keyRing.getSecretKey();
+ if (masterKey != null) {
+ Vector<PGPSecretKey> signKeys = PgpKeyHelper.getUsableSigningKeys(keyRing);
+ if (signKeys.size() > 0) {
+ setSignatureKeyId(masterKey.getKeyID());
+ }
+ }
+ }
+ }
+
+ if (preselectedEncryptionKeyIds != null) {
+ Vector<Long> goodIds = new Vector<Long>();
+ for (int i = 0; i < preselectedEncryptionKeyIds.length; ++i) {
+ long id = ProviderHelper.getMasterKeyId(getActivity(),
+ KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(Long.toString(preselectedEncryptionKeyIds[i]))
+ );
+ // TODO check for available encrypt keys... is this even relevant?
+ goodIds.add(id);
+ }
+ if (goodIds.size() > 0) {
+ long[] keyIds = new long[goodIds.size()];
+ for (int i = 0; i < goodIds.size(); ++i) {
+ keyIds[i] = goodIds.get(i);
+ }
+ setEncryptionKeyIds(keyIds);
+ }
+ }
+ }
+
+ private void updateView() {
+ if (mEncryptionKeyIds == null || mEncryptionKeyIds.length == 0) {
+ mSelectKeysButton.setText(getString(R.string.select_keys_button_default));
+ } else {
+ mSelectKeysButton.setText(getResources().getQuantityString(
+ R.plurals.select_keys_button, mEncryptionKeyIds.length,
+ mEncryptionKeyIds.length));
+ }
+
+ if (mSecretKeyId == Id.key.none) {
+ mSign.setChecked(false);
+ mMainUserId.setText("");
+ mMainUserIdRest.setText("");
+ } else {
+ // See if we can get a user_id from a unified query
+ String userIdResult = (String) ProviderHelper.getUnifiedData(
+ getActivity(), mSecretKeyId, KeyRings.USER_ID, ProviderHelper.FIELD_TYPE_STRING);
+ String[] userId = PgpKeyHelper.splitUserId(userIdResult);
+ if (userId[0] != null) {
+ mMainUserId.setText(userId[0]);
+ } else {
+ mMainUserId.setText(getResources().getString(R.string.user_id_no_name));
+ }
+ if (userId[1] != null) {
+ mMainUserIdRest.setText(userId[1]);
+ } else {
+ mMainUserIdRest.setText("");
+ }
+ mSign.setChecked(true);
+ }
+ }
+
+ private void selectPublicKeys() {
+ Intent intent = new Intent(getActivity(), SelectPublicKeyActivity.class);
+ Vector<Long> keyIds = new Vector<Long>();
+ if (mSecretKeyId != 0) {
+ keyIds.add(mSecretKeyId);
+ }
+ if (mEncryptionKeyIds != null && mEncryptionKeyIds.length > 0) {
+ for (int i = 0; i < mEncryptionKeyIds.length; ++i) {
+ keyIds.add(mEncryptionKeyIds[i]);
+ }
+ }
+ long[] initialKeyIds = null;
+ if (keyIds.size() > 0) {
+ initialKeyIds = new long[keyIds.size()];
+ for (int i = 0; i < keyIds.size(); ++i) {
+ initialKeyIds[i] = keyIds.get(i);
+ }
+ }
+ intent.putExtra(SelectPublicKeyActivity.EXTRA_SELECTED_MASTER_KEY_IDS, initialKeyIds);
+ startActivityForResult(intent, Id.request.public_keys);
+ }
+
+ private void selectSecretKey() {
+ Intent intent = new Intent(getActivity(), SelectSecretKeyActivity.class);
+ startActivityForResult(intent, Id.request.secret_keys);
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ switch (requestCode) {
+ case RESULT_CODE_PUBLIC_KEYS: {
+ if (resultCode == Activity.RESULT_OK) {
+ Bundle bundle = data.getExtras();
+ setEncryptionKeyIds(bundle
+ .getLongArray(SelectPublicKeyActivity.RESULT_EXTRA_MASTER_KEY_IDS));
+ }
+ break;
+ }
+
+ case RESULT_CODE_SECRET_KEYS: {
+ if (resultCode == Activity.RESULT_OK) {
+ Uri uriMasterKey = data.getData();
+ setSignatureKeyId(Long.valueOf(uriMasterKey.getLastPathSegment()));
+ } else {
+ setSignatureKeyId(Id.key.none);
+ }
+ break;
+ }
+
+ default: {
+ super.onActivityResult(requestCode, resultCode, data);
+
+ break;
+ }
+ }
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFileFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFileFragment.java
new file mode 100644
index 000000000..470c85715
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFileFragment.java
@@ -0,0 +1,380 @@
+/*
+ * 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.app.ProgressDialog;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Messenger;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.CheckBox;
+import android.widget.EditText;
+import android.widget.Spinner;
+
+import com.beardedhen.androidbootstrap.BootstrapButton;
+import com.devspark.appmsg.AppMsg;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.Id;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.helper.FileHelper;
+import org.sufficientlysecure.keychain.helper.Preferences;
+import org.sufficientlysecure.keychain.service.KeychainIntentService;
+import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
+import org.sufficientlysecure.keychain.service.PassphraseCacheService;
+import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment;
+import org.sufficientlysecure.keychain.ui.dialog.FileDialogFragment;
+import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
+import org.sufficientlysecure.keychain.util.Choice;
+import org.sufficientlysecure.keychain.util.Log;
+
+import java.io.File;
+
+public class EncryptFileFragment extends Fragment {
+ public static final String ARG_FILENAME = "filename";
+ public static final String ARG_ASCII_ARMOR = "ascii_armor";
+
+ private static final int RESULT_CODE_FILE = 0x00007003;
+
+ private EncryptActivityInterface mEncryptInterface;
+
+ // view
+ private CheckBox mAsciiArmor = null;
+ private Spinner mFileCompression = null;
+ private EditText mFilename = null;
+ private CheckBox mDeleteAfter = null;
+ private CheckBox mShareAfter = null;
+ private BootstrapButton mBrowse = null;
+ private BootstrapButton mEncryptFile;
+
+ private FileDialogFragment mFileDialog;
+
+ // model
+ private String mInputFilename = null;
+ private String mOutputFilename = null;
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+ try {
+ mEncryptInterface = (EncryptActivityInterface) activity;
+ } catch (ClassCastException e) {
+ throw new ClassCastException(activity.toString() + " must implement EncryptActivityInterface");
+ }
+ }
+
+ /**
+ * Inflate the layout for this fragment
+ */
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.encrypt_file_fragment, container, false);
+
+ mEncryptFile = (BootstrapButton) view.findViewById(R.id.action_encrypt_file);
+ mEncryptFile.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ encryptClicked();
+ }
+ });
+
+ mFilename = (EditText) view.findViewById(R.id.filename);
+ mBrowse = (BootstrapButton) view.findViewById(R.id.btn_browse);
+ mBrowse.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ FileHelper.openFile(EncryptFileFragment.this, mFilename.getText().toString(), "*/*",
+ Id.request.filename);
+ }
+ });
+
+ mFileCompression = (Spinner) view.findViewById(R.id.fileCompression);
+ Choice[] choices = new Choice[] {
+ new Choice(Id.choice.compression.none, getString(R.string.choice_none) + " ("
+ + getString(R.string.compression_fast) + ")"),
+ new Choice(Id.choice.compression.zip, "ZIP ("
+ + getString(R.string.compression_fast) + ")"),
+ new Choice(Id.choice.compression.zlib, "ZLIB ("
+ + getString(R.string.compression_fast) + ")"),
+ new Choice(Id.choice.compression.bzip2, "BZIP2 ("
+ + getString(R.string.compression_very_slow) + ")"),
+ };
+ ArrayAdapter<Choice> adapter = new ArrayAdapter<Choice>(getActivity(),
+ android.R.layout.simple_spinner_item, choices);
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ mFileCompression.setAdapter(adapter);
+
+ int defaultFileCompression = Preferences.getPreferences(getActivity()).getDefaultFileCompression();
+ for (int i = 0; i < choices.length; ++i) {
+ if (choices[i].getId() == defaultFileCompression) {
+ mFileCompression.setSelection(i);
+ break;
+ }
+ }
+
+ mDeleteAfter = (CheckBox) view.findViewById(R.id.deleteAfterEncryption);
+ mShareAfter = (CheckBox) view.findViewById(R.id.shareAfterEncryption);
+
+ mAsciiArmor = (CheckBox) view.findViewById(R.id.asciiArmor);
+ mAsciiArmor.setChecked(Preferences.getPreferences(getActivity()).getDefaultAsciiArmor());
+
+ return view;
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ String filename = getArguments().getString(ARG_FILENAME);
+ if (filename != null) {
+ mFilename.setText(filename);
+ }
+ boolean asciiArmor = getArguments().getBoolean(ARG_ASCII_ARMOR);
+ if (asciiArmor) {
+ mAsciiArmor.setChecked(asciiArmor);
+ }
+ }
+
+ /**
+ * Guess output filename based on input path
+ *
+ * @param path
+ * @return Suggestion for output filename
+ */
+ private String guessOutputFilename(String path) {
+ // output in the same directory but with additional ending
+ File file = new File(path);
+ String ending = (mAsciiArmor.isChecked() ? ".asc" : ".gpg");
+ String outputFilename = file.getParent() + File.separator + file.getName() + ending;
+
+ return outputFilename;
+ }
+
+ private void showOutputFileDialog() {
+ // Message is received after file is selected
+ Handler returnHandler = new Handler() {
+ @Override
+ public void handleMessage(Message message) {
+ if (message.what == FileDialogFragment.MESSAGE_OKAY) {
+ Bundle data = message.getData();
+ mOutputFilename = data.getString(FileDialogFragment.MESSAGE_DATA_FILENAME);
+ encryptStart();
+ }
+ }
+ };
+
+ // Create a new Messenger for the communication back
+ Messenger messenger = new Messenger(returnHandler);
+
+ mFileDialog = FileDialogFragment.newInstance(messenger,
+ getString(R.string.title_encrypt_to_file),
+ getString(R.string.specify_file_to_encrypt_to), mOutputFilename, null);
+
+ mFileDialog.show(getActivity().getSupportFragmentManager(), "fileDialog");
+ }
+
+ private void encryptClicked() {
+ String currentFilename = mFilename.getText().toString();
+ if (mInputFilename == null || !mInputFilename.equals(currentFilename)) {
+ mInputFilename = mFilename.getText().toString();
+ }
+
+ mOutputFilename = guessOutputFilename(mInputFilename);
+
+ if (mInputFilename.equals("")) {
+ AppMsg.makeText(getActivity(), R.string.no_file_selected, AppMsg.STYLE_ALERT).show();
+ return;
+ }
+
+ if (!mInputFilename.startsWith("content")) {
+ File file = new File(mInputFilename);
+ if (!file.exists() || !file.isFile()) {
+ AppMsg.makeText(
+ getActivity(),
+ getString(R.string.error_message,
+ getString(R.string.error_file_not_found)), AppMsg.STYLE_ALERT)
+ .show();
+ return;
+ }
+ }
+
+ if (mEncryptInterface.isModeSymmetric()) {
+ // symmetric encryption
+
+ boolean gotPassphrase = (mEncryptInterface.getPassphrase() != null
+ && mEncryptInterface.getPassphrase().length() != 0);
+ if (!gotPassphrase) {
+ AppMsg.makeText(getActivity(), R.string.passphrase_must_not_be_empty, AppMsg.STYLE_ALERT)
+ .show();
+ return;
+ }
+
+ if (!mEncryptInterface.getPassphrase().equals(mEncryptInterface.getPassphraseAgain())) {
+ AppMsg.makeText(getActivity(), R.string.passphrases_do_not_match, AppMsg.STYLE_ALERT).show();
+ return;
+ }
+ } else {
+ // asymmetric encryption
+
+ boolean gotEncryptionKeys = (mEncryptInterface.getEncryptionKeys() != null
+ && mEncryptInterface.getEncryptionKeys().length > 0);
+
+ if (!gotEncryptionKeys) {
+ AppMsg.makeText(getActivity(), R.string.select_encryption_key, AppMsg.STYLE_ALERT).show();
+ return;
+ }
+
+ if (!gotEncryptionKeys && mEncryptInterface.getSignatureKey() == 0) {
+ AppMsg.makeText(getActivity(), R.string.select_encryption_or_signature_key,
+ AppMsg.STYLE_ALERT).show();
+ return;
+ }
+
+ if (mEncryptInterface.getSignatureKey() != 0 &&
+ PassphraseCacheService.getCachedPassphrase(getActivity(),
+ mEncryptInterface.getSignatureKey()) == null) {
+ PassphraseDialogFragment.show(getActivity(), mEncryptInterface.getSignatureKey(),
+ new Handler() {
+ @Override
+ public void handleMessage(Message message) {
+ if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) {
+ showOutputFileDialog();
+ }
+ }
+ });
+
+ return;
+ }
+ }
+
+ showOutputFileDialog();
+ }
+
+ private void encryptStart() {
+ // Send all information needed to service to edit key in other thread
+ Intent intent = new Intent(getActivity(), KeychainIntentService.class);
+
+ intent.setAction(KeychainIntentService.ACTION_ENCRYPT_SIGN);
+
+ // fill values for this action
+ Bundle data = new Bundle();
+
+ data.putInt(KeychainIntentService.TARGET, KeychainIntentService.TARGET_URI);
+
+ if (mEncryptInterface.isModeSymmetric()) {
+ Log.d(Constants.TAG, "Symmetric encryption enabled!");
+ String passphrase = mEncryptInterface.getPassphrase();
+ if (passphrase.length() == 0) {
+ passphrase = null;
+ }
+ data.putString(KeychainIntentService.ENCRYPT_SYMMETRIC_PASSPHRASE, passphrase);
+ } else {
+ data.putLong(KeychainIntentService.ENCRYPT_SIGNATURE_KEY_ID,
+ mEncryptInterface.getSignatureKey());
+ data.putLongArray(KeychainIntentService.ENCRYPT_ENCRYPTION_KEYS_IDS,
+ mEncryptInterface.getEncryptionKeys());
+ }
+
+ Log.d(Constants.TAG, "mInputFilename=" + mInputFilename + ", mOutputFilename="
+ + mOutputFilename);
+
+ data.putString(KeychainIntentService.ENCRYPT_INPUT_FILE, mInputFilename);
+ data.putString(KeychainIntentService.ENCRYPT_OUTPUT_FILE, mOutputFilename);
+
+ boolean useAsciiArmor = mAsciiArmor.isChecked();
+ data.putBoolean(KeychainIntentService.ENCRYPT_USE_ASCII_ARMOR, useAsciiArmor);
+
+ int compressionId = ((Choice) mFileCompression.getSelectedItem()).getId();
+ data.putInt(KeychainIntentService.ENCRYPT_COMPRESSION_ID, compressionId);
+// data.putBoolean(KeychainIntentService.ENCRYPT_GENERATE_SIGNATURE, mGenerateSignature);
+
+ intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
+
+ // Message is received after encrypting is done in KeychainIntentService
+ KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(getActivity(),
+ getString(R.string.progress_encrypting), ProgressDialog.STYLE_HORIZONTAL) {
+ public void handleMessage(Message message) {
+ // handle messages by standard KeychainIntentServiceHandler first
+ super.handleMessage(message);
+
+ if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
+ AppMsg.makeText(getActivity(), R.string.encryption_successful,
+ AppMsg.STYLE_INFO).show();
+
+ if (mDeleteAfter.isChecked()) {
+ // Create and show dialog to delete original file
+ DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment
+ .newInstance(mInputFilename);
+ deleteFileDialog.show(getActivity().getSupportFragmentManager(), "deleteDialog");
+ }
+
+ if (mShareAfter.isChecked()) {
+ // Share encrypted file
+ Intent sendFileIntent = new Intent(Intent.ACTION_SEND);
+ sendFileIntent.setType("*/*");
+ sendFileIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse(mOutputFilename));
+ startActivity(Intent.createChooser(sendFileIntent,
+ getString(R.string.title_send_file)));
+ }
+ }
+ }
+ };
+
+ // 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 RESULT_CODE_FILE: {
+ if (resultCode == Activity.RESULT_OK && data != null) {
+ try {
+ String path = FileHelper.getPath(getActivity(), data.getData());
+ Log.d(Constants.TAG, "path=" + path);
+
+ mFilename.setText(path);
+ } catch (NullPointerException e) {
+ Log.e(Constants.TAG, "Nullpointer while retrieving path!");
+ }
+ }
+ return;
+ }
+
+ default: {
+ super.onActivityResult(requestCode, resultCode, data);
+
+ break;
+ }
+ }
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptMessageFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptMessageFragment.java
new file mode 100644
index 000000000..ba11074fc
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptMessageFragment.java
@@ -0,0 +1,259 @@
+/*
+ * 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.app.ProgressDialog;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Messenger;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.EditText;
+
+import com.beardedhen.androidbootstrap.BootstrapButton;
+import com.devspark.appmsg.AppMsg;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
+import org.sufficientlysecure.keychain.helper.Preferences;
+import org.sufficientlysecure.keychain.service.KeychainIntentService;
+import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
+import org.sufficientlysecure.keychain.service.PassphraseCacheService;
+import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
+import org.sufficientlysecure.keychain.util.Log;
+
+public class EncryptMessageFragment extends Fragment {
+ public static final String ARG_TEXT = "text";
+
+ private EditText mMessage = null;
+ private BootstrapButton mEncryptShare;
+ private BootstrapButton mEncryptClipboard;
+
+ private EncryptActivityInterface mEncryptInterface;
+
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+ try {
+ mEncryptInterface = (EncryptActivityInterface) activity;
+ } catch (ClassCastException e) {
+ throw new ClassCastException(activity.toString() + " must implement EncryptActivityInterface");
+ }
+ }
+
+ /**
+ * Inflate the layout for this fragment
+ */
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.encrypt_message_fragment, container, false);
+
+ mMessage = (EditText) view.findViewById(R.id.message);
+ mEncryptClipboard = (BootstrapButton) view.findViewById(R.id.action_encrypt_clipboard);
+ mEncryptShare = (BootstrapButton) view.findViewById(R.id.action_encrypt_share);
+ mEncryptClipboard.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ encryptClicked(true);
+ }
+ });
+ mEncryptShare.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ encryptClicked(false);
+ }
+ });
+
+ return view;
+ }
+
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ String text = getArguments().getString(ARG_TEXT);
+ if (text != null) {
+ mMessage.setText(text);
+ }
+ }
+
+ /**
+ * Fixes bad message characters for gmail
+ *
+ * @param message
+ * @return
+ */
+ private String fixBadCharactersForGmail(String message) {
+ // fix the message a bit, trailing spaces and newlines break stuff,
+ // because GMail sends as HTML and such things fuck up the
+ // signature,
+ // TODO: things like "<" and ">" also fuck up the signature
+ message = message.replaceAll(" +\n", "\n");
+ message = message.replaceAll("\n\n+", "\n\n");
+ message = message.replaceFirst("^\n+", "");
+ // make sure there'll be exactly one newline at the end
+ message = message.replaceFirst("\n*$", "\n");
+
+ return message;
+ }
+
+ private void encryptClicked(final boolean toClipboard) {
+ if (mEncryptInterface.isModeSymmetric()) {
+ // symmetric encryption
+
+ boolean gotPassphrase = (mEncryptInterface.getPassphrase() != null
+ && mEncryptInterface.getPassphrase().length() != 0);
+ if (!gotPassphrase) {
+ AppMsg.makeText(getActivity(), R.string.passphrase_must_not_be_empty, AppMsg.STYLE_ALERT)
+ .show();
+ return;
+ }
+
+ if (!mEncryptInterface.getPassphrase().equals(mEncryptInterface.getPassphraseAgain())) {
+ AppMsg.makeText(getActivity(), R.string.passphrases_do_not_match, AppMsg.STYLE_ALERT).show();
+ return;
+ }
+
+ } else {
+ // asymmetric encryption
+
+ boolean gotEncryptionKeys = (mEncryptInterface.getEncryptionKeys() != null
+ && mEncryptInterface.getEncryptionKeys().length > 0);
+
+ if (!gotEncryptionKeys && mEncryptInterface.getSignatureKey() == 0) {
+ AppMsg.makeText(getActivity(), R.string.select_encryption_or_signature_key,
+ AppMsg.STYLE_ALERT).show();
+ return;
+ }
+
+ if (mEncryptInterface.getSignatureKey() != 0 &&
+ PassphraseCacheService.getCachedPassphrase(getActivity(),
+ mEncryptInterface.getSignatureKey()) == null) {
+ PassphraseDialogFragment.show(getActivity(), mEncryptInterface.getSignatureKey(),
+ new Handler() {
+ @Override
+ public void handleMessage(Message message) {
+ if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) {
+ encryptStart(toClipboard);
+ }
+ }
+ });
+
+ return;
+ }
+ }
+
+ encryptStart(toClipboard);
+ }
+
+ private void encryptStart(final boolean toClipboard) {
+ // Send all information needed to service to edit key in other thread
+ Intent intent = new Intent(getActivity(), KeychainIntentService.class);
+
+ intent.setAction(KeychainIntentService.ACTION_ENCRYPT_SIGN);
+
+ // fill values for this action
+ Bundle data = new Bundle();
+
+ data.putInt(KeychainIntentService.TARGET, KeychainIntentService.TARGET_BYTES);
+
+ String message = mMessage.getText().toString();
+
+ if (mEncryptInterface.isModeSymmetric()) {
+ Log.d(Constants.TAG, "Symmetric encryption enabled!");
+ String passphrase = mEncryptInterface.getPassphrase();
+ if (passphrase.length() == 0) {
+ passphrase = null;
+ }
+ data.putString(KeychainIntentService.ENCRYPT_SYMMETRIC_PASSPHRASE, passphrase);
+ } else {
+ data.putLong(KeychainIntentService.ENCRYPT_SIGNATURE_KEY_ID,
+ mEncryptInterface.getSignatureKey());
+ data.putLongArray(KeychainIntentService.ENCRYPT_ENCRYPTION_KEYS_IDS,
+ mEncryptInterface.getEncryptionKeys());
+
+ boolean signOnly = (mEncryptInterface.getEncryptionKeys() == null
+ || mEncryptInterface.getEncryptionKeys().length == 0);
+ if (signOnly) {
+ message = fixBadCharactersForGmail(message);
+ }
+ }
+
+ data.putByteArray(KeychainIntentService.ENCRYPT_MESSAGE_BYTES, message.getBytes());
+
+ data.putBoolean(KeychainIntentService.ENCRYPT_USE_ASCII_ARMOR, true);
+
+ int compressionId = Preferences.getPreferences(getActivity()).getDefaultMessageCompression();
+ data.putInt(KeychainIntentService.ENCRYPT_COMPRESSION_ID, compressionId);
+// data.putBoolean(KeychainIntentService.ENCRYPT_GENERATE_SIGNATURE, mGenerateSignature);
+
+ intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
+
+ // Message is received after encrypting is done in KeychainIntentService
+ KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(getActivity(),
+ getString(R.string.progress_encrypting), ProgressDialog.STYLE_HORIZONTAL) {
+ public void handleMessage(Message message) {
+ // handle messages by standard KeychainIntentServiceHandler first
+ super.handleMessage(message);
+
+ if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
+ // get returned data bundle
+ Bundle data = message.getData();
+
+ String output = new String(data.getByteArray(KeychainIntentService.RESULT_BYTES));
+ Log.d(Constants.TAG, "output: " + output);
+
+ if (toClipboard) {
+ ClipboardReflection.copyToClipboard(getActivity(), output);
+ AppMsg.makeText(getActivity(),
+ R.string.encryption_to_clipboard_successful, AppMsg.STYLE_INFO)
+ .show();
+ } else {
+ Intent sendIntent = new Intent(Intent.ACTION_SEND);
+
+ // Type is set to text/plain so that encrypted messages can
+ // be sent with Whatsapp, Hangouts, SMS etc...
+ sendIntent.setType("text/plain");
+
+ sendIntent.putExtra(Intent.EXTRA_TEXT, output);
+ startActivity(Intent.createChooser(sendIntent,
+ getString(R.string.title_send_email)));
+ }
+ }
+ }
+ };
+
+ // 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);
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptSymmetricFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptSymmetricFragment.java
new file mode 100644
index 000000000..8efa07953
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptSymmetricFragment.java
@@ -0,0 +1,98 @@
+/*
+ * 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.text.Editable;
+import android.text.TextWatcher;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.EditText;
+
+import org.sufficientlysecure.keychain.R;
+
+public class EncryptSymmetricFragment extends Fragment {
+
+ OnSymmetricKeySelection mPassphraseUpdateListener;
+
+ private EditText mPassphrase;
+ private EditText mPassphraseAgain;
+
+ // Container Activity must implement this interface
+ public interface OnSymmetricKeySelection {
+ public void onPassphraseUpdate(String passphrase);
+
+ public void onPassphraseAgainUpdate(String passphrase);
+ }
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+ try {
+ mPassphraseUpdateListener = (OnSymmetricKeySelection) activity;
+ } catch (ClassCastException e) {
+ throw new ClassCastException(activity.toString() + " must implement OnSymmetricKeySelection");
+ }
+ }
+
+ /**
+ * 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);
+ mPassphrase.addTextChangedListener(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) {
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ // update passphrase in EncryptActivity
+ mPassphraseUpdateListener.onPassphraseUpdate(s.toString());
+ }
+ });
+ mPassphraseAgain.addTextChangedListener(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) {
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ // update passphrase in EncryptActivity
+ mPassphraseUpdateListener.onPassphraseAgainUpdate(s.toString());
+ }
+ });
+
+ return view;
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpAboutFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpAboutFragment.java
new file mode 100644
index 000000000..a484b57de
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpAboutFragment.java
@@ -0,0 +1,75 @@
+/*
+ * 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.ui;
+
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+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.TextView;
+import org.sufficientlysecure.htmltextview.HtmlTextView;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.util.Log;
+
+
+public class HelpAboutFragment extends Fragment {
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.help_about_fragment, container, false);
+
+ TextView versionText = (TextView) view.findViewById(R.id.help_about_version);
+ versionText.setText(getString(R.string.help_about_version) + " " + getVersion());
+
+ HtmlTextView aboutTextView = (HtmlTextView) view.findViewById(R.id.help_about_text);
+
+ // load html from raw resource (Parsing handled by HtmlTextView library)
+ aboutTextView.setHtmlFromRawResource(getActivity(), R.raw.help_about);
+
+ // no flickering when clicking textview for Android < 4
+ aboutTextView.setTextColor(getResources().getColor(android.R.color.black));
+
+ return view;
+ }
+
+ /**
+ * Get the current package version.
+ *
+ * @return The current version.
+ */
+ private String getVersion() {
+ String result = "";
+ try {
+ PackageManager manager = getActivity().getPackageManager();
+ PackageInfo info = manager.getPackageInfo(getActivity().getPackageName(), 0);
+
+ result = String.format("%s (%s)", info.versionName, info.versionCode);
+ } catch (NameNotFoundException e) {
+ Log.w(Constants.TAG, "Unable to get application version: " + e.getMessage());
+ result = "Unable to get application version.";
+ }
+
+ return result;
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpActivity.java
new file mode 100644
index 000000000..32f37a0a5
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpActivity.java
@@ -0,0 +1,79 @@
+/*
+ * 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;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v4.view.ViewPager;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.ActionBarActivity;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.ui.adapter.TabsAdapter;
+
+public class HelpActivity extends ActionBarActivity {
+ public static final String EXTRA_SELECTED_TAB = "selected_tab";
+
+ ViewPager mViewPager;
+ TabsAdapter mTabsAdapter;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.help_activity);
+
+ mViewPager = (ViewPager) findViewById(R.id.pager);
+
+ final ActionBar actionBar = getSupportActionBar();
+ actionBar.setDisplayShowTitleEnabled(true);
+ actionBar.setDisplayHomeAsUpEnabled(false);
+ actionBar.setHomeButtonEnabled(false);
+ actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
+
+ mTabsAdapter = new TabsAdapter(this, mViewPager);
+
+ int selectedTab = 0;
+ Intent intent = getIntent();
+ if (intent.getExtras() != null && intent.getExtras().containsKey(EXTRA_SELECTED_TAB)) {
+ selectedTab = intent.getExtras().getInt(EXTRA_SELECTED_TAB);
+ }
+
+ Bundle startBundle = new Bundle();
+ startBundle.putInt(HelpHtmlFragment.ARG_HTML_FILE, R.raw.help_start);
+ mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_start)),
+ HelpHtmlFragment.class, startBundle, (selectedTab == 0));
+
+ Bundle faqBundle = new Bundle();
+ faqBundle.putInt(HelpHtmlFragment.ARG_HTML_FILE, R.raw.help_faq);
+ mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_faq)),
+ HelpHtmlFragment.class, faqBundle, (selectedTab == 1));
+
+ Bundle nfcBundle = new Bundle();
+ nfcBundle.putInt(HelpHtmlFragment.ARG_HTML_FILE, R.raw.help_nfc_beam);
+ mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_nfc_beam)),
+ HelpHtmlFragment.class, nfcBundle, (selectedTab == 2));
+
+ Bundle changelogBundle = new Bundle();
+ changelogBundle.putInt(HelpHtmlFragment.ARG_HTML_FILE, R.raw.help_changelog);
+ mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_changelog)),
+ HelpHtmlFragment.class, changelogBundle, (selectedTab == 3));
+
+ mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_about)),
+ HelpAboutFragment.class, null, (selectedTab == 4));
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpHtmlFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpHtmlFragment.java
new file mode 100644
index 000000000..6b3c51b08
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpHtmlFragment.java
@@ -0,0 +1,75 @@
+/*
+ * 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.ui;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ScrollView;
+import org.sufficientlysecure.htmltextview.HtmlTextView;
+
+public class HelpHtmlFragment extends Fragment {
+ private Activity mActivity;
+
+ private int mHtmlFile;
+
+ public static final String ARG_HTML_FILE = "htmlFile";
+
+ /**
+ * Create a new instance of HelpHtmlFragment, providing "htmlFile" as an argument.
+ */
+ static HelpHtmlFragment newInstance(int htmlFile) {
+ HelpHtmlFragment f = new HelpHtmlFragment();
+
+ // Supply html raw file input as an argument.
+ Bundle args = new Bundle();
+ args.putInt(ARG_HTML_FILE, htmlFile);
+ f.setArguments(args);
+
+ return f;
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ mActivity = getActivity();
+
+ mHtmlFile = getArguments().getInt(ARG_HTML_FILE);
+
+ ScrollView scroller = new ScrollView(mActivity);
+ HtmlTextView text = new HtmlTextView(mActivity);
+
+ // padding
+ int padding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16, mActivity
+ .getResources().getDisplayMetrics());
+ text.setPadding(padding, padding, padding, 0);
+
+ scroller.addView(text);
+
+ // load html from raw resource (Parsing handled by HtmlTextView library)
+ text.setHtmlFromRawResource(getActivity(), mHtmlFile);
+
+ // 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
new file mode 100644
index 000000000..6ea79473a
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java
@@ -0,0 +1,489 @@
+/*
+ * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2011 Senecaso
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.sufficientlysecure.keychain.ui;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.nfc.NdefMessage;
+import android.nfc.NfcAdapter;
+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.FragmentTransaction;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.ActionBarActivity;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.ArrayAdapter;
+
+import com.beardedhen.androidbootstrap.BootstrapButton;
+import com.devspark.appmsg.AppMsg;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.helper.ActionBarHelper;
+import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
+import org.sufficientlysecure.keychain.service.KeychainIntentService;
+import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
+import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry;
+import org.sufficientlysecure.keychain.ui.dialog.BadImportKeyDialogFragment;
+import org.sufficientlysecure.keychain.util.Log;
+
+import java.util.ArrayList;
+import java.util.Locale;
+
+public class ImportKeysActivity extends ActionBarActivity implements ActionBar.OnNavigationListener {
+ public static final String ACTION_IMPORT_KEY = Constants.INTENT_PREFIX + "IMPORT_KEY";
+ public static final String ACTION_IMPORT_KEY_FROM_QR_CODE = Constants.INTENT_PREFIX
+ + "IMPORT_KEY_FROM_QR_CODE";
+ public static final String ACTION_IMPORT_KEY_FROM_KEYSERVER = Constants.INTENT_PREFIX
+ + "IMPORT_KEY_FROM_KEYSERVER";
+ public static final String ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN = Constants.INTENT_PREFIX
+ + "IMPORT_KEY_FROM_KEY_SERVER_AND_RETURN";
+
+ // Actions for internal use only:
+ public static final String ACTION_IMPORT_KEY_FROM_FILE = Constants.INTENT_PREFIX
+ + "IMPORT_KEY_FROM_FILE";
+ public static final String ACTION_IMPORT_KEY_FROM_NFC = Constants.INTENT_PREFIX
+ + "IMPORT_KEY_FROM_NFC";
+
+ // only used by ACTION_IMPORT_KEY
+ public static final String EXTRA_KEY_BYTES = "key_bytes";
+
+ // only used by ACTION_IMPORT_KEY_FROM_KEYSERVER
+ public static final String EXTRA_QUERY = "query";
+ public static final String EXTRA_KEY_ID = "key_id";
+ public static final String EXTRA_FINGERPRINT = "fingerprint";
+
+ // only used by ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN when used from OpenPgpService
+ public static final String EXTRA_PENDING_INTENT_DATA = "data";
+ private Intent mPendingIntentData;
+
+ // view
+ private ImportKeysListFragment mListFragment;
+ private String[] mNavigationStrings;
+ private Fragment mCurrentFragment;
+ private BootstrapButton mImportButton;
+
+ private static final Class[] NAVIGATION_CLASSES = new Class[]{
+ ImportKeysServerFragment.class,
+ ImportKeysFileFragment.class,
+ ImportKeysQrCodeFragment.class,
+ ImportKeysClipboardFragment.class,
+ ImportKeysNFCFragment.class
+ };
+
+ private int mCurrentNavPosition = -1;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.import_keys_activity);
+
+ mImportButton = (BootstrapButton) findViewById(R.id.import_import);
+ mImportButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ importKeys();
+ }
+ });
+
+ mNavigationStrings = getResources().getStringArray(R.array.import_action_list);
+
+ if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN.equals(getIntent().getAction())) {
+ setTitle(R.string.nav_import);
+ } else {
+ ActionBarHelper.setBackButton(this);
+ getSupportActionBar().setDisplayShowTitleEnabled(false);
+
+ // set drop down navigation
+ Context context = getSupportActionBar().getThemedContext();
+ ArrayAdapter<CharSequence> navigationAdapter = ArrayAdapter.createFromResource(context,
+ R.array.import_action_list, android.R.layout.simple_spinner_dropdown_item);
+ getSupportActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
+ getSupportActionBar().setListNavigationCallbacks(navigationAdapter, this);
+ }
+
+ handleActions(savedInstanceState, getIntent());
+ }
+
+ protected void handleActions(Bundle savedInstanceState, Intent intent) {
+ String action = intent.getAction();
+ Bundle extras = intent.getExtras();
+ Uri dataUri = intent.getData();
+ String scheme = intent.getScheme();
+
+ if (extras == null) {
+ extras = new Bundle();
+ }
+
+ if (Intent.ACTION_VIEW.equals(action)) {
+ // Android's Action when opening file associated to Keychain (see AndroidManifest.xml)
+ // override action to delegate it to Keychain's ACTION_IMPORT_KEY
+ action = ACTION_IMPORT_KEY;
+ }
+
+ if (scheme != null && scheme.toLowerCase(Locale.ENGLISH).equals(Constants.FINGERPRINT_SCHEME)) {
+ /* Scanning a fingerprint directly with Barcode Scanner */
+ loadFromFingerprintUri(savedInstanceState, dataUri);
+ } else if (ACTION_IMPORT_KEY.equals(action)) {
+ /* Keychain's own Actions */
+
+ // display file fragment
+ loadNavFragment(1, null);
+
+ if (dataUri != null) {
+ // action: directly load data
+ startListFragment(savedInstanceState, null, dataUri, null);
+ } else if (extras.containsKey(EXTRA_KEY_BYTES)) {
+ byte[] importData = intent.getByteArrayExtra(EXTRA_KEY_BYTES);
+
+ // action: directly load data
+ startListFragment(savedInstanceState, importData, null, null);
+ }
+ } else if (ACTION_IMPORT_KEY_FROM_KEYSERVER.equals(action)
+ || ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN.equals(action)) {
+
+ // only used for OpenPgpService
+ if (extras.containsKey(EXTRA_PENDING_INTENT_DATA)) {
+ mPendingIntentData = extras.getParcelable(EXTRA_PENDING_INTENT_DATA);
+ }
+ if (extras.containsKey(EXTRA_QUERY) || extras.containsKey(EXTRA_KEY_ID)) {
+ /* simple search based on query or key id */
+
+ String query = null;
+ if (extras.containsKey(EXTRA_QUERY)) {
+ query = extras.getString(EXTRA_QUERY);
+ } else if (extras.containsKey(EXTRA_KEY_ID)) {
+ long keyId = intent.getLongExtra(EXTRA_KEY_ID, 0);
+ if (keyId != 0) {
+ query = PgpKeyHelper.convertKeyIdToHex(keyId);
+ }
+ }
+
+ if (query != null && query.length() > 0) {
+ // display keyserver fragment with query
+ Bundle args = new Bundle();
+ args.putString(ImportKeysServerFragment.ARG_QUERY, query);
+ loadNavFragment(0, args);
+
+ // action: search immediately
+ startListFragment(savedInstanceState, null, null, query);
+ } else {
+ Log.e(Constants.TAG, "Query is empty!");
+ return;
+ }
+ } else if (extras.containsKey(EXTRA_FINGERPRINT)) {
+ /*
+ * search based on fingerprint, here we can enforce a check in the end
+ * if the right key has been downloaded
+ */
+
+ String fingerprint = intent.getStringExtra(EXTRA_FINGERPRINT);
+ loadFromFingerprint(savedInstanceState, fingerprint);
+ } else {
+ Log.e(Constants.TAG,
+ "IMPORT_KEY_FROM_KEYSERVER action needs to contain the 'query', 'key_id', or " +
+ "'fingerprint' extra!");
+ return;
+ }
+ } else if (ACTION_IMPORT_KEY_FROM_FILE.equals(action)) {
+
+ // NOTE: this only displays the appropriate fragment, no actions are taken
+ loadNavFragment(1, null);
+
+ // no immediate actions!
+ startListFragment(savedInstanceState, null, null, null);
+ } else if (ACTION_IMPORT_KEY_FROM_QR_CODE.equals(action)) {
+ // also exposed in AndroidManifest
+
+ // NOTE: this only displays the appropriate fragment, no actions are taken
+ loadNavFragment(2, null);
+
+ // no immediate actions!
+ startListFragment(savedInstanceState, null, null, null);
+ } else if (ACTION_IMPORT_KEY_FROM_NFC.equals(action)) {
+
+ // NOTE: this only displays the appropriate fragment, no actions are taken
+ loadNavFragment(3, null);
+
+ // no immediate actions!
+ startListFragment(savedInstanceState, null, null, null);
+ } else {
+ startListFragment(savedInstanceState, null, null, null);
+ }
+ }
+
+ private void startListFragment(Bundle savedInstanceState, byte[] bytes, Uri dataUri, String serverQuery) {
+ // 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
+ mListFragment = ImportKeysListFragment.newInstance(bytes, dataUri, serverQuery);
+
+ // Add the fragment to the 'fragment_container' FrameLayout
+ // NOTE: We use commitAllowingStateLoss() to prevent weird crashes!
+ getSupportFragmentManager().beginTransaction()
+ .replace(R.id.import_keys_list_container, mListFragment)
+ .commitAllowingStateLoss();
+ // do it immediately!
+ getSupportFragmentManager().executePendingTransactions();
+ }
+
+ /**
+ * "Basically, when using a list navigation, onNavigationItemSelected() is automatically
+ * called when your activity is created/re-created, whether you like it or not. To prevent
+ * your Fragment's onCreateView() from being called twice, this initial automatic call to
+ * onNavigationItemSelected() should check whether the Fragment is already in existence
+ * inside your Activity."
+ * <p/>
+ * from http://stackoverflow.com/a/14295474
+ * <p/>
+ * In our case, if we start ImportKeysActivity with parameters to directly search using a fingerprint,
+ * the fragment would be loaded twice resulting in the query being empty after the second load.
+ * <p/>
+ * Our solution:
+ * To prevent that a fragment will be loaded again even if it was already loaded loadNavFragment
+ * checks against mCurrentNavPosition.
+ *
+ * @param itemPosition
+ * @param itemId
+ * @return
+ */
+ @Override
+ public boolean onNavigationItemSelected(int itemPosition, long itemId) {
+ Log.d(Constants.TAG, "onNavigationItemSelected");
+
+ loadNavFragment(itemPosition, null);
+
+ return true;
+ }
+
+ private void loadNavFragment(int itemPosition, Bundle args) {
+ if (mCurrentNavPosition != itemPosition) {
+ if (ActionBar.NAVIGATION_MODE_LIST == getSupportActionBar().getNavigationMode()) {
+ getSupportActionBar().setSelectedNavigationItem(itemPosition);
+ }
+ loadFragment(NAVIGATION_CLASSES[itemPosition], args, mNavigationStrings[itemPosition]);
+ mCurrentNavPosition = itemPosition;
+ }
+ }
+
+ private void loadFragment(Class<?> clss, Bundle args, String tag) {
+ mCurrentFragment = Fragment.instantiate(this, clss.getName(), args);
+
+ FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
+ // Replace whatever is in the fragment container with this fragment
+ // and give the fragment a tag name equal to the string at the position selected
+ ft.replace(R.id.import_navigation_fragment, mCurrentFragment, tag);
+ // Apply changes
+ ft.commit();
+ }
+
+ public void loadFromFingerprintUri(Bundle savedInstanceState, Uri dataUri) {
+ String fingerprint = dataUri.toString().split(":")[1].toLowerCase(Locale.ENGLISH);
+
+ Log.d(Constants.TAG, "fingerprint: " + fingerprint);
+
+ loadFromFingerprint(savedInstanceState, fingerprint);
+ }
+
+ public void loadFromFingerprint(Bundle savedInstanceState, String fingerprint) {
+ if (fingerprint == null || fingerprint.length() < 40) {
+ AppMsg.makeText(this, R.string.import_qr_code_too_short_fingerprint,
+ AppMsg.STYLE_ALERT).show();
+ return;
+ }
+
+ String query = "0x" + fingerprint;
+
+ // display keyserver fragment with query
+ Bundle args = new Bundle();
+ args.putString(ImportKeysServerFragment.ARG_QUERY, query);
+ args.putBoolean(ImportKeysServerFragment.ARG_DISABLE_QUERY_EDIT, true);
+ loadNavFragment(0, args);
+
+ // action: search directly
+ startListFragment(savedInstanceState, null, null, query);
+ }
+
+ public void loadCallback(byte[] importData, Uri dataUri, String serverQuery, String keyServer) {
+ mListFragment.loadNew(importData, dataUri, serverQuery, keyServer);
+ }
+
+ /**
+ * Import keys with mImportData
+ */
+ public void importKeys() {
+ // Message is received after importing is done in KeychainIntentService
+ KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(
+ this,
+ getString(R.string.progress_importing),
+ ProgressDialog.STYLE_HORIZONTAL) {
+ public void handleMessage(Message message) {
+ // handle messages by standard KeychainIntentServiceHandler first
+ super.handleMessage(message);
+
+ if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
+ // get returned data bundle
+ Bundle returnData = message.getData();
+
+ int added = returnData.getInt(KeychainIntentService.RESULT_IMPORT_ADDED);
+ int updated = returnData
+ .getInt(KeychainIntentService.RESULT_IMPORT_UPDATED);
+ int bad = returnData.getInt(KeychainIntentService.RESULT_IMPORT_BAD);
+ String toastMessage;
+ if (added > 0 && updated > 0) {
+ String addedStr = getResources().getQuantityString(
+ R.plurals.keys_added_and_updated_1, added, added);
+ String updatedStr = getResources().getQuantityString(
+ R.plurals.keys_added_and_updated_2, updated, updated);
+ toastMessage = addedStr + updatedStr;
+ } else if (added > 0) {
+ toastMessage = getResources().getQuantityString(R.plurals.keys_added,
+ added, added);
+ } else if (updated > 0) {
+ toastMessage = getResources().getQuantityString(R.plurals.keys_updated,
+ updated, updated);
+ } else {
+ toastMessage = getString(R.string.no_keys_added_or_updated);
+ }
+ AppMsg.makeText(ImportKeysActivity.this, toastMessage, AppMsg.STYLE_INFO)
+ .show();
+ if (bad > 0) {
+ BadImportKeyDialogFragment badImportKeyDialogFragment =
+ BadImportKeyDialogFragment.newInstance(bad);
+ badImportKeyDialogFragment.show(getSupportFragmentManager(), "badKeyDialog");
+ }
+
+ if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN.equals(getIntent().getAction())) {
+ ImportKeysActivity.this.setResult(Activity.RESULT_OK, mPendingIntentData);
+ finish();
+ }
+ }
+ }
+ };
+
+ if (mListFragment.getKeyBytes() != null || mListFragment.getDataUri() != null) {
+ Log.d(Constants.TAG, "importKeys started");
+
+ // Send all information needed to service to import key in other thread
+ Intent intent = new Intent(this, KeychainIntentService.class);
+
+ intent.setAction(KeychainIntentService.ACTION_IMPORT_KEYRING);
+
+ // fill values for this action
+ Bundle data = new Bundle();
+
+ // get selected key entries
+ ArrayList<ImportKeysListEntry> selectedEntries = mListFragment.getSelectedData();
+ data.putParcelableArrayList(KeychainIntentService.IMPORT_KEY_LIST, selectedEntries);
+
+ intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
+
+ // Create a new Messenger for the communication back
+ Messenger messenger = new Messenger(saveHandler);
+ intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
+
+ // show progress dialog
+ saveHandler.showProgressDialog(this);
+
+ // start service with intent
+ startService(intent);
+ } else if (mListFragment.getServerQuery() != null) {
+ // Send all information needed to service to query keys in other thread
+ Intent intent = new Intent(this, KeychainIntentService.class);
+
+ intent.setAction(KeychainIntentService.ACTION_DOWNLOAD_AND_IMPORT_KEYS);
+
+ // fill values for this action
+ Bundle data = new Bundle();
+
+ data.putString(KeychainIntentService.DOWNLOAD_KEY_SERVER, mListFragment.getKeyServer());
+
+ // get selected key entries
+ ArrayList<ImportKeysListEntry> selectedEntries = mListFragment.getSelectedData();
+ data.putParcelableArrayList(KeychainIntentService.DOWNLOAD_KEY_LIST, selectedEntries);
+
+ intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
+
+ // Create a new Messenger for the communication back
+ Messenger messenger = new Messenger(saveHandler);
+ intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
+
+ // show progress dialog
+ saveHandler.showProgressDialog(this);
+
+ // start service with intent
+ startService(intent);
+ } else {
+ AppMsg.makeText(this, R.string.error_nothing_import, AppMsg.STYLE_ALERT).show();
+ }
+ }
+
+ /**
+ * NFC
+ */
+ @Override
+ public void onResume() {
+ super.onResume();
+ // Check to see that the Activity started due to an Android Beam
+ if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) {
+ handleActionNdefDiscovered(getIntent());
+ }
+ }
+
+ /**
+ * NFC
+ */
+ @Override
+ public void onNewIntent(Intent intent) {
+ // onResume gets called after this to handle the intent
+ setIntent(intent);
+ }
+
+ /**
+ * NFC: Parses the NDEF Message from the intent and prints to the TextView
+ */
+ @SuppressLint("NewApi")
+ void handleActionNdefDiscovered(Intent intent) {
+ Parcelable[] rawMsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
+ // 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();
+
+ Intent importIntent = new Intent(this, ImportKeysActivity.class);
+ importIntent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY);
+ importIntent.putExtra(ImportKeysActivity.EXTRA_KEY_BYTES, receivedKeyringBytes);
+
+ handleActions(null, importIntent);
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysClipboardFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysClipboardFragment.java
new file mode 100644
index 000000000..28e2091a9
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysClipboardFragment.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.ui;
+
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import com.beardedhen.androidbootstrap.BootstrapButton;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
+
+import java.util.Locale;
+
+public class ImportKeysClipboardFragment extends Fragment {
+
+ private ImportKeysActivity mImportActivity;
+ private BootstrapButton mButton;
+
+ /**
+ * Creates new instance of this fragment
+ */
+ public static ImportKeysClipboardFragment newInstance() {
+ ImportKeysClipboardFragment frag = new ImportKeysClipboardFragment();
+
+ Bundle args = new Bundle();
+ frag.setArguments(args);
+
+ return frag;
+ }
+
+ /**
+ * Inflate the layout for this fragment
+ */
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.import_keys_clipboard_fragment, container, false);
+
+ mButton = (BootstrapButton) view.findViewById(R.id.import_clipboard_button);
+ mButton.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ CharSequence clipboardText = ClipboardReflection.getClipboardText(getActivity());
+ String sendText = "";
+ if (clipboardText != null) {
+ sendText = clipboardText.toString();
+ if (sendText.toLowerCase(Locale.ENGLISH).startsWith(Constants.FINGERPRINT_SCHEME)) {
+ mImportActivity.loadFromFingerprintUri(null, Uri.parse(sendText));
+ return;
+ }
+ }
+ mImportActivity.loadCallback(sendText.getBytes(), null, null, null);
+ }
+ });
+
+ return view;
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ mImportActivity = (ImportKeysActivity) getActivity();
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java
new file mode 100644
index 000000000..31d5f3fd0
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.ui;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import com.beardedhen.androidbootstrap.BootstrapButton;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.Id;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.helper.FileHelper;
+
+public class ImportKeysFileFragment extends Fragment {
+ private ImportKeysActivity mImportActivity;
+ private BootstrapButton mBrowse;
+
+ /**
+ * Creates new instance of this fragment
+ */
+ public static ImportKeysFileFragment newInstance() {
+ ImportKeysFileFragment frag = new ImportKeysFileFragment();
+
+ Bundle args = new Bundle();
+
+ frag.setArguments(args);
+ return frag;
+ }
+
+ /**
+ * Inflate the layout for this fragment
+ */
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.import_keys_file_fragment, container, false);
+
+ mBrowse = (BootstrapButton) view.findViewById(R.id.import_keys_file_browse);
+
+ mBrowse.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ // open .asc or .gpg files
+ // setting it to text/plain prevents Cynaogenmod's file manager from selecting asc
+ // or gpg types!
+ FileHelper.openFile(ImportKeysFileFragment.this, Constants.Path.APP_DIR + "/",
+ "*/*", Id.request.filename);
+ }
+ });
+
+ return view;
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ mImportActivity = (ImportKeysActivity) getActivity();
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ switch (requestCode & 0xFFFF) {
+ case Id.request.filename: {
+ if (resultCode == Activity.RESULT_OK && data != null) {
+
+ // load data
+ mImportActivity.loadCallback(null, data.getData(), null, null);
+ }
+
+ break;
+ }
+
+ default:
+ super.onActivityResult(requestCode, resultCode, data);
+
+ break;
+ }
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java
new file mode 100644
index 000000000..077fa0cab
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java
@@ -0,0 +1,305 @@
+/*
+ * 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;
+
+import android.app.Activity;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v4.app.ListFragment;
+import android.support.v4.app.LoaderManager;
+import android.support.v4.content.Loader;
+import android.view.View;
+import android.widget.ListView;
+import com.devspark.appmsg.AppMsg;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.helper.Preferences;
+import org.sufficientlysecure.keychain.ui.adapter.AsyncTaskResultWrapper;
+import org.sufficientlysecure.keychain.ui.adapter.ImportKeysAdapter;
+import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry;
+import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListLoader;
+import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListServerLoader;
+import org.sufficientlysecure.keychain.util.InputData;
+import org.sufficientlysecure.keychain.util.KeyServer;
+import org.sufficientlysecure.keychain.util.Log;
+
+import java.io.ByteArrayInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+public class ImportKeysListFragment extends ListFragment implements
+ LoaderManager.LoaderCallbacks<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> {
+ private static final String ARG_DATA_URI = "uri";
+ private static final String ARG_BYTES = "bytes";
+ private static final String ARG_SERVER_QUERY = "query";
+
+ private Activity mActivity;
+ private ImportKeysAdapter mAdapter;
+
+ private byte[] mKeyBytes;
+ private Uri mDataUri;
+ private String mServerQuery;
+ private String mKeyServer;
+
+ private static final int LOADER_ID_BYTES = 0;
+ private static final int LOADER_ID_SERVER_QUERY = 1;
+
+ public byte[] getKeyBytes() {
+ return mKeyBytes;
+ }
+
+ public Uri getDataUri() {
+ return mDataUri;
+ }
+
+ public String getServerQuery() {
+ return mServerQuery;
+ }
+
+ public String getKeyServer() {
+ return mKeyServer;
+ }
+
+ public List<ImportKeysListEntry> getData() {
+ return mAdapter.getData();
+ }
+
+ public ArrayList<ImportKeysListEntry> getSelectedData() {
+ return mAdapter.getSelectedData();
+ }
+
+ /**
+ * Creates new instance of this fragment
+ */
+ public static ImportKeysListFragment newInstance(byte[] bytes, Uri dataUri, String serverQuery) {
+ ImportKeysListFragment frag = new ImportKeysListFragment();
+
+ Bundle args = new Bundle();
+ args.putByteArray(ARG_BYTES, bytes);
+ args.putParcelable(ARG_DATA_URI, dataUri);
+ args.putString(ARG_SERVER_QUERY, serverQuery);
+
+ frag.setArguments(args);
+
+ return frag;
+ }
+
+ /**
+ * Define Adapter and Loader on create of Activity
+ */
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ mActivity = getActivity();
+
+ // Give some text to display if there is no data. In a real
+ // application this would come from a resource.
+ setEmptyText(mActivity.getString(R.string.error_nothing_import));
+
+ // Create an empty adapter we will use to display the loaded data.
+ mAdapter = new ImportKeysAdapter(mActivity);
+ setListAdapter(mAdapter);
+
+ mDataUri = getArguments().getParcelable(ARG_DATA_URI);
+ mKeyBytes = getArguments().getByteArray(ARG_BYTES);
+ mServerQuery = getArguments().getString(ARG_SERVER_QUERY);
+
+ // TODO: this is used when scanning QR Code. Currently it simply uses keyserver nr 0
+ mKeyServer = Preferences.getPreferences(getActivity())
+ .getKeyServers()[0];
+
+ if (mDataUri != null || mKeyBytes != null) {
+ // Start out with a progress indicator.
+ setListShown(false);
+
+ // Prepare the loader. Either re-connect with an existing one,
+ // or start a new one.
+ // give arguments to onCreateLoader()
+ getLoaderManager().initLoader(LOADER_ID_BYTES, null, this);
+ }
+
+ if (mServerQuery != null && mKeyServer != null) {
+ // Start out with a progress indicator.
+ setListShown(false);
+
+ // Prepare the loader. Either re-connect with an existing one,
+ // or start a new one.
+ // give arguments to onCreateLoader()
+ getLoaderManager().initLoader(LOADER_ID_SERVER_QUERY, null, this);
+ }
+ }
+
+ @Override
+ public void onListItemClick(ListView l, View v, int position, long id) {
+ super.onListItemClick(l, v, position, id);
+
+ // Select checkbox!
+ // Update underlying data and notify adapter of change. The adapter will
+ // update the view automatically.
+ ImportKeysListEntry entry = mAdapter.getItem(position);
+ entry.setSelected(!entry.isSelected());
+ mAdapter.notifyDataSetChanged();
+ }
+
+ public void loadNew(byte[] keyBytes, Uri dataUri, String serverQuery, String keyServer) {
+ mKeyBytes = keyBytes;
+ mDataUri = dataUri;
+ mServerQuery = serverQuery;
+ mKeyServer = keyServer;
+
+ if (mKeyBytes != null || mDataUri != null) {
+ // Start out with a progress indicator.
+ setListShown(false);
+
+ getLoaderManager().restartLoader(LOADER_ID_BYTES, null, this);
+ }
+
+ if (mServerQuery != null && mKeyServer != null) {
+ // Start out with a progress indicator.
+ setListShown(false);
+
+ getLoaderManager().restartLoader(LOADER_ID_SERVER_QUERY, null, this);
+ }
+ }
+
+ @Override
+ public Loader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>>
+ onCreateLoader(int id, Bundle args) {
+ switch (id) {
+ case LOADER_ID_BYTES: {
+ InputData inputData = getInputData(mKeyBytes, mDataUri);
+ return new ImportKeysListLoader(mActivity, inputData);
+ }
+ case LOADER_ID_SERVER_QUERY: {
+ return new ImportKeysListServerLoader(getActivity(), mServerQuery, mKeyServer);
+ }
+
+ default:
+ return null;
+ }
+ }
+
+ @Override
+ public void onLoadFinished(Loader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> loader,
+ AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> data) {
+ // Swap the new cursor in. (The framework will take care of closing the
+ // old cursor once we return.)
+
+ Log.d(Constants.TAG, "data: " + data.getResult());
+
+ // swap in the real data!
+ mAdapter.setData(data.getResult());
+ mAdapter.notifyDataSetChanged();
+
+ setListAdapter(mAdapter);
+
+ // The list should now be shown.
+ if (isResumed()) {
+ setListShown(true);
+ } else {
+ setListShownNoAnimation(true);
+ }
+
+ Exception error = data.getError();
+
+ switch (loader.getId()) {
+ case LOADER_ID_BYTES:
+
+ if (error == null) {
+ // No error
+ } else if (error instanceof ImportKeysListLoader.FileHasNoContent) {
+ AppMsg.makeText(getActivity(), R.string.error_import_file_no_content,
+ AppMsg.STYLE_ALERT).show();
+ } else if (error instanceof ImportKeysListLoader.NonPgpPart) {
+ AppMsg.makeText(getActivity(),
+ ((ImportKeysListLoader.NonPgpPart) error).getCount() + " " + getResources().
+ getQuantityString(R.plurals.error_import_non_pgp_part,
+ ((ImportKeysListLoader.NonPgpPart) error).getCount()),
+ new AppMsg.Style(AppMsg.LENGTH_LONG, R.color.confirm)).show();
+ } else {
+ AppMsg.makeText(getActivity(), R.string.error_generic_report_bug,
+ new AppMsg.Style(AppMsg.LENGTH_LONG, R.color.alert)).show();
+ }
+ break;
+
+ case LOADER_ID_SERVER_QUERY:
+
+ if (error == null) {
+ AppMsg.makeText(
+ getActivity(), getResources().getQuantityString(R.plurals.keys_found,
+ mAdapter.getCount(), mAdapter.getCount()),
+ AppMsg.STYLE_INFO
+ ).show();
+ } else if (error instanceof KeyServer.InsufficientQuery) {
+ AppMsg.makeText(getActivity(), R.string.error_keyserver_insufficient_query,
+ AppMsg.STYLE_ALERT).show();
+ } else if (error instanceof KeyServer.QueryException) {
+ AppMsg.makeText(getActivity(), R.string.error_keyserver_query,
+ AppMsg.STYLE_ALERT).show();
+ } else if (error instanceof KeyServer.TooManyResponses) {
+ AppMsg.makeText(getActivity(), R.string.error_keyserver_too_many_responses,
+ AppMsg.STYLE_ALERT).show();
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ @Override
+ public void onLoaderReset(Loader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> loader) {
+ switch (loader.getId()) {
+ case LOADER_ID_BYTES:
+ // Clear the data in the adapter.
+ mAdapter.clear();
+ break;
+ case LOADER_ID_SERVER_QUERY:
+ // Clear the data in the adapter.
+ mAdapter.clear();
+ break;
+ default:
+ break;
+ }
+ }
+
+ private InputData getInputData(byte[] importBytes, Uri dataUri) {
+ InputData inputData = null;
+ if (importBytes != null) {
+ inputData = new InputData(new ByteArrayInputStream(importBytes), importBytes.length);
+ } else if (dataUri != null) {
+ try {
+ InputStream inputStream = getActivity().getContentResolver().openInputStream(dataUri);
+ int length = inputStream.available();
+
+ inputData = new InputData(inputStream, length);
+ } catch (FileNotFoundException e) {
+ Log.e(Constants.TAG, "FileNotFoundException!", e);
+ } catch (IOException e) {
+ Log.e(Constants.TAG, "IOException!", e);
+ }
+ }
+
+ return inputData;
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysNFCFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysNFCFragment.java
new file mode 100644
index 000000000..110647284
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysNFCFragment.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.ui;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import com.beardedhen.androidbootstrap.BootstrapButton;
+import org.sufficientlysecure.keychain.R;
+
+public class ImportKeysNFCFragment extends Fragment {
+
+ private BootstrapButton mButton;
+
+ /**
+ * Creates new instance of this fragment
+ */
+ public static ImportKeysNFCFragment newInstance() {
+ ImportKeysNFCFragment frag = new ImportKeysNFCFragment();
+
+ Bundle args = new Bundle();
+ frag.setArguments(args);
+
+ return frag;
+ }
+
+ /**
+ * Inflate the layout for this fragment
+ */
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.import_keys_nfc_fragment, container, false);
+
+ mButton = (BootstrapButton) view.findViewById(R.id.import_nfc_button);
+ mButton.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ // show nfc help
+ Intent intent = new Intent(getActivity(), HelpActivity.class);
+ intent.putExtra(HelpActivity.EXTRA_SELECTED_TAB, 2);
+ startActivityForResult(intent, 0);
+ }
+ });
+
+ return view;
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysQrCodeFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysQrCodeFragment.java
new file mode 100644
index 000000000..8b553d273
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysQrCodeFragment.java
@@ -0,0 +1,205 @@
+/*
+ * 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;
+
+import com.google.zxing.integration.android.IntentResult;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.beardedhen.androidbootstrap.BootstrapButton;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.util.IntentIntegratorSupportV4;
+import org.sufficientlysecure.keychain.util.Log;
+
+import java.util.ArrayList;
+import java.util.Locale;
+
+public class ImportKeysQrCodeFragment extends Fragment {
+
+ private ImportKeysActivity mImportActivity;
+ private BootstrapButton mButton;
+ private TextView mText;
+ private ProgressBar mProgress;
+
+ private String[] mScannedContent;
+
+ /**
+ * Creates new instance of this fragment
+ */
+ public static ImportKeysQrCodeFragment newInstance() {
+ ImportKeysQrCodeFragment frag = new ImportKeysQrCodeFragment();
+
+ Bundle args = new Bundle();
+ frag.setArguments(args);
+
+ return frag;
+ }
+
+ /**
+ * Inflate the layout for this fragment
+ */
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.import_keys_qr_code_fragment, container, false);
+
+ mButton = (BootstrapButton) view.findViewById(R.id.import_qrcode_button);
+ mText = (TextView) view.findViewById(R.id.import_qrcode_text);
+ mProgress = (ProgressBar) view.findViewById(R.id.import_qrcode_progress);
+
+ mButton.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ // scan using xzing's Barcode Scanner
+ new IntentIntegratorSupportV4(ImportKeysQrCodeFragment.this).initiateScan();
+ }
+ });
+
+ return view;
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ mImportActivity = (ImportKeysActivity) getActivity();
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ switch (requestCode & 0xFFFF) {
+ case IntentIntegratorSupportV4.REQUEST_CODE: {
+ IntentResult scanResult = IntentIntegratorSupportV4.parseActivityResult(requestCode,
+ resultCode, data);
+ if (scanResult != null && scanResult.getFormatName() != null) {
+ String scannedContent = scanResult.getContents();
+
+ Log.d(Constants.TAG, "scannedContent: " + scannedContent);
+
+ // look if it's fingerprint only
+ if (scannedContent.toLowerCase(Locale.ENGLISH).startsWith(Constants.FINGERPRINT_SCHEME)) {
+ importFingerprint(Uri.parse(scanResult.getContents()));
+ return;
+ }
+
+ // look if it is the whole key
+ String[] parts = scannedContent.split(",");
+ if (parts.length == 3) {
+ importParts(parts);
+ return;
+ }
+
+ // is this a full key encoded as qr code?
+ if (scannedContent.startsWith("-----BEGIN PGP")) {
+ mImportActivity.loadCallback(scannedContent.getBytes(), null, null, null);
+ return;
+ }
+
+ // fail...
+ Toast.makeText(getActivity(), R.string.import_qr_code_wrong, Toast.LENGTH_LONG)
+ .show();
+ }
+
+ break;
+ }
+
+ default:
+ super.onActivityResult(requestCode, resultCode, data);
+
+ break;
+ }
+ }
+
+ public void importFingerprint(Uri dataUri) {
+ mImportActivity.loadFromFingerprintUri(null, dataUri);
+ }
+
+ private void importParts(String[] parts) {
+ int counter = Integer.valueOf(parts[0]);
+ int size = Integer.valueOf(parts[1]);
+ String content = parts[2];
+
+ Log.d(Constants.TAG, "" + counter);
+ Log.d(Constants.TAG, "" + size);
+ Log.d(Constants.TAG, "" + content);
+
+ // first qr code -> setup
+ if (counter == 0) {
+ mScannedContent = new String[size];
+ mProgress.setMax(size);
+ mProgress.setVisibility(View.VISIBLE);
+ mText.setVisibility(View.VISIBLE);
+ }
+
+ if (mScannedContent == null || counter > mScannedContent.length) {
+ Toast.makeText(getActivity(), R.string.import_qr_code_start_with_one, Toast.LENGTH_LONG)
+ .show();
+ return;
+ }
+
+ // save scanned content
+ mScannedContent[counter] = content;
+
+ // get missing numbers
+ ArrayList<Integer> missing = new ArrayList<Integer>();
+ for (int i = 0; i < mScannedContent.length; i++) {
+ if (mScannedContent[i] == null) {
+ missing.add(i);
+ }
+ }
+
+ // update progress and text
+ int alreadyScanned = mScannedContent.length - missing.size();
+ mProgress.setProgress(alreadyScanned);
+
+ String missingString = "";
+ for (int m : missing) {
+ if (!missingString.equals("")) {
+ missingString += ", ";
+ }
+ missingString += String.valueOf(m + 1);
+ }
+
+ String missingText = getResources().getQuantityString(R.plurals.import_qr_code_missing,
+ missing.size(), missingString);
+ mText.setText(missingText);
+
+ // finished!
+ if (missing.size() == 0) {
+ mText.setText(R.string.import_qr_code_finished);
+ String result = "";
+ for (String in : mScannedContent) {
+ result += in;
+ }
+ mImportActivity.loadCallback(result.getBytes(), null, null, null);
+ }
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysServerFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysServerFragment.java
new file mode 100644
index 000000000..3eb463dac
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysServerFragment.java
@@ -0,0 +1,155 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.ArrayAdapter;
+import android.widget.EditText;
+import android.widget.Spinner;
+import android.widget.TextView;
+import com.beardedhen.androidbootstrap.BootstrapButton;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.helper.Preferences;
+import org.sufficientlysecure.keychain.util.Log;
+
+public class ImportKeysServerFragment extends Fragment {
+ public static final String ARG_QUERY = "query";
+ public static final String ARG_KEY_SERVER = "key_server";
+ public static final String ARG_DISABLE_QUERY_EDIT = "disable_query_edit";
+
+ private ImportKeysActivity mImportActivity;
+
+ private BootstrapButton mSearchButton;
+ private EditText mQueryEditText;
+ private Spinner mServerSpinner;
+ private ArrayAdapter<String> mServerAdapter;
+
+ /**
+ * Creates new instance of this fragment
+ */
+ public static ImportKeysServerFragment newInstance(String query, String keyServer) {
+ ImportKeysServerFragment frag = new ImportKeysServerFragment();
+
+ Bundle args = new Bundle();
+ args.putString(ARG_QUERY, query);
+ args.putString(ARG_KEY_SERVER, keyServer);
+
+ frag.setArguments(args);
+
+ return frag;
+ }
+
+ /**
+ * Inflate the layout for this fragment
+ */
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.import_keys_server_fragment, container, false);
+
+ mSearchButton = (BootstrapButton) view.findViewById(R.id.import_server_search);
+ mQueryEditText = (EditText) view.findViewById(R.id.import_server_query);
+ mServerSpinner = (Spinner) view.findViewById(R.id.import_server_spinner);
+
+ // add keyservers to spinner
+ mServerAdapter = new ArrayAdapter<String>(getActivity(),
+ android.R.layout.simple_spinner_item, Preferences.getPreferences(getActivity())
+ .getKeyServers());
+ mServerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ mServerSpinner.setAdapter(mServerAdapter);
+ if (mServerAdapter.getCount() > 0) {
+ mServerSpinner.setSelection(0);
+ } else {
+ mSearchButton.setEnabled(false);
+ }
+
+ mSearchButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ String query = mQueryEditText.getText().toString();
+ String keyServer = (String) mServerSpinner.getSelectedItem();
+ search(query, keyServer);
+
+ // close keyboard after pressing search
+ InputMethodManager imm =
+ (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.hideSoftInputFromWindow(mQueryEditText.getWindowToken(), 0);
+ }
+ });
+
+ mQueryEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
+ @Override
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ if (actionId == EditorInfo.IME_ACTION_SEARCH) {
+ String query = mQueryEditText.getText().toString();
+ String keyServer = (String) mServerSpinner.getSelectedItem();
+ search(query, keyServer);
+
+ // Don't return true to let the keyboard close itself after pressing search
+ return false;
+ }
+ return false;
+ }
+ });
+
+ return view;
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ mImportActivity = (ImportKeysActivity) getActivity();
+
+ // set displayed values
+ if (getArguments() != null) {
+ if (getArguments().containsKey(ARG_QUERY)) {
+ String query = getArguments().getString(ARG_QUERY);
+ mQueryEditText.setText(query, TextView.BufferType.EDITABLE);
+
+ Log.d(Constants.TAG, "query: " + query);
+ }
+
+ if (getArguments().containsKey(ARG_KEY_SERVER)) {
+ String keyServer = getArguments().getString(ARG_KEY_SERVER);
+ int keyServerPos = mServerAdapter.getPosition(keyServer);
+ mServerSpinner.setSelection(keyServerPos);
+
+ Log.d(Constants.TAG, "keyServer: " + keyServer);
+ }
+
+ if (getArguments().getBoolean(ARG_DISABLE_QUERY_EDIT, false)) {
+ mQueryEditText.setEnabled(false);
+ }
+ }
+ }
+
+ private void search(String query, String keyServer) {
+ mImportActivity.loadCallback(null, null, query, keyServer);
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java
new file mode 100644
index 000000000..8db643583
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java
@@ -0,0 +1,95 @@
+/*
+ * 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;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuItem;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.helper.ExportHelper;
+
+public class KeyListActivity extends DrawerActivity {
+
+ ExportHelper mExportHelper;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mExportHelper = new ExportHelper(this);
+
+ setContentView(R.layout.key_list_activity);
+
+ // now setup navigation drawer in DrawerActivity...
+ setupDrawerNavigation(savedInstanceState);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ getMenuInflater().inflate(R.menu.key_list, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.menu_key_list_import:
+ importKeys();
+ return true;
+
+ case R.id.menu_key_list_create:
+ createKey();
+ return true;
+
+ case R.id.menu_key_list_create_expert:
+ createKeyExpert();
+ return true;
+
+ case R.id.menu_key_list_export:
+ mExportHelper.showExportKeysDialog(null, Constants.Path.APP_DIR_FILE, true);
+ return true;
+
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ private void importKeys() {
+ Intent intent = new Intent(this, ImportKeysActivity.class);
+ startActivityForResult(intent, 0);
+ }
+
+ private void createKey() {
+ Intent intent = new Intent(this, EditKeyActivity.class);
+ intent.setAction(EditKeyActivity.ACTION_CREATE_KEY);
+ intent.putExtra(EditKeyActivity.EXTRA_GENERATE_DEFAULT_KEYS, true);
+ intent.putExtra(EditKeyActivity.EXTRA_USER_IDS, ""); // show user id view
+ startActivityForResult(intent, 0);
+ }
+
+ private void createKeyExpert() {
+ Intent intent = new Intent(this, EditKeyActivity.class);
+ intent.setAction(EditKeyActivity.ACTION_CREATE_KEY);
+ startActivityForResult(intent, 0);
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java
new file mode 100644
index 000000000..3e2c96464
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java
@@ -0,0 +1,700 @@
+/*
+ * 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;
+
+import android.annotation.SuppressLint;
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.graphics.Color;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+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.support.v4.view.MenuItemCompat;
+import android.support.v7.app.ActionBarActivity;
+import android.support.v7.widget.SearchView;
+import android.text.TextUtils;
+import android.view.ActionMode;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.view.animation.AnimationUtils;
+import android.widget.AbsListView.MultiChoiceModeListener;
+import android.widget.AdapterView;
+import android.widget.Button;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+import com.beardedhen.androidbootstrap.BootstrapButton;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.Id;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.helper.ExportHelper;
+import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
+import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
+import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData;
+import org.sufficientlysecure.keychain.ui.adapter.HighlightQueryCursorAdapter;
+import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment;
+import org.sufficientlysecure.keychain.util.Log;
+import se.emilsjolander.stickylistheaders.ApiLevelTooLowException;
+import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter;
+import se.emilsjolander.stickylistheaders.StickyListHeadersListView;
+
+import java.util.HashMap;
+
+/**
+ * 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 Fragment
+ implements SearchView.OnQueryTextListener, AdapterView.OnItemClickListener,
+ LoaderManager.LoaderCallbacks<Cursor> {
+
+ private KeyListAdapter mAdapter;
+ private StickyListHeadersListView mStickyList;
+
+ // rebuild functionality of ListFragment, http://stackoverflow.com/a/12504097
+ boolean mListShown;
+ View mProgressContainer;
+ View mListContainer;
+
+ private String mCurQuery;
+ private SearchView mSearchView;
+ // empty list layout
+ private BootstrapButton mButtonEmptyCreate;
+ private BootstrapButton mButtonEmptyImport;
+
+
+ /**
+ * Load custom layout with StickyListView from library
+ */
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View root = inflater.inflate(R.layout.key_list_fragment, container, false);
+
+ mStickyList = (StickyListHeadersListView) root.findViewById(R.id.key_list_list);
+ mStickyList.setOnItemClickListener(this);
+
+
+ // empty view
+ mButtonEmptyCreate = (BootstrapButton) root.findViewById(R.id.key_list_empty_button_create);
+ mButtonEmptyCreate.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ Intent intent = new Intent(getActivity(), EditKeyActivity.class);
+ intent.setAction(EditKeyActivity.ACTION_CREATE_KEY);
+ intent.putExtra(EditKeyActivity.EXTRA_GENERATE_DEFAULT_KEYS, true);
+ intent.putExtra(EditKeyActivity.EXTRA_USER_IDS, ""); // show user id view
+ startActivityForResult(intent, 0);
+ }
+ });
+ mButtonEmptyImport = (BootstrapButton) root.findViewById(R.id.key_list_empty_button_import);
+ mButtonEmptyImport.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ Intent intent = new Intent(getActivity(), ImportKeysActivity.class);
+ intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_FILE);
+ startActivityForResult(intent, 0);
+ }
+ });
+
+ // rebuild functionality of ListFragment, http://stackoverflow.com/a/12504097
+ mListContainer = root.findViewById(R.id.key_list_list_container);
+ mProgressContainer = root.findViewById(R.id.key_list_progress_container);
+ mListShown = true;
+
+ return root;
+ }
+
+ /**
+ * Define Adapter and Loader on create of Activity
+ */
+ @SuppressLint("NewApi")
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ mStickyList.setOnItemClickListener(this);
+ mStickyList.setAreHeadersSticky(true);
+ mStickyList.setDrawingListUnderStickyHeader(false);
+ mStickyList.setFastScrollEnabled(true);
+ try {
+ mStickyList.setFastScrollAlwaysVisible(true);
+ } catch (ApiLevelTooLowException e) {
+ }
+
+ /*
+ * ActionBarSherlock does not support MultiChoiceModeListener. Thus multi-selection is only
+ * available for Android >= 3.0
+ */
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ mStickyList.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
+ mStickyList.getWrappedList().setMultiChoiceModeListener(new MultiChoiceModeListener() {
+
+ @Override
+ public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+ android.view.MenuInflater inflater = getActivity().getMenuInflater();
+ inflater.inflate(R.menu.key_list_multi, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+ return false;
+ }
+
+ @Override
+ public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+
+ // get IDs for checked positions as long array
+ long[] ids;
+
+ switch (item.getItemId()) {
+ case R.id.menu_key_list_multi_encrypt: {
+ ids = mAdapter.getCurrentSelectedMasterKeyIds();
+ encrypt(mode, ids);
+ break;
+ }
+ case R.id.menu_key_list_multi_delete: {
+ ids = mAdapter.getCurrentSelectedMasterKeyIds();
+ showDeleteKeyDialog(mode, ids);
+ break;
+ }
+ case R.id.menu_key_list_multi_export: {
+ ids = mAdapter.getCurrentSelectedMasterKeyIds();
+ ExportHelper mExportHelper = new ExportHelper((ActionBarActivity) getActivity());
+ mExportHelper.showExportKeysDialog(
+ ids, Constants.Path.APP_DIR_FILE, mAdapter.isAnySecretSelected());
+ break;
+ }
+ case R.id.menu_key_list_multi_select_all: {
+ // select all
+ for (int i = 0; i < mStickyList.getCount(); i++) {
+ mStickyList.setItemChecked(i, true);
+ }
+ break;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public void onDestroyActionMode(ActionMode mode) {
+ mAdapter.clearSelection();
+ }
+
+ @Override
+ public void onItemCheckedStateChanged(ActionMode mode, int position, long id,
+ boolean checked) {
+ if (checked) {
+ mAdapter.setNewSelection(position, checked);
+ } else {
+ mAdapter.removeSelection(position);
+ }
+ int count = mStickyList.getCheckedItemCount();
+ String keysSelected = getResources().getQuantityString(
+ R.plurals.key_list_selected_keys, count, count);
+ mode.setTitle(keysSelected);
+ }
+
+ });
+ }
+
+ // We have a menu item to show in action bar.
+ setHasOptionsMenu(true);
+
+ // NOTE: Not supported by StickyListHeader, but reimplemented here
+ // Start out with a progress indicator.
+ setListShown(false);
+
+ // Create an empty adapter we will use to display the loaded data.
+ mAdapter = new KeyListAdapter(getActivity(), null, Id.type.public_key);
+ mStickyList.setAdapter(mAdapter);
+
+ // Prepare the loader. Either re-connect with an existing one,
+ // or start a new one.
+ getLoaderManager().initLoader(0, null, this);
+ }
+
+ // These are the rows that we will retrieve.
+ static final String[] PROJECTION = new String[]{
+ KeyRings._ID,
+ KeyRings.MASTER_KEY_ID,
+ KeyRings.USER_ID,
+ KeyRings.IS_REVOKED,
+ KeyRings.VERIFIED,
+ KeyRings.HAS_SECRET
+ };
+
+ static final int INDEX_MASTER_KEY_ID = 1;
+ static final int INDEX_USER_ID = 2;
+ static final int INDEX_IS_REVOKED = 3;
+ static final int INDEX_VERIFIED = 4;
+ static final int INDEX_HAS_SECRET = 5;
+
+ @Override
+ public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+ // This is called when a new Loader needs to be created. This
+ // sample only has one Loader, so we don't care about the ID.
+ Uri baseUri = KeyRings.buildUnifiedKeyRingsUri();
+ String where = null;
+ String whereArgs[] = null;
+ if (mCurQuery != null) {
+ where = KeyRings.USER_ID + " LIKE ?";
+ whereArgs = new String[]{"%" + mCurQuery + "%"};
+ }
+ // 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, where, whereArgs, null);
+ }
+
+ @Override
+ public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+ // Swap the new cursor in. (The framework will take care of closing the
+ // old cursor once we return.)
+ mAdapter.setSearchQuery(mCurQuery);
+ mAdapter.swapCursor(data);
+
+ mStickyList.setAdapter(mAdapter);
+
+ // this view is made visible if no data is available
+ mStickyList.setEmptyView(getActivity().findViewById(R.id.key_list_empty));
+
+ // NOTE: Not supported by StickyListHeader, but reimplemented here
+ // The list should now be shown.
+ if (isResumed()) {
+ setListShown(true);
+ } else {
+ setListShownNoAnimation(true);
+ }
+ }
+
+ @Override
+ public void onLoaderReset(Loader<Cursor> loader) {
+ // This is called when the last Cursor provided to onLoadFinished()
+ // above is about to be closed. We need to make sure we are no
+ // longer using it.
+ mAdapter.swapCursor(null);
+ }
+
+ /**
+ * On click on item, start key view activity
+ */
+ @Override
+ public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
+ Intent viewIntent = null;
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
+ viewIntent = new Intent(getActivity(), ViewKeyActivity.class);
+ } else {
+ viewIntent = new Intent(getActivity(), ViewKeyActivityJB.class);
+ }
+ viewIntent.setData(
+ KeyRings.buildGenericKeyRingUri(Long.toString(mAdapter.getMasterKeyId(position))));
+ startActivity(viewIntent);
+ }
+
+ @TargetApi(11)
+ protected void encrypt(ActionMode mode, long[] masterKeyIds) {
+ Intent intent = new Intent(getActivity(), EncryptActivity.class);
+ intent.setAction(EncryptActivity.ACTION_ENCRYPT);
+ intent.putExtra(EncryptActivity.EXTRA_ENCRYPTION_KEY_IDS, masterKeyIds);
+ // used instead of startActivity set actionbar based on callingPackage
+ startActivityForResult(intent, 0);
+
+ mode.finish();
+ }
+
+ /**
+ * Show dialog to delete key
+ *
+ * @param masterKeyIds
+ */
+ @TargetApi(11)
+ // TODO: this method needs an overhaul to handle both public and secret keys gracefully!
+ public void showDeleteKeyDialog(final ActionMode mode, long[] masterKeyIds) {
+ // Message is received after key is deleted
+ Handler returnHandler = new Handler() {
+ @Override
+ public void handleMessage(Message message) {
+ if (message.what == DeleteKeyDialogFragment.MESSAGE_OKAY) {
+ mode.finish();
+ }
+ }
+ };
+
+ // Create a new Messenger for the communication back
+ Messenger messenger = new Messenger(returnHandler);
+
+ DeleteKeyDialogFragment deleteKeyDialog = DeleteKeyDialogFragment.newInstance(messenger,
+ masterKeyIds);
+
+ deleteKeyDialog.show(getActivity().getSupportFragmentManager(), "deleteKeyDialog");
+ }
+
+
+ @Override
+ public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
+ // Get the searchview
+ MenuItem searchItem = menu.findItem(R.id.menu_key_list_search);
+ mSearchView = (SearchView) MenuItemCompat.getActionView(searchItem);
+
+ // Execute this when searching
+ mSearchView.setOnQueryTextListener(this);
+
+ // Erase search result without focus
+ MenuItemCompat.setOnActionExpandListener(searchItem, new MenuItemCompat.OnActionExpandListener() {
+ @Override
+ public boolean onMenuItemActionExpand(MenuItem item) {
+ return true;
+ }
+
+ @Override
+ public boolean onMenuItemActionCollapse(MenuItem item) {
+ mCurQuery = null;
+ mSearchView.setQuery("", true);
+ getLoaderManager().restartLoader(0, null, KeyListFragment.this);
+ return true;
+ }
+ });
+
+ super.onCreateOptionsMenu(menu, inflater);
+ }
+
+ @Override
+ public boolean onQueryTextSubmit(String s) {
+ return true;
+ }
+
+ @Override
+ public boolean onQueryTextChange(String s) {
+ // Called when the action bar search text has changed. Update
+ // the search filter, and restart the loader to do a new query
+ // with this filter.
+ mCurQuery = !TextUtils.isEmpty(s) ? s : null;
+ getLoaderManager().restartLoader(0, null, this);
+ return true;
+ }
+
+ // rebuild functionality of ListFragment, http://stackoverflow.com/a/12504097
+ public void setListShown(boolean shown, boolean animate) {
+ if (mListShown == shown) {
+ return;
+ }
+ mListShown = shown;
+ if (shown) {
+ if (animate) {
+ mProgressContainer.startAnimation(AnimationUtils.loadAnimation(
+ getActivity(), android.R.anim.fade_out));
+ mListContainer.startAnimation(AnimationUtils.loadAnimation(
+ getActivity(), android.R.anim.fade_in));
+ }
+ mProgressContainer.setVisibility(View.GONE);
+ mListContainer.setVisibility(View.VISIBLE);
+ } else {
+ if (animate) {
+ mProgressContainer.startAnimation(AnimationUtils.loadAnimation(
+ getActivity(), android.R.anim.fade_in));
+ mListContainer.startAnimation(AnimationUtils.loadAnimation(
+ getActivity(), android.R.anim.fade_out));
+ }
+ mProgressContainer.setVisibility(View.VISIBLE);
+ mListContainer.setVisibility(View.INVISIBLE);
+ }
+ }
+
+ // rebuild functionality of ListFragment, http://stackoverflow.com/a/12504097
+ public void setListShown(boolean shown) {
+ setListShown(shown, true);
+ }
+
+ // rebuild functionality of ListFragment, http://stackoverflow.com/a/12504097
+ public void setListShownNoAnimation(boolean shown) {
+ setListShown(shown, false);
+ }
+
+ /**
+ * Implements StickyListHeadersAdapter from library
+ */
+ private class KeyListAdapter extends HighlightQueryCursorAdapter implements StickyListHeadersAdapter {
+ private LayoutInflater mInflater;
+
+ private HashMap<Integer, Boolean> mSelection = new HashMap<Integer, Boolean>();
+
+ public KeyListAdapter(Context context, Cursor c, int flags) {
+ super(context, c, flags);
+
+ mInflater = LayoutInflater.from(context);
+ }
+
+ @Override
+ public Cursor swapCursor(Cursor newCursor) {
+ return super.swapCursor(newCursor);
+ }
+
+ /**
+ * Bind cursor data to the item list view
+ * <p/>
+ * NOTE: CursorAdapter already implements the ViewHolder pattern in its getView() method.
+ * Thus no ViewHolder is required here.
+ */
+ @Override
+ public void bindView(View view, Context context, Cursor cursor) {
+
+ { // set name and stuff, common to both key types
+ TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId);
+ TextView mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest);
+
+ String userId = cursor.getString(INDEX_USER_ID);
+ String[] userIdSplit = PgpKeyHelper.splitUserId(userId);
+ if (userIdSplit[0] != null) {
+ mainUserId.setText(highlightSearchQuery(userIdSplit[0]));
+ } else {
+ mainUserId.setText(R.string.user_id_no_name);
+ }
+ if (userIdSplit[1] != null) {
+ mainUserIdRest.setText(highlightSearchQuery(userIdSplit[1]));
+ mainUserIdRest.setVisibility(View.VISIBLE);
+ } else {
+ mainUserIdRest.setVisibility(View.GONE);
+ }
+ }
+
+ { // set edit button and revoked info, specific by key type
+ View statusDivider = (View) view.findViewById(R.id.status_divider);
+ FrameLayout statusLayout = (FrameLayout) view.findViewById(R.id.status_layout);
+ Button button = (Button) view.findViewById(R.id.edit);
+ TextView revoked = (TextView) view.findViewById(R.id.revoked);
+ ImageView verified = (ImageView) view.findViewById(R.id.verified);
+
+ if (cursor.getInt(KeyListFragment.INDEX_HAS_SECRET) != 0) {
+ // this is a secret key - show the edit button
+ statusDivider.setVisibility(View.VISIBLE);
+ statusLayout.setVisibility(View.VISIBLE);
+ revoked.setVisibility(View.GONE);
+ verified.setVisibility(View.GONE);
+ button.setVisibility(View.VISIBLE);
+
+ final long id = cursor.getLong(INDEX_MASTER_KEY_ID);
+ button.setOnClickListener(new OnClickListener() {
+ public void onClick(View view) {
+ Intent editIntent = new Intent(getActivity(), EditKeyActivity.class);
+ editIntent.setData(KeyRingData.buildSecretKeyRingUri(Long.toString(id)));
+ editIntent.setAction(EditKeyActivity.ACTION_EDIT_KEY);
+ startActivityForResult(editIntent, 0);
+ }
+ });
+ } else {
+ // this is a public key - hide the edit button, show if it's revoked
+ statusDivider.setVisibility(View.GONE);
+ button.setVisibility(View.GONE);
+
+ boolean isRevoked = cursor.getInt(INDEX_IS_REVOKED) > 0;
+ if(isRevoked) {
+ statusLayout.setVisibility(isRevoked ? View.VISIBLE : View.GONE);
+ revoked.setVisibility(isRevoked ? View.VISIBLE : View.GONE);
+ verified.setVisibility(View.GONE);
+ } else {
+ boolean isVerified = cursor.getInt(INDEX_VERIFIED) > 0;
+ statusLayout.setVisibility(isVerified ? View.VISIBLE : View.GONE);
+ revoked.setVisibility(View.GONE);
+ verified.setVisibility(isVerified ? View.VISIBLE : View.GONE);
+ }
+ }
+ }
+
+ }
+
+ public boolean isSecretAvailable(int id) {
+ if (!mCursor.moveToPosition(id)) {
+ throw new IllegalStateException("couldn't move cursor to position " + id);
+ }
+
+ return mCursor.getInt(INDEX_HAS_SECRET) != 0;
+ }
+ public long getMasterKeyId(int id) {
+ if (!mCursor.moveToPosition(id)) {
+ throw new IllegalStateException("couldn't move cursor to position " + id);
+ }
+
+ return mCursor.getLong(INDEX_MASTER_KEY_ID);
+ }
+
+ @Override
+ public View newView(Context context, Cursor cursor, ViewGroup parent) {
+ return mInflater.inflate(R.layout.key_list_item, parent, false);
+ }
+
+ /**
+ * Creates a new header view and binds the section headers to it. It uses the ViewHolder
+ * pattern. Most functionality is similar to getView() from Android's CursorAdapter.
+ * <p/>
+ * NOTE: The variables mDataValid and mCursor are available due to the super class
+ * CursorAdapter.
+ */
+ @Override
+ public View getHeaderView(int position, View convertView, ViewGroup parent) {
+ HeaderViewHolder holder;
+ if (convertView == null) {
+ holder = new HeaderViewHolder();
+ convertView = mInflater.inflate(R.layout.key_list_header, parent, false);
+ holder.mText = (TextView) convertView.findViewById(R.id.stickylist_header_text);
+ holder.mCount = (TextView) convertView.findViewById(R.id.contacts_num);
+ convertView.setTag(holder);
+ } else {
+ holder = (HeaderViewHolder) convertView.getTag();
+ }
+
+ if (!mDataValid) {
+ // no data available at this point
+ Log.d(Constants.TAG, "getHeaderView: No data available at this point!");
+ return convertView;
+ }
+
+ if (!mCursor.moveToPosition(position)) {
+ throw new IllegalStateException("couldn't move cursor to position " + position);
+ }
+
+ if (mCursor.getInt(KeyListFragment.INDEX_HAS_SECRET) != 0) {
+ { // set contact count
+ int num = mCursor.getCount();
+ String contactsTotal = getResources().getQuantityString(R.plurals.n_contacts, num, num);
+ holder.mCount.setText(contactsTotal);
+ holder.mCount.setVisibility(View.VISIBLE);
+ }
+
+ holder.mText.setText(convertView.getResources().getString(R.string.my_keys));
+ return convertView;
+ }
+
+ // set header text as first char in user id
+ String userId = mCursor.getString(KeyListFragment.INDEX_USER_ID);
+ String headerText = convertView.getResources().getString(R.string.user_id_no_name);
+ if (userId != null && userId.length() > 0) {
+ headerText = "" + userId.subSequence(0, 1).charAt(0);
+ }
+ holder.mText.setText(headerText);
+ holder.mCount.setVisibility(View.GONE);
+ return convertView;
+ }
+
+ /**
+ * Header IDs should be static, position=1 should always return the same Id that is.
+ */
+ @Override
+ public long getHeaderId(int position) {
+ if (!mDataValid) {
+ // no data available at this point
+ Log.d(Constants.TAG, "getHeaderView: No data available at this point!");
+ return -1;
+ }
+
+ if (!mCursor.moveToPosition(position)) {
+ throw new IllegalStateException("couldn't move cursor to position " + position);
+ }
+
+ // early breakout: all secret keys are assigned id 0
+ if (mCursor.getInt(KeyListFragment.INDEX_HAS_SECRET) != 0) {
+ return 1L;
+ }
+ // otherwise, return the first character of the name as ID
+ String userId = mCursor.getString(KeyListFragment.INDEX_USER_ID);
+ if (userId != null && userId.length() > 0) {
+ return userId.charAt(0);
+ } else {
+ return Long.MAX_VALUE;
+ }
+ }
+
+ class HeaderViewHolder {
+ TextView mText;
+ TextView mCount;
+ }
+
+ /**
+ * -------------------------- MULTI-SELECTION METHODS --------------
+ */
+ public void setNewSelection(int position, boolean value) {
+ mSelection.put(position, value);
+ notifyDataSetChanged();
+ }
+
+ public boolean isAnySecretSelected() {
+ for (int pos : mSelection.keySet()) {
+ if(mAdapter.isSecretAvailable(pos))
+ return true;
+ }
+ return false;
+ }
+
+ public long[] getCurrentSelectedMasterKeyIds() {
+ long[] ids = new long[mSelection.size()];
+ int i = 0;
+ // get master key ids
+ for (int pos : mSelection.keySet()) {
+ ids[i++] = mAdapter.getMasterKeyId(pos);
+ }
+ return ids;
+ }
+
+ public void removeSelection(int position) {
+ mSelection.remove(position);
+ notifyDataSetChanged();
+ }
+
+ public void clearSelection() {
+ mSelection.clear();
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ // let the adapter handle setting up the row views
+ View v = super.getView(position, convertView, parent);
+
+ /**
+ * Change color for multi-selection
+ */
+ if (mSelection.get(position) != null) {
+ // selected position color
+ v.setBackgroundColor(parent.getResources().getColor(R.color.emphasis));
+ } else {
+ // default color
+ v.setBackgroundColor(Color.TRANSPARENT);
+ }
+
+ return v;
+ }
+
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesActivity.java
new file mode 100644
index 000000000..265bb2139
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesActivity.java
@@ -0,0 +1,387 @@
+/*
+ * Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
+ *
+ * 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;
+
+import android.annotation.SuppressLint;
+import android.content.Intent;
+import android.os.Build;
+import android.os.Bundle;
+import android.preference.CheckBoxPreference;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceFragment;
+import android.preference.PreferenceScreen;
+
+import org.spongycastle.bcpg.HashAlgorithmTags;
+import org.spongycastle.openpgp.PGPEncryptedData;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.Id;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.helper.Preferences;
+import org.sufficientlysecure.keychain.ui.widget.IntegerListPreference;
+
+import java.util.List;
+
+@SuppressLint("NewApi")
+public class PreferencesActivity extends PreferenceActivity {
+
+ public static final String ACTION_PREFS_GEN = "org.sufficientlysecure.keychain.ui.PREFS_GEN";
+ public static final String ACTION_PREFS_ADV = "org.sufficientlysecure.keychain.ui.PREFS_ADV";
+
+ private PreferenceScreen mKeyServerPreference = null;
+ private static Preferences sPreferences;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ sPreferences = Preferences.getPreferences(this);
+ super.onCreate(savedInstanceState);
+
+// final ActionBar actionBar = getSupportActionBar();
+// actionBar.setDisplayShowTitleEnabled(true);
+// actionBar.setDisplayHomeAsUpEnabled(false);
+// actionBar.setHomeButtonEnabled(false);
+
+ String action = getIntent().getAction();
+
+ if (action != null && action.equals(ACTION_PREFS_GEN)) {
+ addPreferencesFromResource(R.xml.gen_preferences);
+
+ initializePassPassphraceCacheTtl(
+ (IntegerListPreference) findPreference(Constants.Pref.PASSPHRASE_CACHE_TTL));
+
+ mKeyServerPreference = (PreferenceScreen) findPreference(Constants.Pref.KEY_SERVERS);
+ String servers[] = sPreferences.getKeyServers();
+ mKeyServerPreference.setSummary(getResources().getQuantityString(R.plurals.n_key_servers,
+ servers.length, servers.length));
+ mKeyServerPreference
+ .setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
+ public boolean onPreferenceClick(Preference preference) {
+ Intent intent = new Intent(PreferencesActivity.this,
+ PreferencesKeyServerActivity.class);
+ intent.putExtra(PreferencesKeyServerActivity.EXTRA_KEY_SERVERS,
+ sPreferences.getKeyServers());
+ startActivityForResult(intent, Id.request.key_server_preference);
+ return false;
+ }
+ });
+
+ } else if (action != null && action.equals(ACTION_PREFS_ADV)) {
+ addPreferencesFromResource(R.xml.adv_preferences);
+
+ initializeEncryptionAlgorithm(
+ (IntegerListPreference) findPreference(Constants.Pref.DEFAULT_ENCRYPTION_ALGORITHM));
+
+ int[] valueIds = new int[]{Id.choice.compression.none, Id.choice.compression.zip,
+ Id.choice.compression.zlib, Id.choice.compression.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];
+ }
+
+ initializeHashAlgorithm(
+ (IntegerListPreference) findPreference(Constants.Pref.DEFAULT_HASH_ALGORITHM),
+ valueIds, entries, values);
+
+ initializeMessageCompression(
+ (IntegerListPreference) findPreference(Constants.Pref.DEFAULT_MESSAGE_COMPRESSION),
+ valueIds, entries, values);
+
+ initializeFileCompression(
+ (IntegerListPreference) findPreference(Constants.Pref.DEFAULT_FILE_COMPRESSION),
+ entries, values);
+
+ initializeAsciiArmor(
+ (CheckBoxPreference) findPreference(Constants.Pref.DEFAULT_ASCII_ARMOR));
+
+ initializeForceV3Signatures(
+ (CheckBoxPreference) findPreference(Constants.Pref.FORCE_V3_SIGNATURES));
+
+ } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
+ // Load the legacy preferences headers
+ addPreferencesFromResource(R.xml.preference_headers_legacy);
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ switch (requestCode) {
+ case Id.request.key_server_preference: {
+ if (resultCode == RESULT_CANCELED || data == null) {
+ return;
+ }
+ String servers[] = data
+ .getStringArrayExtra(PreferencesKeyServerActivity.EXTRA_KEY_SERVERS);
+ sPreferences.setKeyServers(servers);
+ mKeyServerPreference.setSummary(getResources().getQuantityString(
+ R.plurals.n_key_servers, servers.length, servers.length));
+ break;
+ }
+
+ default: {
+ super.onActivityResult(requestCode, resultCode, data);
+ break;
+ }
+ }
+ }
+
+ /* Called only on Honeycomb and later */
+ @Override
+ public void onBuildHeaders(List<Header> target) {
+ super.onBuildHeaders(target);
+ loadHeadersFromResource(R.xml.preference_headers, target);
+ }
+
+ /**
+ * This fragment shows the general preferences in android 3.0+
+ */
+ public static class GeneralPrefsFragment extends PreferenceFragment {
+
+ private PreferenceScreen mKeyServerPreference = null;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Load the preferences from an XML resource
+ addPreferencesFromResource(R.xml.gen_preferences);
+
+ initializePassPassphraceCacheTtl(
+ (IntegerListPreference) findPreference(Constants.Pref.PASSPHRASE_CACHE_TTL));
+
+ mKeyServerPreference = (PreferenceScreen) findPreference(Constants.Pref.KEY_SERVERS);
+ String servers[] = sPreferences.getKeyServers();
+ mKeyServerPreference.setSummary(getResources().getQuantityString(R.plurals.n_key_servers,
+ servers.length, servers.length));
+ mKeyServerPreference
+ .setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
+ public boolean onPreferenceClick(Preference preference) {
+ Intent intent = new Intent(getActivity(),
+ PreferencesKeyServerActivity.class);
+ intent.putExtra(PreferencesKeyServerActivity.EXTRA_KEY_SERVERS,
+ sPreferences.getKeyServers());
+ startActivityForResult(intent, Id.request.key_server_preference);
+ return false;
+ }
+ });
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ switch (requestCode) {
+ case Id.request.key_server_preference: {
+ if (resultCode == RESULT_CANCELED || data == null) {
+ return;
+ }
+ String servers[] = data
+ .getStringArrayExtra(PreferencesKeyServerActivity.EXTRA_KEY_SERVERS);
+ sPreferences.setKeyServers(servers);
+ mKeyServerPreference.setSummary(getResources().getQuantityString(
+ R.plurals.n_key_servers, servers.length, servers.length));
+ break;
+ }
+
+ default: {
+ super.onActivityResult(requestCode, resultCode, data);
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * This fragment shows the advanced preferences in android 3.0+
+ */
+ public static class AdvancedPrefsFragment extends PreferenceFragment {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Load the preferences from an XML resource
+ addPreferencesFromResource(R.xml.adv_preferences);
+
+ initializeEncryptionAlgorithm(
+ (IntegerListPreference) findPreference(Constants.Pref.DEFAULT_ENCRYPTION_ALGORITHM));
+
+ int[] valueIds = new int[]{Id.choice.compression.none, Id.choice.compression.zip,
+ Id.choice.compression.zlib, Id.choice.compression.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];
+ }
+
+ initializeHashAlgorithm(
+ (IntegerListPreference) findPreference(Constants.Pref.DEFAULT_HASH_ALGORITHM),
+ valueIds, entries, values);
+
+ initializeMessageCompression(
+ (IntegerListPreference) findPreference(Constants.Pref.DEFAULT_MESSAGE_COMPRESSION),
+ valueIds, entries, values);
+
+ initializeFileCompression(
+ (IntegerListPreference) findPreference(Constants.Pref.DEFAULT_FILE_COMPRESSION),
+ entries, values);
+
+ initializeAsciiArmor(
+ (CheckBoxPreference) findPreference(Constants.Pref.DEFAULT_ASCII_ARMOR));
+
+ initializeForceV3Signatures(
+ (CheckBoxPreference) findPreference(Constants.Pref.FORCE_V3_SIGNATURES));
+ }
+ }
+
+ protected boolean isValidFragment(String fragmentName) {
+ return AdvancedPrefsFragment.class.getName().equals(fragmentName)
+ || GeneralPrefsFragment.class.getName().equals(fragmentName)
+ || super.isValidFragment(fragmentName);
+ }
+
+ private static void initializePassPassphraceCacheTtl(final IntegerListPreference mPassphraseCacheTtl) {
+ mPassphraseCacheTtl.setValue("" + sPreferences.getPassphraseCacheTtl());
+ mPassphraseCacheTtl.setSummary(mPassphraseCacheTtl.getEntry());
+ mPassphraseCacheTtl
+ .setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ mPassphraseCacheTtl.setValue(newValue.toString());
+ mPassphraseCacheTtl.setSummary(mPassphraseCacheTtl.getEntry());
+ sPreferences.setPassphraseCacheTtl(Integer.parseInt(newValue.toString()));
+ return false;
+ }
+ });
+ }
+
+ private static void initializeEncryptionAlgorithm(final IntegerListPreference mEncryptionAlgorithm) {
+ int valueIds[] = {PGPEncryptedData.AES_128, PGPEncryptedData.AES_192,
+ PGPEncryptedData.AES_256, PGPEncryptedData.BLOWFISH, PGPEncryptedData.TWOFISH,
+ PGPEncryptedData.CAST5, PGPEncryptedData.DES, PGPEncryptedData.TRIPLE_DES,
+ PGPEncryptedData.IDEA, };
+ String entries[] = {"AES-128", "AES-192", "AES-256", "Blowfish", "Twofish", "CAST5",
+ "DES", "Triple DES", "IDEA", };
+ String values[] = new String[valueIds.length];
+ for (int i = 0; i < values.length; ++i) {
+ values[i] = "" + valueIds[i];
+ }
+ mEncryptionAlgorithm.setEntries(entries);
+ mEncryptionAlgorithm.setEntryValues(values);
+ mEncryptionAlgorithm.setValue("" + sPreferences.getDefaultEncryptionAlgorithm());
+ mEncryptionAlgorithm.setSummary(mEncryptionAlgorithm.getEntry());
+ mEncryptionAlgorithm
+ .setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ mEncryptionAlgorithm.setValue(newValue.toString());
+ mEncryptionAlgorithm.setSummary(mEncryptionAlgorithm.getEntry());
+ sPreferences.setDefaultEncryptionAlgorithm(Integer.parseInt(newValue
+ .toString()));
+ return false;
+ }
+ });
+ }
+
+ private static void initializeHashAlgorithm
+ (final IntegerListPreference mHashAlgorithm, int[] valueIds, String[] entries, String[] values) {
+ valueIds = new int[]{HashAlgorithmTags.MD5, HashAlgorithmTags.RIPEMD160,
+ HashAlgorithmTags.SHA1, HashAlgorithmTags.SHA224, HashAlgorithmTags.SHA256,
+ HashAlgorithmTags.SHA384, HashAlgorithmTags.SHA512, };
+ entries = new String[]{"MD5", "RIPEMD-160", "SHA-1", "SHA-224", "SHA-256", "SHA-384",
+ "SHA-512", };
+ values = new String[valueIds.length];
+ for (int i = 0; i < values.length; ++i) {
+ values[i] = "" + valueIds[i];
+ }
+ mHashAlgorithm.setEntries(entries);
+ mHashAlgorithm.setEntryValues(values);
+ mHashAlgorithm.setValue("" + sPreferences.getDefaultHashAlgorithm());
+ mHashAlgorithm.setSummary(mHashAlgorithm.getEntry());
+ mHashAlgorithm.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ mHashAlgorithm.setValue(newValue.toString());
+ mHashAlgorithm.setSummary(mHashAlgorithm.getEntry());
+ sPreferences.setDefaultHashAlgorithm(Integer.parseInt(newValue.toString()));
+ return false;
+ }
+ });
+ }
+
+ private static void initializeMessageCompression(
+ final IntegerListPreference mMessageCompression,
+ int[] valueIds, String[] entries, String[] values) {
+ mMessageCompression.setEntries(entries);
+ mMessageCompression.setEntryValues(values);
+ mMessageCompression.setValue("" + sPreferences.getDefaultMessageCompression());
+ mMessageCompression.setSummary(mMessageCompression.getEntry());
+ mMessageCompression
+ .setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ mMessageCompression.setValue(newValue.toString());
+ mMessageCompression.setSummary(mMessageCompression.getEntry());
+ sPreferences.setDefaultMessageCompression(Integer.parseInt(newValue
+ .toString()));
+ return false;
+ }
+ });
+ }
+
+ private static void initializeFileCompression
+ (final IntegerListPreference mFileCompression, String[] entries, String[] values) {
+ mFileCompression.setEntries(entries);
+ mFileCompression.setEntryValues(values);
+ mFileCompression.setValue("" + sPreferences.getDefaultFileCompression());
+ mFileCompression.setSummary(mFileCompression.getEntry());
+ mFileCompression.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ mFileCompression.setValue(newValue.toString());
+ mFileCompression.setSummary(mFileCompression.getEntry());
+ sPreferences.setDefaultFileCompression(Integer.parseInt(newValue.toString()));
+ return false;
+ }
+ });
+ }
+
+ private static void initializeAsciiArmor(final CheckBoxPreference mAsciiArmor) {
+ mAsciiArmor.setChecked(sPreferences.getDefaultAsciiArmor());
+ mAsciiArmor.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ mAsciiArmor.setChecked((Boolean) newValue);
+ sPreferences.setDefaultAsciiArmor((Boolean) newValue);
+ return false;
+ }
+ });
+ }
+
+ private static void initializeForceV3Signatures(final CheckBoxPreference mForceV3Signatures) {
+ mForceV3Signatures.setChecked(sPreferences.getForceV3Signatures());
+ mForceV3Signatures
+ .setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ mForceV3Signatures.setChecked((Boolean) newValue);
+ sPreferences.setForceV3Signatures((Boolean) newValue);
+ return false;
+ }
+ });
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesKeyServerActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesKeyServerActivity.java
new file mode 100644
index 000000000..719378274
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesKeyServerActivity.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
+ *
+ * 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;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v7.app.ActionBarActivity;
+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.helper.ActionBarHelper;
+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 PreferencesKeyServerActivity extends ActionBarActivity implements OnClickListener,
+ EditorListener {
+
+ public static final String EXTRA_KEY_SERVERS = "key_servers";
+
+ private LayoutInflater mInflater;
+ private ViewGroup mEditors;
+ private View mAdd;
+ private TextView mTitle;
+ private TextView mSummary;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Inflate a "Done"/"Cancel" custom action bar view
+ ActionBarHelper.setTwoButtonView(getSupportActionBar(), R.string.btn_okay, R.drawable.ic_action_done,
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // ok
+ okClicked();
+ }
+ }, R.string.btn_do_not_save, R.drawable.ic_action_cancel, new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // cancel
+ cancelClicked();
+ }
+ }
+ );
+
+ setContentView(R.layout.key_server_preference);
+
+ mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+ mTitle = (TextView) findViewById(R.id.title);
+ mSummary = (TextView) findViewById(R.id.summary);
+
+ mTitle.setText(R.string.label_key_servers);
+
+ mEditors = (ViewGroup) findViewById(R.id.editors);
+ mAdd = findViewById(R.id.add);
+ mAdd.setOnClickListener(this);
+
+ Intent intent = getIntent();
+ String servers[] = intent.getStringArrayExtra(EXTRA_KEY_SERVERS);
+ if (servers != null) {
+ 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);
+ }
+ }
+ }
+
+ public void onDeleted(Editor editor, boolean wasNewItem) {
+ // nothing to do
+ }
+
+ @Override
+ public void onEdited() {
+
+ }
+
+ public void onClick(View v) {
+ KeyServerEditor view = (KeyServerEditor) mInflater.inflate(R.layout.key_server_editor,
+ mEditors, false);
+ view.setEditorListener(this);
+ mEditors.addView(view);
+ }
+
+ private void cancelClicked() {
+ setResult(RESULT_CANCELED, null);
+ finish();
+ }
+
+ private void okClicked() {
+ Intent data = new Intent();
+ Vector<String> servers = new Vector<String>();
+ for (int i = 0; i < mEditors.getChildCount(); ++i) {
+ KeyServerEditor editor = (KeyServerEditor) mEditors.getChildAt(i);
+ String tmp = editor.getValue();
+ if (tmp.length() > 0) {
+ servers.add(tmp);
+ }
+ }
+ String[] dummy = new String[0];
+ data.putExtra(EXTRA_KEY_SERVERS, servers.toArray(dummy));
+ setResult(RESULT_OK, data);
+ finish();
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyActivity.java
new file mode 100644
index 000000000..874703704
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyActivity.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2012 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
+ *
+ * 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;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v7.app.ActionBarActivity;
+import android.view.View;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.helper.ActionBarHelper;
+
+public class SelectPublicKeyActivity extends ActionBarActivity {
+
+ // Actions for internal use only:
+ public static final String ACTION_SELECT_PUBLIC_KEYS = Constants.INTENT_PREFIX
+ + "SELECT_PUBLIC_KEYRINGS";
+
+ public static final String EXTRA_SELECTED_MASTER_KEY_IDS = "master_key_ids";
+
+ public static final String RESULT_EXTRA_MASTER_KEY_IDS = "master_key_ids";
+ public static final String RESULT_EXTRA_USER_IDS = "user_ids";
+
+ SelectPublicKeyFragment mSelectFragment;
+
+ long mSelectedMasterKeyIds[];
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Inflate a "Done"/"Cancel" custom action bar view
+ ActionBarHelper.setTwoButtonView(getSupportActionBar(), R.string.btn_okay, R.drawable.ic_action_done,
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // ok
+ okClicked();
+ }
+ }, R.string.btn_do_not_save, R.drawable.ic_action_cancel, new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // cancel
+ cancelClicked();
+ }
+ }
+ );
+
+ setContentView(R.layout.select_public_key_activity);
+
+ setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
+
+ handleIntent(getIntent());
+
+ // Check that the activity is using the layout version with
+ // the fragment_container FrameLayout
+ if (findViewById(R.id.select_public_key_fragment_container) != null) {
+
+ // 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
+ mSelectFragment = SelectPublicKeyFragment.newInstance(mSelectedMasterKeyIds);
+
+ // Add the fragment to the 'fragment_container' FrameLayout
+ getSupportFragmentManager().beginTransaction()
+ .add(R.id.select_public_key_fragment_container, mSelectFragment).commit();
+ }
+
+ // TODO: reimplement!
+ // mFilterLayout = findViewById(R.id.layout_filter);
+ // mFilterInfo = (TextView) mFilterLayout.findViewById(R.id.filterInfo);
+ // mClearFilterButton = (Button) mFilterLayout.findViewById(R.id.btn_clear);
+ //
+ // mClearFilterButton.setOnClickListener(new OnClickListener() {
+ // public void onClick(View v) {
+ // handleIntent(new Intent());
+ // }
+ // });
+
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ handleIntent(intent);
+ }
+
+ private void handleIntent(Intent intent) {
+ // TODO: reimplement search!
+
+ // String searchString = null;
+ // if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
+ // searchString = intent.getStringExtra(SearchManager.QUERY);
+ // if (searchString != null && searchString.trim().length() == 0) {
+ // searchString = null;
+ // }
+ // }
+
+ // if (searchString == null) {
+ // mFilterLayout.setVisibility(View.GONE);
+ // } else {
+ // mFilterLayout.setVisibility(View.VISIBLE);
+ // mFilterInfo.setText(getString(R.string.filterInfo, searchString));
+ // }
+
+ // preselected master keys
+ mSelectedMasterKeyIds = intent.getLongArrayExtra(EXTRA_SELECTED_MASTER_KEY_IDS);
+ }
+
+ private void cancelClicked() {
+ setResult(RESULT_CANCELED, null);
+ finish();
+ }
+
+ private void okClicked() {
+ Intent data = new Intent();
+ data.putExtra(RESULT_EXTRA_MASTER_KEY_IDS, mSelectFragment.getSelectedMasterKeyIds());
+ data.putExtra(RESULT_EXTRA_USER_IDS, mSelectFragment.getSelectedUserIds());
+ setResult(RESULT_OK, data);
+ finish();
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyFragment.java
new file mode 100644
index 000000000..9bfe3eaa9
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyFragment.java
@@ -0,0 +1,350 @@
+/*
+ * Copyright (C) 2012-2013 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
+ *
+ * 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;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v4.app.LoaderManager;
+import android.support.v4.content.CursorLoader;
+import android.support.v4.content.Loader;
+import android.text.Editable;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.EditText;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import org.sufficientlysecure.keychain.Id;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.compatibility.ListFragmentWorkaround;
+import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
+import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
+import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
+import org.sufficientlysecure.keychain.provider.KeychainDatabase;
+import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
+import org.sufficientlysecure.keychain.ui.adapter.SelectKeyCursorAdapter;
+
+import java.util.Date;
+import java.util.Vector;
+
+public class SelectPublicKeyFragment extends ListFragmentWorkaround implements TextWatcher,
+ LoaderManager.LoaderCallbacks<Cursor> {
+ public static final String ARG_PRESELECTED_KEY_IDS = "preselected_key_ids";
+
+ private SelectKeyCursorAdapter mAdapter;
+ private EditText mSearchView;
+ private long mSelectedMasterKeyIds[];
+ private String mCurQuery;
+
+ // copied from ListFragment
+ static final int INTERNAL_EMPTY_ID = 0x00ff0001;
+ static final int INTERNAL_PROGRESS_CONTAINER_ID = 0x00ff0002;
+ static final int INTERNAL_LIST_CONTAINER_ID = 0x00ff0003;
+ // added for search view
+ static final int SEARCH_ID = 0x00ff0004;
+
+ /**
+ * Creates new instance of this fragment
+ */
+ public static SelectPublicKeyFragment newInstance(long[] preselectedKeyIds) {
+ SelectPublicKeyFragment frag = new SelectPublicKeyFragment();
+ Bundle args = new Bundle();
+
+ args.putLongArray(ARG_PRESELECTED_KEY_IDS, preselectedKeyIds);
+
+ frag.setArguments(args);
+
+ return frag;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mSelectedMasterKeyIds = getArguments().getLongArray(ARG_PRESELECTED_KEY_IDS);
+ }
+
+ /**
+ * Copied from ListFragment and added EditText for search on top of list.
+ * We do not use a custom layout here, because this breaks the progress bar functionality
+ * of ListFragment.
+ *
+ * @param inflater
+ * @param container
+ * @param savedInstanceState
+ * @return
+ */
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ final Context context = getActivity();
+
+ FrameLayout root = new FrameLayout(context);
+
+ // ------------------------------------------------------------------
+
+ LinearLayout pframe = new LinearLayout(context);
+ pframe.setId(INTERNAL_PROGRESS_CONTAINER_ID);
+ pframe.setOrientation(LinearLayout.VERTICAL);
+ pframe.setVisibility(View.GONE);
+ pframe.setGravity(Gravity.CENTER);
+
+ ProgressBar progress = new ProgressBar(context, null,
+ android.R.attr.progressBarStyleLarge);
+ pframe.addView(progress, new FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
+
+ root.addView(pframe, new FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT));
+
+ // ------------------------------------------------------------------
+
+ FrameLayout lframe = new FrameLayout(context);
+ lframe.setId(INTERNAL_LIST_CONTAINER_ID);
+
+ TextView tv = new TextView(getActivity());
+ tv.setId(INTERNAL_EMPTY_ID);
+ tv.setGravity(Gravity.CENTER);
+ lframe.addView(tv, new FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT));
+
+ // Added for search view: linearLayout, mSearchView
+ LinearLayout linearLayout = new LinearLayout(context);
+ linearLayout.setOrientation(LinearLayout.VERTICAL);
+
+ mSearchView = new EditText(context);
+ mSearchView.setId(SEARCH_ID);
+ mSearchView.setHint(R.string.menu_search);
+ mSearchView.setCompoundDrawablesWithIntrinsicBounds(
+ getResources().getDrawable(R.drawable.ic_action_search), null, null, null);
+
+ linearLayout.addView(mSearchView, new FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
+
+ ListView lv = new ListView(getActivity());
+ lv.setId(android.R.id.list);
+ lv.setDrawSelectorOnTop(false);
+ linearLayout.addView(lv, new FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT));
+
+ lframe.addView(linearLayout, new FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT));
+
+ root.addView(lframe, new FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT));
+
+ // ------------------------------------------------------------------
+
+ root.setLayoutParams(new FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT));
+
+ return root;
+ }
+
+ /**
+ * Define Adapter and Loader on create of Activity
+ */
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ 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));
+
+ mSearchView.addTextChangedListener(this);
+
+ mAdapter = new SelectKeyCursorAdapter(getActivity(), null, 0, getListView(), Id.type.public_key);
+
+ setListAdapter(mAdapter);
+
+ // Start out with a progress indicator.
+ setListShown(false);
+
+ // 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(long[] masterKeyIds) {
+ if (masterKeyIds != null) {
+ for (int i = 0; i < getListView().getCount(); ++i) {
+ long keyId = mAdapter.getMasterKeyId(i);
+ for (long masterKeyId : masterKeyIds) {
+ if (keyId == masterKeyId) {
+ getListView().setItemChecked(i, true);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns all selected master key ids
+ *
+ * @return
+ */
+ public long[] getSelectedMasterKeyIds() {
+ // mListView.getCheckedItemIds() would give the row ids of the KeyRings not the master key
+ // ids!
+ Vector<Long> vector = new Vector<Long>();
+ for (int i = 0; i < getListView().getCount(); ++i) {
+ if (getListView().isItemChecked(i)) {
+ vector.add(mAdapter.getMasterKeyId(i));
+ }
+ }
+
+ // convert to long array
+ long[] selectedMasterKeyIds = new long[vector.size()];
+ for (int i = 0; i < vector.size(); ++i) {
+ selectedMasterKeyIds[i] = vector.get(i);
+ }
+
+ return selectedMasterKeyIds;
+ }
+
+ /**
+ * Returns all selected user ids
+ *
+ * @return
+ */
+ public String[] getSelectedUserIds() {
+ Vector<String> userIds = new Vector<String>();
+ for (int i = 0; i < getListView().getCount(); ++i) {
+ if (getListView().isItemChecked(i)) {
+ userIds.add((String) mAdapter.getUserId(i));
+ }
+ }
+
+ // make empty array to not return null
+ String userIdArray[] = new String[0];
+ return userIds.toArray(userIdArray);
+ }
+
+ @Override
+ public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+ Uri baseUri = KeyRings.buildUnifiedKeyRingsUri();
+
+ // These are the rows that we will retrieve.
+ long now = new Date().getTime() / 1000;
+ String[] projection = new String[]{
+ KeyRings._ID,
+ KeyRings.MASTER_KEY_ID,
+ UserIds.USER_ID,
+ "(SELECT COUNT(*) FROM " + Tables.KEYS + " AS k"
+ +" WHERE k." + Keys.MASTER_KEY_ID + " = "
+ + KeychainDatabase.Tables.KEYS + "." + Keys.MASTER_KEY_ID
+ + " AND k." + Keys.IS_REVOKED + " = '0'"
+ + " AND k." + Keys.CAN_ENCRYPT + " = '1'"
+ + ") AS " + SelectKeyCursorAdapter.PROJECTION_ROW_AVAILABLE,
+ "(SELECT COUNT(*) FROM " + Tables.KEYS + " AS k"
+ + " WHERE k." + Keys.MASTER_KEY_ID + " = "
+ + KeychainDatabase.Tables.KEYS + "." + Keys.MASTER_KEY_ID
+ + " AND k." + Keys.IS_REVOKED + " = '0'"
+ + " AND k." + Keys.CAN_ENCRYPT + " = '1'"
+ + " AND k." + Keys.CREATION + " <= '" + now + "'"
+ + " AND ( k." + Keys.EXPIRY + " IS NULL OR k." + Keys.EXPIRY + " >= '" + now + "' )"
+ + ") AS " + SelectKeyCursorAdapter.PROJECTION_ROW_VALID, };
+
+ String inMasterKeyList = null;
+ if (mSelectedMasterKeyIds != null && mSelectedMasterKeyIds.length > 0) {
+ inMasterKeyList = Tables.KEYS + "." + KeyRings.MASTER_KEY_ID + " IN (";
+ for (int i = 0; i < mSelectedMasterKeyIds.length; ++i) {
+ if (i != 0) {
+ inMasterKeyList += ", ";
+ }
+ inMasterKeyList += DatabaseUtils.sqlEscapeString("" + mSelectedMasterKeyIds[i]);
+ }
+ inMasterKeyList += ")";
+ }
+
+ String orderBy = UserIds.USER_ID + " ASC";
+ if (inMasterKeyList != null) {
+ // sort by selected master keys
+ orderBy = inMasterKeyList + " DESC, " + orderBy;
+ }
+ String where = null;
+ String whereArgs[] = null;
+ if (mCurQuery != null) {
+ where = UserIds.USER_ID + " LIKE ?";
+ whereArgs = new String[]{"%" + mCurQuery + "%"};
+ }
+
+ // 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, where, whereArgs, orderBy);
+ }
+
+ @Override
+ public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+ // Swap the new cursor in. (The framework will take care of closing the
+ // old cursor once we return.)
+ mAdapter.setSearchQuery(mCurQuery);
+ mAdapter.swapCursor(data);
+
+ // The list should now be shown.
+ if (isResumed()) {
+ setListShown(true);
+ } else {
+ setListShownNoAnimation(true);
+ }
+
+ // preselect given master keys
+ preselectMasterKeyIds(mSelectedMasterKeyIds);
+ }
+
+ @Override
+ public void onLoaderReset(Loader<Cursor> loader) {
+ // This is called when the last Cursor provided to onLoadFinished()
+ // above is about to be closed. We need to make sure we are no
+ // longer using it.
+ mAdapter.swapCursor(null);
+ }
+
+ @Override
+ public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {
+
+ }
+
+ @Override
+ public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {
+
+ }
+
+ @Override
+ public void afterTextChanged(Editable editable) {
+ mCurQuery = !TextUtils.isEmpty(editable.toString()) ? editable.toString() : null;
+ getLoaderManager().restartLoader(0, null, this);
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectSecretKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectSecretKeyActivity.java
new file mode 100644
index 000000000..0ff88d97c
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectSecretKeyActivity.java
@@ -0,0 +1,83 @@
+/*
+ * 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;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.ActionBarActivity;
+
+import org.sufficientlysecure.keychain.R;
+
+public class SelectSecretKeyActivity extends ActionBarActivity {
+
+ public static final String EXTRA_FILTER_CERTIFY = "filter_certify";
+
+ public static final String RESULT_EXTRA_MASTER_KEY_ID = "master_key_id";
+
+ private boolean mFilterCertify;
+ private SelectSecretKeyFragment mSelectFragment;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.select_secret_key_activity);
+
+ final ActionBar actionBar = getSupportActionBar();
+ actionBar.setDisplayShowTitleEnabled(true);
+ actionBar.setDisplayHomeAsUpEnabled(false);
+ actionBar.setHomeButtonEnabled(false);
+
+ mFilterCertify = getIntent().getBooleanExtra(EXTRA_FILTER_CERTIFY, false);
+
+ // Check that the activity is using the layout version with
+ // the fragment_container FrameLayout
+ if (findViewById(R.id.select_secret_key_fragment_container) != null) {
+
+ // 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
+ mSelectFragment = SelectSecretKeyFragment.newInstance(mFilterCertify);
+
+ // Add the fragment to the 'fragment_container' FrameLayout
+ getSupportFragmentManager().beginTransaction()
+ .add(R.id.select_secret_key_fragment_container, mSelectFragment).commit();
+ }
+ }
+
+ /**
+ * This is executed by SelectSecretKeyFragment after clicking on an item
+ *
+ * @param selectedUri
+ */
+ public void afterListSelection(Uri selectedUri) {
+ Intent data = new Intent();
+ data.setData(selectedUri);
+
+ setResult(RESULT_OK, data);
+ finish();
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectSecretKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectSecretKeyFragment.java
new file mode 100644
index 000000000..9987facbc
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectSecretKeyFragment.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
+ *
+ * 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;
+
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v4.app.ListFragment;
+import android.support.v4.app.LoaderManager;
+import android.support.v4.content.CursorLoader;
+import android.support.v4.content.Loader;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ListView;
+
+import org.sufficientlysecure.keychain.Id;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
+import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
+import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
+import org.sufficientlysecure.keychain.provider.KeychainDatabase;
+import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
+import org.sufficientlysecure.keychain.ui.adapter.SelectKeyCursorAdapter;
+
+import java.util.Date;
+
+public class SelectSecretKeyFragment extends ListFragment implements
+ LoaderManager.LoaderCallbacks<Cursor> {
+
+ private SelectSecretKeyActivity mActivity;
+ private SelectKeyCursorAdapter mAdapter;
+ private ListView mListView;
+
+ private boolean mFilterCertify;
+
+ private static final String ARG_FILTER_CERTIFY = "filter_certify";
+
+ /**
+ * Creates new instance of this fragment
+ */
+ public static SelectSecretKeyFragment newInstance(boolean filterCertify) {
+ SelectSecretKeyFragment frag = new SelectSecretKeyFragment();
+
+ Bundle args = new Bundle();
+ args.putBoolean(ARG_FILTER_CERTIFY, filterCertify);
+ frag.setArguments(args);
+
+ return frag;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mFilterCertify = getArguments().getBoolean(ARG_FILTER_CERTIFY);
+ }
+
+ /**
+ * Define Adapter and Loader on create of Activity
+ */
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ mActivity = (SelectSecretKeyActivity) getActivity();
+ mListView = getListView();
+
+ mListView.setOnItemClickListener(new OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
+ long masterKeyId = mAdapter.getMasterKeyId(position);
+ Uri result = KeyRings.buildGenericKeyRingUri(String.valueOf(masterKeyId));
+
+ // return data to activity, which results in finishing it
+ mActivity.afterListSelection(result);
+ }
+ });
+
+ // 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 SelectKeyCursorAdapter(mActivity, null, 0, mListView, Id.type.secret_key);
+
+ setListAdapter(mAdapter);
+
+ // Start out with a progress indicator.
+ setListShown(false);
+
+ // Prepare the loader. Either re-connect with an existing one,
+ // or start a new one.
+ getLoaderManager().initLoader(0, null, this);
+ }
+
+ @Override
+ public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+ // This is called when a new Loader needs to be created. This
+ // sample only has one Loader, so we don't care about the ID.
+ Uri baseUri = KeyRings.buildUnifiedKeyRingsUri();
+
+ // These are the rows that we will retrieve.
+ long now = new Date().getTime() / 1000;
+ String[] projection = new String[]{
+ KeyRings._ID,
+ KeyRings.MASTER_KEY_ID,
+ UserIds.USER_ID,
+ "(SELECT COUNT(*) FROM " + Tables.KEYS + " AS k"
+ + " WHERE k." + Keys.MASTER_KEY_ID + " = "
+ + KeychainDatabase.Tables.KEYS + "." + KeyRings.MASTER_KEY_ID
+ + " AND k." + Keys.CAN_CERTIFY + " = '1'"
+ + ") AS cert",
+ "(SELECT COUNT(*) FROM " + Tables.KEYS + " AS k"
+ +" WHERE k." + Keys.MASTER_KEY_ID + " = "
+ + KeychainDatabase.Tables.KEYS + "." + Keys.MASTER_KEY_ID
+ + " AND k." + Keys.IS_REVOKED + " = '0'"
+ + " AND k." + Keys.CAN_SIGN + " = '1'"
+ + ") AS " + SelectKeyCursorAdapter.PROJECTION_ROW_AVAILABLE,
+ "(SELECT COUNT(*) FROM " + Tables.KEYS + " AS k"
+ + " WHERE k." + Keys.MASTER_KEY_ID + " = "
+ + KeychainDatabase.Tables.KEYS + "." + Keys.MASTER_KEY_ID
+ + " AND k." + Keys.IS_REVOKED + " = '0'"
+ + " AND k." + Keys.CAN_SIGN + " = '1'"
+ + " AND k." + Keys.CREATION + " <= '" + now + "'"
+ + " AND ( k." + Keys.EXPIRY + " IS NULL OR k." + Keys.EXPIRY + " >= '" + now + "' )"
+ + ") AS " + SelectKeyCursorAdapter.PROJECTION_ROW_VALID, };
+
+ String orderBy = UserIds.USER_ID + " ASC";
+
+ String where = Tables.KEY_RINGS_SECRET + "." + KeyRings.MASTER_KEY_ID + " IS NOT NULL";
+ if (mFilterCertify) {
+ where += " AND (cert > 0)";
+ }
+
+ // 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, where, null, orderBy);
+ }
+
+ @Override
+ public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+ // Swap the new cursor in. (The framework will take care of closing the
+ // old cursor once we return.)
+ mAdapter.swapCursor(data);
+
+ // The list should now be shown.
+ if (isResumed()) {
+ setListShown(true);
+ } else {
+ setListShownNoAnimation(true);
+ }
+ }
+
+ @Override
+ public void onLoaderReset(Loader<Cursor> loader) {
+ // This is called when the last Cursor provided to onLoadFinished()
+ // above is about to be closed. We need to make sure we are no
+ // longer using it.
+ mAdapter.swapCursor(null);
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectSecretKeyLayoutFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectSecretKeyLayoutFragment.java
new file mode 100644
index 000000000..514951385
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectSecretKeyLayoutFragment.java
@@ -0,0 +1,211 @@
+/*
+ * 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.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+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.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.beardedhen.androidbootstrap.BootstrapButton;
+
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
+import org.sufficientlysecure.keychain.provider.KeychainContract;
+
+public class SelectSecretKeyLayoutFragment extends Fragment implements LoaderManager.LoaderCallbacks<Cursor> {
+
+ private TextView mKeyUserId;
+ private TextView mKeyUserIdRest;
+ private TextView mKeyMasterKeyIdHex;
+ private TextView mNoKeySelected;
+ private BootstrapButton mSelectKeyButton;
+ private Boolean mFilterCertify;
+
+ private Uri mReceivedUri = null;
+
+ private SelectSecretKeyCallback mCallback;
+
+ private static final int REQUEST_CODE_SELECT_KEY = 8882;
+
+ private static final int LOADER_ID = 0;
+
+ //The Projection we will retrieve, Master Key ID is for convenience sake,
+ //to avoid having to pass the Key Around
+ final String[] PROJECTION = new String[] {
+ KeychainContract.Keys.MASTER_KEY_ID,
+ KeychainContract.UserIds.USER_ID
+ };
+ final int INDEX_MASTER_KEY_ID = 0;
+ final int INDEX_USER_ID = 1;
+
+ public interface SelectSecretKeyCallback {
+ void onKeySelected(long secretKeyId);
+ }
+
+ public void setCallback(SelectSecretKeyCallback callback) {
+ mCallback = callback;
+ }
+
+ public void setFilterCertify(Boolean filterCertify) {
+ mFilterCertify = filterCertify;
+ }
+
+ public void setNoKeySelected() {
+ mNoKeySelected.setVisibility(View.VISIBLE);
+ mKeyUserId.setVisibility(View.GONE);
+ mKeyUserIdRest.setVisibility(View.GONE);
+ mKeyMasterKeyIdHex.setVisibility(View.GONE);
+ }
+
+ public void setSelectedKeyData(String userName, String email, String masterKeyHex) {
+
+ mNoKeySelected.setVisibility(View.GONE);
+
+ mKeyUserId.setText(userName);
+ mKeyUserIdRest.setText(email);
+ mKeyMasterKeyIdHex.setText(masterKeyHex);
+
+ mKeyUserId.setVisibility(View.VISIBLE);
+ mKeyUserIdRest.setVisibility(View.VISIBLE);
+ mKeyMasterKeyIdHex.setVisibility(View.VISIBLE);
+
+ }
+
+ public void setError(String error) {
+ mNoKeySelected.requestFocus();
+ mNoKeySelected.setError(error);
+ }
+
+ /**
+ * Inflate the layout for this fragment
+ */
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.select_secret_key_layout_fragment, container, false);
+
+ mNoKeySelected = (TextView) view.findViewById(R.id.no_key_selected);
+ mKeyUserId = (TextView) view.findViewById(R.id.select_secret_key_user_id);
+ mKeyUserIdRest = (TextView) view.findViewById(R.id.select_secret_key_user_id_rest);
+ mKeyMasterKeyIdHex = (TextView) view.findViewById(R.id.select_secret_key_master_key_hex);
+ mSelectKeyButton = (BootstrapButton) view
+ .findViewById(R.id.select_secret_key_select_key_button);
+ mFilterCertify = false;
+ mSelectKeyButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ startSelectKeyActivity();
+ }
+ });
+
+ return view;
+ }
+
+ //For AppSettingsFragment
+ public void selectKey(long masterKeyId) {
+ Uri buildUri = KeychainContract.KeyRings.buildGenericKeyRingUri(String.valueOf(masterKeyId));
+ mReceivedUri = buildUri;
+ getActivity().getSupportLoaderManager().restartLoader(LOADER_ID, null, this);
+ }
+
+ private void startSelectKeyActivity() {
+ Intent intent = new Intent(getActivity(), SelectSecretKeyActivity.class);
+ intent.putExtra(SelectSecretKeyActivity.EXTRA_FILTER_CERTIFY, mFilterCertify);
+ startActivityForResult(intent, REQUEST_CODE_SELECT_KEY);
+ }
+
+ @Override
+ public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+ Uri uri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(mReceivedUri);
+ //We don't care about the Loader id
+ return new CursorLoader(getActivity(), uri, PROJECTION, null, null, null);
+ }
+
+ @Override
+ public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+ if (data.moveToFirst()) {
+ String userName, email, masterKeyHex;
+ String userID = data.getString(INDEX_USER_ID);
+ long masterKeyID = data.getLong(INDEX_MASTER_KEY_ID);
+
+ String splitUserID[] = PgpKeyHelper.splitUserId(userID);
+
+ if (splitUserID[0] != null) {
+ userName = splitUserID[0];
+ } else {
+ userName = getActivity().getResources().getString(R.string.user_id_no_name);
+ }
+
+ if (splitUserID[1] != null) {
+ email = splitUserID[1];
+ } else {
+ email = getActivity().getResources().getString(R.string.error_user_id_no_email);
+ }
+
+ //TODO Can the cursor return invalid values for the Master Key ?
+ masterKeyHex = PgpKeyHelper.convertKeyIdToHexShort(masterKeyID);
+
+ //Set the data
+ setSelectedKeyData(userName, email, masterKeyHex);
+
+ //Give value to the callback
+ mCallback.onKeySelected(masterKeyID);
+ } else {
+ //Set The empty View
+ setNoKeySelected();
+ }
+
+ }
+
+ @Override
+ public void onLoaderReset(Loader<Cursor> loader) {
+ return;
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ switch (requestCode) {
+ case REQUEST_CODE_SELECT_KEY: {
+ if (resultCode == Activity.RESULT_OK) {
+ mReceivedUri = data.getData();
+
+ //Must be restartLoader() or the data will not be updated on selecting a new key
+ getActivity().getSupportLoaderManager().restartLoader(0, null, this);
+
+ mKeyUserId.setError(null);
+ }
+ break;
+ }
+
+ default:
+ super.onActivityResult(requestCode, resultCode, data);
+
+ break;
+ }
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java
new file mode 100644
index 000000000..0e231e6a8
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2011 Senecaso
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.sufficientlysecure.keychain.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.v7.app.ActionBarActivity;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.ArrayAdapter;
+import android.widget.Spinner;
+import android.widget.Toast;
+import com.beardedhen.androidbootstrap.BootstrapButton;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.helper.Preferences;
+import org.sufficientlysecure.keychain.service.KeychainIntentService;
+import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
+import org.sufficientlysecure.keychain.util.Log;
+
+/**
+ * Sends the selected public key to a keyserver
+ */
+public class UploadKeyActivity extends ActionBarActivity {
+ private BootstrapButton mUploadButton;
+ private Spinner mKeyServerSpinner;
+
+ private Uri mDataUri;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.key_server_export);
+
+ mUploadButton = (BootstrapButton) findViewById(R.id.btn_export_to_server);
+ mKeyServerSpinner = (Spinner) findViewById(R.id.sign_key_keyserver);
+
+ ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
+ android.R.layout.simple_spinner_item, Preferences.getPreferences(this)
+ .getKeyServers());
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ mKeyServerSpinner.setAdapter(adapter);
+ if (adapter.getCount() > 0) {
+ mKeyServerSpinner.setSelection(0);
+ } else {
+ mUploadButton.setEnabled(false);
+ }
+
+ mUploadButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ uploadKey();
+ }
+ });
+
+ mDataUri = getIntent().getData();
+ if (mDataUri == null) {
+ Log.e(Constants.TAG, "Intent data missing. Should be Uri of key!");
+ finish();
+ return;
+ }
+ }
+
+ 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);
+
+ // set data uri as path to keyring
+ intent.setData(mDataUri);
+
+ // fill values for this action
+ Bundle data = new Bundle();
+
+ 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
+ KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this,
+ getString(R.string.progress_exporting), ProgressDialog.STYLE_HORIZONTAL) {
+ public void handleMessage(Message message) {
+ // handle messages by standard KeychainIntentServiceHandler first
+ super.handleMessage(message);
+
+ if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
+
+ Toast.makeText(UploadKeyActivity.this, R.string.key_send_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);
+
+ // start service with intent
+ startService(intent);
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewCertActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewCertActivity.java
new file mode 100644
index 000000000..294fadab2
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewCertActivity.java
@@ -0,0 +1,253 @@
+/*
+ * 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;
+
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.v4.app.LoaderManager;
+import android.support.v4.content.CursorLoader;
+import android.support.v4.content.Loader;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.ActionBarActivity;
+import android.text.format.DateFormat;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.TextView;
+
+import org.spongycastle.bcpg.SignatureSubpacket;
+import org.spongycastle.bcpg.SignatureSubpacketTags;
+import org.spongycastle.bcpg.sig.RevocationReason;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPKeyRing;
+import org.spongycastle.openpgp.PGPSignature;
+import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.pgp.PgpConversionHelper;
+import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
+import org.sufficientlysecure.keychain.provider.KeychainContract;
+import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
+import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.util.Log;
+
+import java.security.SignatureException;
+import java.util.Date;
+
+public class ViewCertActivity extends ActionBarActivity
+ implements LoaderManager.LoaderCallbacks<Cursor> {
+
+ // These are the rows that we will retrieve.
+ static final String[] PROJECTION = new String[]{
+ Certs.MASTER_KEY_ID,
+ Certs.USER_ID,
+ Certs.TYPE,
+ Certs.CREATION,
+ Certs.KEY_ID_CERTIFIER,
+ Certs.SIGNER_UID,
+ Certs.DATA,
+ };
+ private static final int INDEX_MASTER_KEY_ID = 0;
+ private static final int INDEX_USER_ID = 1;
+ private static final int INDEX_TYPE = 2;
+ private static final int INDEX_CREATION = 3;
+ private static final int INDEX_KEY_ID_CERTIFIER = 4;
+ private static final int INDEX_SIGNER_UID = 5;
+ private static final int INDEX_DATA = 6;
+
+ private Uri mDataUri;
+
+ private long mSignerKeyId;
+
+ private TextView mSigneeKey, mSigneeUid, mAlgorithm, mType, mRReason, mCreation;
+ private TextView mSignerKey, mSignerUid, mStatus;
+ private View mRowReason;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ ActionBar actionBar = getSupportActionBar();
+ actionBar.setDisplayHomeAsUpEnabled(true);
+
+ setContentView(R.layout.view_cert_activity);
+
+ mStatus = (TextView) findViewById(R.id.status);
+ mSigneeKey = (TextView) findViewById(R.id.signee_key);
+ mSigneeUid = (TextView) findViewById(R.id.signee_uid);
+ mAlgorithm = (TextView) findViewById(R.id.algorithm);
+ mType = (TextView) findViewById(R.id.signature_type);
+ mRReason = (TextView) findViewById(R.id.reason);
+ mCreation = (TextView) findViewById(R.id.creation);
+
+ mSignerKey = (TextView) findViewById(R.id.signer_key_id);
+ mSignerUid = (TextView) findViewById(R.id.signer_uid);
+
+ mRowReason = findViewById(R.id.row_reason);
+
+ mDataUri = getIntent().getData();
+ if (mDataUri == null) {
+ Log.e(Constants.TAG, "Intent data missing. Should be Uri of key!");
+ finish();
+ return;
+ }
+
+ getSupportLoaderManager().initLoader(0, null, this);
+ }
+
+ @Override
+ public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+ // Now create and return a CursorLoader that will take care of
+ // creating a Cursor for the data being displayed.
+ return new CursorLoader(this, mDataUri, PROJECTION, null, null, null);
+ }
+
+ @Override
+ public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+ if (data.moveToFirst()) {
+ String signeeKey = "0x" + PgpKeyHelper.convertKeyIdToHex(data.getLong(INDEX_MASTER_KEY_ID));
+ mSigneeKey.setText(signeeKey);
+
+ String signeeUid = data.getString(INDEX_USER_ID);
+ mSigneeUid.setText(signeeUid);
+
+ Date creationDate = new Date(data.getLong(INDEX_CREATION) * 1000);
+ mCreation.setText(DateFormat.getDateFormat(getApplicationContext()).format(creationDate));
+
+ mSignerKeyId = data.getLong(INDEX_KEY_ID_CERTIFIER);
+ String signerKey = "0x" + PgpKeyHelper.convertKeyIdToHex(mSignerKeyId);
+ mSignerKey.setText(signerKey);
+
+ String signerUid = data.getString(INDEX_SIGNER_UID);
+ if (signerUid != null) {
+ mSignerUid.setText(signerUid);
+ } else {
+ mSignerUid.setText(R.string.unknown_uid);
+ }
+
+ PGPSignature sig = PgpConversionHelper.BytesToPGPSignature(data.getBlob(INDEX_DATA));
+ PGPKeyRing signeeRing = ProviderHelper.getPGPKeyRing(this,
+ KeychainContract.KeyRingData.buildPublicKeyRingUri(
+ Long.toString(data.getLong(INDEX_MASTER_KEY_ID))));
+ PGPKeyRing signerRing = ProviderHelper.getPGPKeyRing(this,
+ KeychainContract.KeyRingData.buildPublicKeyRingUri(
+ Long.toString(sig.getKeyID())));
+
+ if (signerRing != null) {
+ try {
+ sig.init(new JcaPGPContentVerifierBuilderProvider().setProvider(
+ Constants.BOUNCY_CASTLE_PROVIDER_NAME), signeeRing.getPublicKey());
+ if (sig.verifyCertification(signeeUid, signerRing.getPublicKey())) {
+ mStatus.setText("ok");
+ mStatus.setTextColor(getResources().getColor(R.color.bbutton_success));
+ } else {
+ mStatus.setText("failed!");
+ mStatus.setTextColor(getResources().getColor(R.color.alert));
+ }
+ } catch (SignatureException e) {
+ mStatus.setText("error!");
+ mStatus.setTextColor(getResources().getColor(R.color.alert));
+ } catch (PGPException e) {
+ mStatus.setText("error!");
+ mStatus.setTextColor(getResources().getColor(R.color.alert));
+ }
+ } else {
+ mStatus.setText("key unavailable");
+ mStatus.setTextColor(getResources().getColor(R.color.black));
+ }
+
+ String algorithmStr = PgpKeyHelper.getAlgorithmInfo(sig.getKeyAlgorithm(), 0);
+ mAlgorithm.setText(algorithmStr);
+
+ mRowReason.setVisibility(View.GONE);
+ switch (data.getInt(INDEX_TYPE)) {
+ case PGPSignature.DEFAULT_CERTIFICATION:
+ mType.setText(R.string.cert_default);
+ break;
+ case PGPSignature.NO_CERTIFICATION:
+ mType.setText(R.string.cert_none);
+ break;
+ case PGPSignature.CASUAL_CERTIFICATION:
+ mType.setText(R.string.cert_casual);
+ break;
+ case PGPSignature.POSITIVE_CERTIFICATION:
+ mType.setText(R.string.cert_positive);
+ break;
+ case PGPSignature.CERTIFICATION_REVOCATION: {
+ mType.setText(R.string.cert_revoke);
+ if (sig.getHashedSubPackets().hasSubpacket(SignatureSubpacketTags.REVOCATION_REASON)) {
+ SignatureSubpacket p = sig.getHashedSubPackets().getSubpacket(
+ SignatureSubpacketTags.REVOCATION_REASON);
+ // For some reason, this is missing in SignatureSubpacketInputStream:146
+ if (!(p instanceof RevocationReason)) {
+ p = new RevocationReason(false, p.getData());
+ }
+ String reason = ((RevocationReason) p).getRevocationDescription();
+ mRReason.setText(reason);
+ mRowReason.setVisibility(View.VISIBLE);
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onLoaderReset(Loader<Cursor> loader) {
+ }
+
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ getMenuInflater().inflate(R.menu.view_cert, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.menu_view_cert_view_signer:
+ // can't do this before the data is initialized
+ Intent viewIntent = null;
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
+ viewIntent = new Intent(this, ViewKeyActivity.class);
+ } else {
+ viewIntent = new Intent(this, ViewKeyActivityJB.class);
+ }
+ //
+ long signerMasterKeyId = ProviderHelper.getMasterKeyId(this,
+ KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(Long.toString(mSignerKeyId))
+ );
+ // TODO notify user of this, maybe offer download?
+ if (mSignerKeyId == 0L)
+ return true;
+ viewIntent.setData(KeyRings.buildGenericKeyRingUri(
+ Long.toString(signerMasterKeyId))
+ );
+ startActivity(viewIntent);
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java
new file mode 100644
index 000000000..cce34139c
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2013-2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2013 Bahtiar 'kalkin' Gadimov
+ *
+ * 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.os.Handler;
+import android.os.Message;
+import android.support.v4.view.ViewPager;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.ActionBarActivity;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.Window;
+import android.widget.Toast;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.Id;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
+import org.sufficientlysecure.keychain.helper.ExportHelper;
+import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
+import org.sufficientlysecure.keychain.provider.KeychainContract;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.ui.adapter.TabsAdapter;
+import org.sufficientlysecure.keychain.ui.dialog.ShareNfcDialogFragment;
+import org.sufficientlysecure.keychain.ui.dialog.ShareQrCodeDialogFragment;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class ViewKeyActivity extends ActionBarActivity {
+
+ ExportHelper mExportHelper;
+
+ protected Uri mDataUri;
+
+ public static final String EXTRA_SELECTED_TAB = "selectedTab";
+
+ ViewPager mViewPager;
+ TabsAdapter mTabsAdapter;
+
+ private static final int RESULT_CODE_LOOKUP_KEY = 0x00007006;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
+ super.onCreate(savedInstanceState);
+
+ mExportHelper = new ExportHelper(this);
+
+ // let the actionbar look like Android's contact app
+ ActionBar actionBar = getSupportActionBar();
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ actionBar.setIcon(android.R.color.transparent);
+ actionBar.setHomeButtonEnabled(true);
+ actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
+
+ setContentView(R.layout.view_key_activity);
+
+ mViewPager = (ViewPager) findViewById(R.id.pager);
+
+ mTabsAdapter = new TabsAdapter(this, mViewPager);
+
+ int selectedTab = 0;
+ Intent intent = getIntent();
+ if (intent.getExtras() != null && intent.getExtras().containsKey(EXTRA_SELECTED_TAB)) {
+ selectedTab = intent.getExtras().getInt(EXTRA_SELECTED_TAB);
+ }
+
+ mDataUri = getIntent().getData();
+
+ Bundle mainBundle = new Bundle();
+ mainBundle.putParcelable(ViewKeyMainFragment.ARG_DATA_URI, mDataUri);
+ mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.key_view_tab_main)),
+ ViewKeyMainFragment.class, mainBundle, (selectedTab == 0));
+
+ Bundle certBundle = new Bundle();
+ certBundle.putParcelable(ViewKeyCertsFragment.ARG_DATA_URI, mDataUri);
+ mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.key_view_tab_certs)),
+ ViewKeyCertsFragment.class, certBundle, (selectedTab == 1));
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ getMenuInflater().inflate(R.menu.key_view, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ Intent homeIntent = new Intent(this, KeyListActivity.class);
+ homeIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ startActivity(homeIntent);
+ return true;
+ case R.id.menu_key_view_update:
+ updateFromKeyserver(mDataUri);
+ return true;
+ case R.id.menu_key_view_export_keyserver:
+ uploadToKeyserver(mDataUri);
+ return true;
+ case R.id.menu_key_view_export_file:
+ exportToFile(mDataUri);
+ return true;
+ case R.id.menu_key_view_share_default_fingerprint:
+ shareKey(mDataUri, true);
+ return true;
+ case R.id.menu_key_view_share_default:
+ shareKey(mDataUri, false);
+ return true;
+ case R.id.menu_key_view_share_qr_code_fingerprint:
+ shareKeyQrCode(mDataUri, true);
+ return true;
+ case R.id.menu_key_view_share_qr_code:
+ shareKeyQrCode(mDataUri, false);
+ return true;
+ case R.id.menu_key_view_share_nfc:
+ shareNfc();
+ return true;
+ case R.id.menu_key_view_share_clipboard:
+ copyToClipboard(mDataUri);
+ return true;
+ case R.id.menu_key_view_delete: {
+ deleteKey(mDataUri);
+ return true;
+ }
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ private void exportToFile(Uri dataUri) {
+ Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(dataUri);
+
+ HashMap<String, Object> data = ProviderHelper.getGenericData(this,
+ baseUri,
+ new String[]{KeychainContract.Keys.MASTER_KEY_ID, KeychainContract.KeyRings.HAS_SECRET},
+ new int[]{ProviderHelper.FIELD_TYPE_INTEGER, ProviderHelper.FIELD_TYPE_INTEGER});
+
+ mExportHelper.showExportKeysDialog(
+ new long[]{(Long) data.get(KeychainContract.KeyRings.MASTER_KEY_ID)},
+ Constants.Path.APP_DIR_FILE,
+ ((Long) data.get(KeychainContract.KeyRings.HAS_SECRET) == 1)
+ );
+ }
+
+ private void uploadToKeyserver(Uri dataUri) {
+ Intent uploadIntent = new Intent(this, UploadKeyActivity.class);
+ uploadIntent.setData(dataUri);
+ startActivityForResult(uploadIntent, Id.request.export_to_server);
+ }
+
+ private void updateFromKeyserver(Uri dataUri) {
+ byte[] blob = (byte[]) ProviderHelper.getGenericData(
+ this, KeychainContract.KeyRings.buildUnifiedKeyRingUri(dataUri),
+ KeychainContract.Keys.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB);
+ String fingerprint = PgpKeyHelper.convertFingerprintToHex(blob);
+
+ Intent queryIntent = new Intent(this, ImportKeysActivity.class);
+ queryIntent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN);
+ queryIntent.putExtra(ImportKeysActivity.EXTRA_FINGERPRINT, fingerprint);
+
+ startActivityForResult(queryIntent, RESULT_CODE_LOOKUP_KEY);
+ }
+
+ private void shareKey(Uri dataUri, boolean fingerprintOnly) {
+ String content;
+ if (fingerprintOnly) {
+ byte[] data = (byte[]) ProviderHelper.getGenericData(
+ this, KeychainContract.KeyRings.buildUnifiedKeyRingUri(dataUri),
+ KeychainContract.Keys.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB);
+ if (data != null) {
+ String fingerprint = PgpKeyHelper.convertFingerprintToHex(data);
+ content = Constants.FINGERPRINT_SCHEME + ":" + fingerprint;
+ } else {
+ Toast.makeText(getApplicationContext(), "Bad key selected!",
+ Toast.LENGTH_LONG).show();
+ return;
+ }
+ } else {
+ // get public keyring as ascii armored string
+ long masterKeyId = ProviderHelper.getMasterKeyId(this, dataUri);
+ ArrayList<String> keyringArmored = ProviderHelper.getKeyRingsAsArmoredString(
+ this, new long[]{masterKeyId});
+
+ content = keyringArmored.get(0);
+
+ // Android will fail with android.os.TransactionTooLargeException if key is too big
+ // see http://www.lonestarprod.com/?p=34
+ if (content.length() >= 86389) {
+ Toast.makeText(getApplicationContext(), R.string.key_too_big_for_sharing,
+ Toast.LENGTH_LONG).show();
+ return;
+ }
+ }
+
+ // let user choose application
+ Intent sendIntent = new Intent(Intent.ACTION_SEND);
+ sendIntent.putExtra(Intent.EXTRA_TEXT, content);
+ sendIntent.setType("text/plain");
+ startActivity(Intent.createChooser(sendIntent,
+ getResources().getText(R.string.action_share_key_with)));
+ }
+
+ private void shareKeyQrCode(Uri dataUri, boolean fingerprintOnly) {
+ ShareQrCodeDialogFragment dialog = ShareQrCodeDialogFragment.newInstance(dataUri,
+ fingerprintOnly);
+ dialog.show(getSupportFragmentManager(), "shareQrCodeDialog");
+ }
+
+ private void copyToClipboard(Uri dataUri) {
+ // get public keyring as ascii armored string
+ long masterKeyId = ProviderHelper.getMasterKeyId(this, dataUri);
+ ArrayList<String> keyringArmored = ProviderHelper.getKeyRingsAsArmoredString(
+ this, new long[]{masterKeyId});
+
+ ClipboardReflection.copyToClipboard(this, keyringArmored.get(0));
+ Toast.makeText(getApplicationContext(), R.string.key_copied_to_clipboard, Toast.LENGTH_LONG)
+ .show();
+ }
+
+ private void shareNfc() {
+ ShareNfcDialogFragment dialog = ShareNfcDialogFragment.newInstance();
+ dialog.show(getSupportFragmentManager(), "shareNfcDialog");
+ }
+
+ private void deleteKey(Uri dataUri) {
+ // Message is received after key is deleted
+ Handler returnHandler = new Handler() {
+ @Override
+ public void handleMessage(Message message) {
+ setResult(RESULT_CANCELED);
+ finish();
+ }
+ };
+
+ mExportHelper.deleteKey(dataUri, returnHandler);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ switch (requestCode) {
+ case RESULT_CODE_LOOKUP_KEY: {
+ if (resultCode == Activity.RESULT_OK) {
+ // TODO: reload key??? move this into fragment?
+ }
+ break;
+ }
+
+ default: {
+ super.onActivityResult(requestCode, resultCode, data);
+
+ break;
+ }
+ }
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivityJB.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivityJB.java
new file mode 100644
index 000000000..6dc0413bb
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivityJB.java
@@ -0,0 +1,124 @@
+/*
+ * 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;
+
+import android.annotation.TargetApi;
+import android.nfc.NdefMessage;
+import android.nfc.NdefRecord;
+import android.nfc.NfcAdapter;
+import android.nfc.NfcAdapter.CreateNdefMessageCallback;
+import android.nfc.NfcAdapter.OnNdefPushCompleteCallback;
+import android.nfc.NfcEvent;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.widget.Toast;
+
+import com.devspark.appmsg.AppMsg;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.util.Log;
+
+import java.io.IOException;
+
+@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+public class ViewKeyActivityJB extends ViewKeyActivity implements CreateNdefMessageCallback,
+ OnNdefPushCompleteCallback {
+
+ private NfcAdapter mNfcAdapter;
+ private byte[] mSharedKeyringBytes;
+ private static final int NFC_SENT = 1;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ initNfc();
+ }
+
+ /**
+ * NFC: Initialize NFC sharing if OS and device supports it
+ */
+ private void initNfc() {
+ // check if NFC Beam is supported (>= Android 4.1)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ // Check for available NFC Adapter
+ mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
+ if (mNfcAdapter != null) {
+ // init nfc
+ // Register callback to set NDEF message
+ mNfcAdapter.setNdefPushMessageCallback(this, this);
+ // Register callback to listen for message-sent success
+ mNfcAdapter.setOnNdefPushCompleteCallback(this, this);
+ }
+ }
+ }
+
+ /**
+ * NFC: Implementation for the CreateNdefMessageCallback interface
+ */
+ @Override
+ public NdefMessage createNdefMessage(NfcEvent event) {
+ /**
+ * When a device receives a push with an AAR in it, the application specified in the AAR is
+ * guaranteed to run. The AAR overrides the tag dispatch system. You can add it back in to
+ * guarantee that this activity starts when receiving a beamed message. For now, this code
+ * uses the tag dispatch system.
+ */
+ try {
+ // get public keyring as byte array
+ mSharedKeyringBytes = ProviderHelper.getPGPKeyRing(this, mDataUri).getEncoded();
+
+ NdefMessage msg = new NdefMessage(NdefRecord.createMime(Constants.NFC_MIME,
+ mSharedKeyringBytes), NdefRecord.createApplicationRecord(Constants.PACKAGE_NAME));
+ return msg;
+ } catch(IOException e) {
+ Log.e(Constants.TAG, "Error parsing keyring", e);
+ return null;
+ }
+ }
+
+ /**
+ * NFC: Implementation for the OnNdefPushCompleteCallback interface
+ */
+ @Override
+ public void onNdefPushComplete(NfcEvent arg0) {
+ // A handler is needed to send messages to the activity when this
+ // callback occurs, because it happens from a binder thread
+ mNfcHandler.obtainMessage(NFC_SENT).sendToTarget();
+ }
+
+ /**
+ * NFC: This handler receives a message from onNdefPushComplete
+ */
+ private final Handler mNfcHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case NFC_SENT:
+ AppMsg.makeText(ViewKeyActivityJB.this, R.string.nfc_successfull,
+ AppMsg.STYLE_INFO).show();
+ break;
+ }
+ }
+ };
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyCertsFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyCertsFragment.java
new file mode 100644
index 000000000..b738970f1
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyCertsFragment.java
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.ui;
+
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.LoaderManager;
+import android.support.v4.content.CursorLoader;
+import android.support.v4.content.Loader;
+import android.support.v4.widget.CursorAdapter;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.TextView;
+
+import org.spongycastle.openpgp.PGPSignature;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
+import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
+import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
+import org.sufficientlysecure.keychain.util.Log;
+
+import se.emilsjolander.stickylistheaders.ApiLevelTooLowException;
+import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter;
+import se.emilsjolander.stickylistheaders.StickyListHeadersListView;
+
+
+public class ViewKeyCertsFragment extends Fragment
+ implements LoaderManager.LoaderCallbacks<Cursor>, AdapterView.OnItemClickListener {
+
+ // These are the rows that we will retrieve.
+ static final String[] PROJECTION = new String[] {
+ Certs._ID,
+ Certs.MASTER_KEY_ID,
+ Certs.VERIFIED,
+ Certs.TYPE,
+ Certs.RANK,
+ Certs.KEY_ID_CERTIFIER,
+ Certs.USER_ID,
+ Certs.SIGNER_UID
+ };
+
+ // sort by our user id,
+ static final String SORT_ORDER =
+ Tables.CERTS + "." + Certs.RANK + " ASC, "
+ + Certs.VERIFIED + " DESC, " + Certs.TYPE + " DESC, " + Certs.SIGNER_UID + " ASC";
+
+ public static final String ARG_DATA_URI = "data_uri";
+
+ private StickyListHeadersListView mStickyList;
+ private CertListAdapter mAdapter;
+
+ private Uri mDataUri;
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.view_key_certs_fragment, container, false);
+
+ return view;
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ mStickyList = (StickyListHeadersListView) getActivity().findViewById(R.id.list);
+
+ if (!getArguments().containsKey(ARG_DATA_URI)) {
+ Log.e(Constants.TAG, "Data missing. Should be Uri of key!");
+ getActivity().finish();
+ return;
+ }
+
+ Uri uri = getArguments().getParcelable(ARG_DATA_URI);
+ mDataUri = Certs.buildCertsUri(uri);
+
+ mStickyList.setAreHeadersSticky(true);
+ mStickyList.setDrawingListUnderStickyHeader(false);
+ mStickyList.setFastScrollEnabled(true);
+ mStickyList.setOnItemClickListener(this);
+
+ try {
+ mStickyList.setFastScrollAlwaysVisible(true);
+ } catch (ApiLevelTooLowException e) {
+ }
+
+ mStickyList.setEmptyView(getActivity().findViewById(R.id.empty));
+
+ // TODO this view is made visible if no data is available
+ // mStickyList.setEmptyView(getActivity().findViewById(R.id.empty));
+
+
+ // Create an empty adapter we will use to display the loaded data.
+ mAdapter = new CertListAdapter(getActivity(), null);
+ mStickyList.setAdapter(mAdapter);
+
+ getLoaderManager().initLoader(0, null, this);
+ }
+
+ @Override
+ public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+ // Now create and return a CursorLoader that will take care of
+ // creating a Cursor for the data being displayed.
+ return new CursorLoader(getActivity(), mDataUri, PROJECTION, null, null, SORT_ORDER);
+ }
+
+ @Override
+ public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+ // Swap the new cursor in. (The framework will take care of closing the
+ // old cursor once we return.)
+ mAdapter.swapCursor(data);
+
+ mStickyList.setAdapter(mAdapter);
+ }
+
+ /**
+ * On click on item, start key view activity
+ */
+ @Override
+ public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
+ if(view.getTag(R.id.tag_mki) != null) {
+ long masterKeyId = (Long) view.getTag(R.id.tag_mki);
+ long rank = (Long) view.getTag(R.id.tag_rank);
+ long certifierId = (Long) view.getTag(R.id.tag_certifierId);
+
+ Intent viewIntent = new Intent(getActivity(), ViewCertActivity.class);
+ viewIntent.setData(Certs.buildCertsSpecificUri(
+ Long.toString(masterKeyId), Long.toString(rank), Long.toString(certifierId)));
+ startActivity(viewIntent);
+ }
+ }
+
+ @Override
+ public void onLoaderReset(Loader<Cursor> loader) {
+ // This is called when the last Cursor provided to onLoadFinished()
+ // above is about to be closed. We need to make sure we are no
+ // longer using it.
+ mAdapter.swapCursor(null);
+ }
+
+ /**
+ * Implements StickyListHeadersAdapter from library
+ */
+ private class CertListAdapter extends CursorAdapter implements StickyListHeadersAdapter {
+ private LayoutInflater mInflater;
+ private int mIndexMasterKeyId, mIndexUserId, mIndexRank;
+ private int mIndexSignerKeyId, mIndexSignerUserId;
+ private int mIndexVerified, mIndexType;
+
+ public CertListAdapter(Context context, Cursor c) {
+ super(context, c, 0);
+
+ mInflater = LayoutInflater.from(context);
+ initIndex(c);
+ }
+
+ @Override
+ public Cursor swapCursor(Cursor newCursor) {
+ initIndex(newCursor);
+
+ return super.swapCursor(newCursor);
+ }
+
+ /**
+ * Get column indexes for performance reasons just once in constructor and swapCursor. For a
+ * performance comparison see http://stackoverflow.com/a/17999582
+ *
+ * @param cursor
+ */
+ private void initIndex(Cursor cursor) {
+ if (cursor != null) {
+ mIndexMasterKeyId = cursor.getColumnIndexOrThrow(Certs.MASTER_KEY_ID);
+ mIndexUserId = cursor.getColumnIndexOrThrow(Certs.USER_ID);
+ mIndexRank = cursor.getColumnIndexOrThrow(Certs.RANK);
+ mIndexType = cursor.getColumnIndexOrThrow(Certs.TYPE);
+ mIndexVerified = cursor.getColumnIndexOrThrow(Certs.VERIFIED);
+ mIndexSignerKeyId = cursor.getColumnIndexOrThrow(Certs.KEY_ID_CERTIFIER);
+ mIndexSignerUserId = cursor.getColumnIndexOrThrow(Certs.SIGNER_UID);
+ }
+ }
+
+ /**
+ * Bind cursor data to the item list view
+ * <p/>
+ * NOTE: CursorAdapter already implements the ViewHolder pattern in its getView() method.
+ * Thus no ViewHolder is required here.
+ */
+ @Override
+ public void bindView(View view, Context context, Cursor cursor) {
+
+ // set name and stuff, common to both key types
+ TextView wSignerKeyId = (TextView) view.findViewById(R.id.signerKeyId);
+ TextView wSignerUserId = (TextView) view.findViewById(R.id.signerUserId);
+ TextView wSignStatus = (TextView) view.findViewById(R.id.signStatus);
+
+ String signerKeyId = PgpKeyHelper.convertKeyIdToHex(cursor.getLong(mIndexSignerKeyId));
+ String signerUserId = cursor.getString(mIndexSignerUserId);
+ switch(cursor.getInt(mIndexType)) {
+ case PGPSignature.DEFAULT_CERTIFICATION: // 0x10
+ wSignStatus.setText(R.string.cert_default); break;
+ case PGPSignature.NO_CERTIFICATION: // 0x11
+ wSignStatus.setText(R.string.cert_none); break;
+ case PGPSignature.CASUAL_CERTIFICATION: // 0x12
+ wSignStatus.setText(R.string.cert_casual); break;
+ case PGPSignature.POSITIVE_CERTIFICATION: // 0x13
+ wSignStatus.setText(R.string.cert_positive); break;
+ case PGPSignature.CERTIFICATION_REVOCATION: // 0x30
+ wSignStatus.setText(R.string.cert_revoke); break;
+ }
+
+ wSignerUserId.setText(signerUserId);
+ wSignerKeyId.setText(signerKeyId);
+
+ view.setTag(R.id.tag_mki, cursor.getLong(mIndexMasterKeyId));
+ view.setTag(R.id.tag_rank, cursor.getLong(mIndexRank));
+ view.setTag(R.id.tag_certifierId, cursor.getLong(mIndexSignerKeyId));
+ }
+
+ @Override
+ public View newView(Context context, Cursor cursor, ViewGroup parent) {
+ return mInflater.inflate(R.layout.view_key_certs_item, parent, false);
+ }
+
+ /**
+ * Creates a new header view and binds the section headers to it. It uses the ViewHolder
+ * pattern. Most functionality is similar to getView() from Android's CursorAdapter.
+ * <p/>
+ * NOTE: The variables mDataValid and mCursor are available due to the super class
+ * CursorAdapter.
+ */
+ @Override
+ public View getHeaderView(int position, View convertView, ViewGroup parent) {
+ HeaderViewHolder holder;
+ if (convertView == null) {
+ holder = new HeaderViewHolder();
+ convertView = mInflater.inflate(R.layout.view_key_certs_header, parent, false);
+ holder.text = (TextView) convertView.findViewById(R.id.stickylist_header_text);
+ holder.count = (TextView) convertView.findViewById(R.id.certs_num);
+ convertView.setTag(holder);
+ } else {
+ holder = (HeaderViewHolder) convertView.getTag();
+ }
+
+ if (!mDataValid) {
+ // no data available at this point
+ Log.d(Constants.TAG, "getHeaderView: No data available at this point!");
+ return convertView;
+ }
+
+ if (!mCursor.moveToPosition(position)) {
+ throw new IllegalStateException("couldn't move cursor to position " + position);
+ }
+
+ // set header text as first char in user id
+ String userId = mCursor.getString(mIndexUserId);
+ holder.text.setText(userId);
+ holder.count.setVisibility(View.GONE);
+ return convertView;
+ }
+
+ /**
+ * Header IDs should be static, position=1 should always return the same Id that is.
+ */
+ @Override
+ public long getHeaderId(int position) {
+ if (!mDataValid) {
+ // no data available at this point
+ Log.d(Constants.TAG, "getHeaderView: No data available at this point!");
+ return -1;
+ }
+
+ if (!mCursor.moveToPosition(position)) {
+ throw new IllegalStateException("couldn't move cursor to position " + position);
+ }
+
+ // otherwise, return the first character of the name as ID
+ return mCursor.getInt(mIndexRank);
+
+ // sort by the first four characters (should be enough I guess?)
+ // return ByteBuffer.wrap(userId.getBytes()).asLongBuffer().get(0);
+ }
+
+ class HeaderViewHolder {
+ TextView text;
+ TextView count;
+ }
+
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java
new file mode 100644
index 000000000..6e96a338a
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java
@@ -0,0 +1,347 @@
+/*
+ * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.ui;
+
+import android.content.Intent;
+import android.database.Cursor;
+import android.graphics.Color;
+import android.net.Uri;
+import android.os.Bundle;
+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.text.format.DateFormat;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import com.beardedhen.androidbootstrap.BootstrapButton;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
+import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
+import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
+import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.ui.adapter.ViewKeyKeysAdapter;
+import org.sufficientlysecure.keychain.ui.adapter.ViewKeyUserIdsAdapter;
+import org.sufficientlysecure.keychain.util.Log;
+
+import java.util.Date;
+
+
+public class ViewKeyMainFragment extends Fragment implements
+ LoaderManager.LoaderCallbacks<Cursor> {
+
+ public static final String ARG_DATA_URI = "uri";
+
+ private LinearLayout mContainer;
+ private TextView mName;
+ private TextView mEmail;
+ private TextView mComment;
+ private TextView mAlgorithm;
+ private TextView mKeyId;
+ private TextView mExpiry;
+ private TextView mCreation;
+ private TextView mFingerprint;
+ private TextView mSecretKey;
+ private BootstrapButton mActionEdit;
+ private BootstrapButton mActionEncrypt;
+ private BootstrapButton mActionCertify;
+
+ private ListView mUserIds;
+ private ListView mKeys;
+
+ private static final int LOADER_ID_UNIFIED = 0;
+ private static final int LOADER_ID_USER_IDS = 1;
+ private static final int LOADER_ID_KEYS = 2;
+
+ private ViewKeyUserIdsAdapter mUserIdsAdapter;
+ private ViewKeyKeysAdapter mKeysAdapter;
+
+ private Uri mDataUri;
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.view_key_main_fragment, container, false);
+
+ mContainer = (LinearLayout) view.findViewById(R.id.container);
+ mName = (TextView) view.findViewById(R.id.name);
+ mEmail = (TextView) view.findViewById(R.id.email);
+ mComment = (TextView) view.findViewById(R.id.comment);
+ mKeyId = (TextView) view.findViewById(R.id.key_id);
+ mAlgorithm = (TextView) view.findViewById(R.id.algorithm);
+ mCreation = (TextView) view.findViewById(R.id.creation);
+ mExpiry = (TextView) view.findViewById(R.id.expiry);
+ mFingerprint = (TextView) view.findViewById(R.id.fingerprint);
+ mSecretKey = (TextView) view.findViewById(R.id.secret_key);
+ mUserIds = (ListView) view.findViewById(R.id.user_ids);
+ mKeys = (ListView) view.findViewById(R.id.keys);
+ mActionEdit = (BootstrapButton) view.findViewById(R.id.action_edit);
+ mActionEncrypt = (BootstrapButton) view.findViewById(R.id.action_encrypt);
+ mActionCertify = (BootstrapButton) view.findViewById(R.id.action_certify);
+
+ return view;
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ Uri dataUri = getArguments().getParcelable(ARG_DATA_URI);
+ if (dataUri == null) {
+ Log.e(Constants.TAG, "Data missing. Should be Uri of key!");
+ getActivity().finish();
+ return;
+ }
+
+ loadData(dataUri);
+ }
+
+ private void loadData(Uri dataUri) {
+ if (dataUri.equals(mDataUri)) {
+ Log.d(Constants.TAG, "Same URI, no need to load the data again!");
+ return;
+ }
+
+ getActivity().setProgressBarIndeterminateVisibility(Boolean.TRUE);
+ mContainer.setVisibility(View.GONE);
+
+ mDataUri = dataUri;
+
+ Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString());
+
+ mActionEncrypt.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ encryptToContact(mDataUri);
+ }
+ });
+ mActionCertify.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View view) {
+ certifyKey(mDataUri);
+ }
+ });
+
+ mUserIdsAdapter = new ViewKeyUserIdsAdapter(getActivity(), null, 0);
+ mUserIds.setAdapter(mUserIdsAdapter);
+
+ mKeysAdapter = new ViewKeyKeysAdapter(getActivity(), null, 0);
+ mKeys.setAdapter(mKeysAdapter);
+
+ // Prepare the loaders. Either re-connect with an existing ones,
+ // or start new ones.
+ getActivity().getSupportLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this);
+ getActivity().getSupportLoaderManager().initLoader(LOADER_ID_USER_IDS, null, this);
+ getActivity().getSupportLoaderManager().initLoader(LOADER_ID_KEYS, null, this);
+ }
+
+ static final String[] UNIFIED_PROJECTION = new String[] {
+ KeyRings._ID, KeyRings.MASTER_KEY_ID, KeyRings.HAS_SECRET,
+ KeyRings.USER_ID, KeyRings.FINGERPRINT,
+ KeyRings.ALGORITHM, KeyRings.KEY_SIZE, KeyRings.CREATION, KeyRings.EXPIRY,
+
+ };
+ static final int INDEX_UNIFIED_MKI = 1;
+ static final int INDEX_UNIFIED_HAS_SECRET = 2;
+ static final int INDEX_UNIFIED_UID = 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_EXPIRY = 8;
+
+ static final String[] KEYS_PROJECTION = new String[] {
+ Keys._ID,
+ Keys.KEY_ID, Keys.RANK, Keys.ALGORITHM, Keys.KEY_SIZE,
+ Keys.CAN_CERTIFY, Keys.CAN_ENCRYPT, Keys.CAN_SIGN, Keys.IS_REVOKED,
+ Keys.CREATION, Keys.EXPIRY, Keys.FINGERPRINT
+ };
+ static final int KEYS_INDEX_CAN_ENCRYPT = 6;
+
+ public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+ switch (id) {
+ case LOADER_ID_UNIFIED: {
+ Uri baseUri = KeyRings.buildUnifiedKeyRingUri(mDataUri);
+ return new CursorLoader(getActivity(), baseUri, UNIFIED_PROJECTION, null, null, null);
+ }
+ case LOADER_ID_USER_IDS: {
+ Uri baseUri = UserIds.buildUserIdsUri(mDataUri);
+ return new CursorLoader(getActivity(), baseUri, ViewKeyUserIdsAdapter.USER_IDS_PROJECTION, null, null, null);
+ }
+ case LOADER_ID_KEYS: {
+ Uri baseUri = Keys.buildKeysUri(mDataUri);
+ return new CursorLoader(getActivity(), baseUri, KEYS_PROJECTION, null, null, null);
+ }
+
+ default:
+ return null;
+ }
+ }
+
+ 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)
+ return;
+ // 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()) {
+ // get name, email, and comment from USER_ID
+ String[] mainUserId = PgpKeyHelper.splitUserId(data.getString(INDEX_UNIFIED_UID));
+ if (mainUserId[0] != null) {
+ getActivity().setTitle(mainUserId[0]);
+ mName.setText(mainUserId[0]);
+ } else {
+ getActivity().setTitle(R.string.user_id_no_name);
+ mName.setText(R.string.user_id_no_name);
+ }
+ mEmail.setText(mainUserId[1]);
+ mComment.setText(mainUserId[2]);
+
+ if (data.getInt(INDEX_UNIFIED_HAS_SECRET) != 0) {
+ mSecretKey.setTextColor(getResources().getColor(R.color.emphasis));
+ mSecretKey.setText(R.string.secret_key_yes);
+
+ // edit button
+ mActionEdit.setVisibility(View.VISIBLE);
+ mActionEdit.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View view) {
+ Intent editIntent = new Intent(getActivity(), EditKeyActivity.class);
+ editIntent.setData(mDataUri);
+ editIntent.setAction(EditKeyActivity.ACTION_EDIT_KEY);
+ startActivityForResult(editIntent, 0);
+ }
+ });
+ } else {
+ mSecretKey.setTextColor(Color.BLACK);
+ mSecretKey.setText(getResources().getString(R.string.secret_key_no));
+
+ // certify button
+ mActionCertify.setVisibility(View.VISIBLE);
+ // edit button
+ mActionEdit.setVisibility(View.GONE);
+ }
+
+ // get key id from MASTER_KEY_ID
+ long masterKeyId = data.getLong(INDEX_UNIFIED_MKI);
+ String keyIdStr = PgpKeyHelper.convertKeyIdToHex(masterKeyId);
+ mKeyId.setText(keyIdStr);
+
+ // get creation date from CREATION
+ if (data.isNull(INDEX_UNIFIED_CREATION)) {
+ mCreation.setText(R.string.none);
+ } else {
+ Date creationDate = new Date(data.getLong(INDEX_UNIFIED_CREATION) * 1000);
+
+ mCreation.setText(
+ DateFormat.getDateFormat(getActivity().getApplicationContext()).format(
+ creationDate));
+ }
+
+ // get expiry date from EXPIRY
+ if (data.isNull(INDEX_UNIFIED_EXPIRY)) {
+ mExpiry.setText(R.string.none);
+ } else {
+ Date expiryDate = new Date(data.getLong(INDEX_UNIFIED_EXPIRY) * 1000);
+
+ mExpiry.setText(
+ DateFormat.getDateFormat(getActivity().getApplicationContext()).format(
+ expiryDate));
+ }
+
+ String algorithmStr = PgpKeyHelper.getAlgorithmInfo(
+ data.getInt(INDEX_UNIFIED_ALGORITHM), data.getInt(INDEX_UNIFIED_KEY_SIZE));
+ mAlgorithm.setText(algorithmStr);
+
+ byte[] fingerprintBlob = data.getBlob(INDEX_UNIFIED_FINGERPRINT);
+ String fingerprint = PgpKeyHelper.convertFingerprintToHex(fingerprintBlob);
+ mFingerprint.setText(PgpKeyHelper.colorizeFingerprint(fingerprint));
+
+ break;
+ }
+ }
+
+ case LOADER_ID_USER_IDS:
+ mUserIdsAdapter.swapCursor(data);
+ break;
+
+ case LOADER_ID_KEYS:
+ // hide encrypt button if no encryption key is available
+ boolean canEncrypt = false;
+ data.moveToFirst();
+ do {
+ if (data.getInt(KEYS_INDEX_CAN_ENCRYPT) == 1) {
+ canEncrypt = true;
+ break;
+ }
+ } while (data.moveToNext());
+ if (!canEncrypt) {
+ mActionEncrypt.setVisibility(View.GONE);
+ }
+
+ mKeysAdapter.swapCursor(data);
+ break;
+ }
+ getActivity().setProgressBarIndeterminateVisibility(Boolean.FALSE);
+ mContainer.setVisibility(View.VISIBLE);
+ }
+
+ /**
+ * 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.
+ */
+ public void onLoaderReset(Loader<Cursor> loader) {
+ switch (loader.getId()) {
+ case LOADER_ID_USER_IDS:
+ mUserIdsAdapter.swapCursor(null);
+ break;
+ case LOADER_ID_KEYS:
+ mKeysAdapter.swapCursor(null);
+ break;
+ }
+ }
+
+ private void encryptToContact(Uri dataUri) {
+ // TODO preselect from uri? should be feasible without trivial query
+ long keyId = ProviderHelper.getMasterKeyId(getActivity(), dataUri);
+
+ long[] encryptionKeyIds = new long[]{ keyId };
+ Intent intent = new Intent(getActivity(), EncryptActivity.class);
+ intent.setAction(EncryptActivity.ACTION_ENCRYPT);
+ intent.putExtra(EncryptActivity.EXTRA_ENCRYPTION_KEY_IDS, encryptionKeyIds);
+ // used instead of startActivity set actionbar based on callingPackage
+ startActivityForResult(intent, 0);
+ }
+
+ private void certifyKey(Uri dataUri) {
+ Intent signIntent = new Intent(getActivity(), CertifyKeyActivity.class);
+ signIntent.setData(dataUri);
+ startActivity(signIntent);
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/AsyncTaskResultWrapper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/AsyncTaskResultWrapper.java
new file mode 100644
index 000000000..5f2aec4fe
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/AsyncTaskResultWrapper.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.ui.adapter;
+
+/**
+ * The AsyncTaskResultWrapper is used to wrap a result from a AsyncTask (for example: Loader).
+ * You can pass the result and an exception in it if an error occurred.
+ * Concept found at:
+ * https://stackoverflow.com/questions/19593577/how-to-handle-errors-in-custom-asynctaskloader
+ *
+ * @param <T> - Typ of the result which is wrapped
+ */
+public class AsyncTaskResultWrapper<T> {
+
+ private final T mResult;
+ private final Exception mError;
+
+ public AsyncTaskResultWrapper(T result, Exception error) {
+ this.mResult = result;
+ this.mError = error;
+ }
+
+ public T getResult() {
+ return mResult;
+ }
+
+ public Exception getError() {
+ return mError;
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/HighlightQueryCursorAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/HighlightQueryCursorAdapter.java
new file mode 100644
index 000000000..a3ed08a4c
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/HighlightQueryCursorAdapter.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.ui.adapter;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.support.v4.widget.CursorAdapter;
+import android.text.Spannable;
+import android.text.style.ForegroundColorSpan;
+import org.sufficientlysecure.keychain.R;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public abstract class HighlightQueryCursorAdapter extends CursorAdapter {
+
+ private String mCurQuery;
+
+ public HighlightQueryCursorAdapter(Context context, Cursor c, int flags) {
+ super(context, c, flags);
+ mCurQuery = null;
+ }
+
+ public void setSearchQuery(String searchQuery) {
+ mCurQuery = searchQuery;
+ }
+
+ public String getSearchQuery() {
+ return mCurQuery;
+ }
+
+ protected Spannable highlightSearchQuery(String text) {
+ Spannable highlight = Spannable.Factory.getInstance().newSpannable(text);
+
+ if (mCurQuery != null) {
+ Pattern pattern = Pattern.compile("(?i)" + mCurQuery);
+ Matcher matcher = pattern.matcher(text);
+ if (matcher.find()) {
+ highlight.setSpan(
+ new ForegroundColorSpan(mContext.getResources().getColor(R.color.emphasis)),
+ matcher.start(),
+ matcher.end(),
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+ return highlight;
+ } else {
+ return highlight;
+ }
+ }
+}
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
new file mode 100644
index 000000000..f322ea980
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.ui.adapter;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Color;
+import android.os.Build;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.CheckBox;
+import android.widget.LinearLayout;
+import android.widget.LinearLayout.LayoutParams;
+import android.widget.TextView;
+
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> {
+ protected LayoutInflater mInflater;
+ protected Activity mActivity;
+
+ protected List<ImportKeysListEntry> mData;
+
+ static class ViewHolder {
+ public TextView mainUserId;
+ public TextView mainUserIdRest;
+ public TextView keyId;
+ public TextView fingerprint;
+ public TextView algorithm;
+ public TextView status;
+ }
+
+ public ImportKeysAdapter(Activity activity) {
+ super(activity, -1);
+ mActivity = activity;
+ mInflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ }
+
+ @SuppressLint("NewApi")
+ public void setData(List<ImportKeysListEntry> data) {
+ clear();
+ if (data != null) {
+ this.mData = data;
+
+ // add data to extended ArrayAdapter
+ if (Build.VERSION.SDK_INT >= 11) {
+ addAll(data);
+ } else {
+ for (ImportKeysListEntry entry : data) {
+ add(entry);
+ }
+ }
+ }
+ }
+
+ public List<ImportKeysListEntry> getData() {
+ return mData;
+ }
+
+ public ArrayList<ImportKeysListEntry> getSelectedData() {
+ ArrayList<ImportKeysListEntry> selectedData = new ArrayList<ImportKeysListEntry>();
+ for (ImportKeysListEntry entry : mData) {
+ if (entry.isSelected()) {
+ selectedData.add(entry);
+ }
+ }
+ return selectedData;
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return true;
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ ImportKeysListEntry entry = mData.get(position);
+ ViewHolder holder;
+ if (convertView == null) {
+ holder = new ViewHolder();
+ convertView = mInflater.inflate(R.layout.import_keys_list_entry, null);
+ holder.mainUserId = (TextView) convertView.findViewById(R.id.mainUserId);
+ holder.mainUserIdRest = (TextView) convertView.findViewById(R.id.mainUserIdRest);
+ holder.keyId = (TextView) convertView.findViewById(R.id.keyId);
+ holder.fingerprint = (TextView) convertView.findViewById(R.id.fingerprint);
+ holder.algorithm = (TextView) convertView.findViewById(R.id.algorithm);
+ holder.status = (TextView) convertView.findViewById(R.id.status);
+ convertView.setTag(holder);
+ } else {
+ holder = (ViewHolder) convertView.getTag();
+ }
+ // main user id
+ String userId = entry.userIds.get(0);
+ String[] userIdSplit = PgpKeyHelper.splitUserId(userId);
+
+ // name
+ if (userIdSplit[0] != null) {
+ // show red user id if it is a secret key
+ if (entry.secretKey) {
+ userIdSplit[0] = mActivity.getString(R.string.secret_key) + " " + userIdSplit[0];
+ holder.mainUserId.setTextColor(Color.RED);
+ }
+ holder.mainUserId.setText(userIdSplit[0]);
+ } else {
+ holder.mainUserId.setText(R.string.user_id_no_name);
+ }
+
+ // email
+ if (userIdSplit[1] != null) {
+ holder.mainUserIdRest.setText(userIdSplit[1]);
+ holder.mainUserIdRest.setVisibility(View.VISIBLE);
+ } else {
+ holder.mainUserIdRest.setVisibility(View.GONE);
+ }
+
+ holder.keyId.setText(entry.keyIdHex);
+
+ if (entry.fingerPrintHex != null) {
+ holder.fingerprint.setText(PgpKeyHelper.colorizeFingerprint(entry.fingerPrintHex));
+ holder.fingerprint.setVisibility(View.VISIBLE);
+ } else {
+ holder.fingerprint.setVisibility(View.GONE);
+ }
+
+ holder.algorithm.setText("" + entry.bitStrength + "/" + entry.algorithm);
+
+ if (entry.revoked) {
+ holder.status.setText(R.string.revoked);
+ } else {
+ holder.status.setVisibility(View.GONE);
+ }
+
+ LinearLayout ll = (LinearLayout) convertView.findViewById(R.id.list);
+ ll.removeAllViews();
+ if (entry.userIds.size() == 1) {
+ ll.setVisibility(View.GONE);
+ } else {
+ boolean first = true;
+ boolean second = true;
+ for (String uid : entry.userIds) {
+ if (first) {
+ first = false;
+ continue;
+ }
+ if (!second) {
+ View sep = new View(mActivity);
+ sep.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, 1));
+ sep.setBackgroundResource(android.R.drawable.divider_horizontal_dark);
+ ll.addView(sep);
+ }
+ TextView uidView = (TextView) mInflater.inflate(
+ R.layout.import_keys_list_entry_user_id, null);
+ uidView.setText(uid);
+ ll.addView(uidView);
+ second = false;
+ }
+ }
+
+ CheckBox cBox = (CheckBox) convertView.findViewById(R.id.selected);
+ cBox.setChecked(entry.isSelected());
+
+ return convertView;
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListEntry.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListEntry.java
new file mode 100644
index 000000000..5631d40ea
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListEntry.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.ui.adapter;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.SparseArray;
+
+import org.spongycastle.openpgp.PGPKeyRing;
+import org.spongycastle.openpgp.PGPPublicKey;
+import org.spongycastle.openpgp.PGPSecretKeyRing;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
+import org.sufficientlysecure.keychain.util.IterableIterator;
+import org.sufficientlysecure.keychain.util.Log;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Date;
+
+public class ImportKeysListEntry implements Serializable, Parcelable {
+ private static final long serialVersionUID = -7797972103284992662L;
+
+ public ArrayList<String> userIds;
+ public long keyId;
+ public String keyIdHex;
+ public boolean revoked;
+ public Date date; // TODO: not displayed
+ public String fingerPrintHex;
+ public int bitStrength;
+ public String algorithm;
+ public boolean secretKey;
+
+ private boolean mSelected;
+
+ private byte[] mBytes = new byte[]{};
+
+ public ImportKeysListEntry(ImportKeysListEntry b) {
+ this.userIds = b.userIds;
+ this.keyId = b.keyId;
+ this.revoked = b.revoked;
+ this.date = b.date;
+ this.fingerPrintHex = b.fingerPrintHex;
+ this.keyIdHex = b.keyIdHex;
+ this.bitStrength = b.bitStrength;
+ this.algorithm = b.algorithm;
+ this.secretKey = b.secretKey;
+ this.mSelected = b.mSelected;
+ this.mBytes = b.mBytes;
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeStringList(userIds);
+ dest.writeLong(keyId);
+ dest.writeByte((byte) (revoked ? 1 : 0));
+ dest.writeSerializable(date);
+ dest.writeString(fingerPrintHex);
+ dest.writeString(keyIdHex);
+ dest.writeInt(bitStrength);
+ dest.writeString(algorithm);
+ dest.writeByte((byte) (secretKey ? 1 : 0));
+ dest.writeByte((byte) (mSelected ? 1 : 0));
+ dest.writeInt(mBytes.length);
+ dest.writeByteArray(mBytes);
+ }
+
+ public static final Creator<ImportKeysListEntry> CREATOR = new Creator<ImportKeysListEntry>() {
+ public ImportKeysListEntry createFromParcel(final Parcel source) {
+ ImportKeysListEntry vr = new ImportKeysListEntry();
+ vr.userIds = new ArrayList<String>();
+ source.readStringList(vr.userIds);
+ vr.keyId = source.readLong();
+ vr.revoked = source.readByte() == 1;
+ vr.date = (Date) source.readSerializable();
+ vr.fingerPrintHex = source.readString();
+ vr.keyIdHex = source.readString();
+ vr.bitStrength = source.readInt();
+ vr.algorithm = source.readString();
+ vr.secretKey = source.readByte() == 1;
+ vr.mSelected = source.readByte() == 1;
+ vr.mBytes = new byte[source.readInt()];
+ source.readByteArray(vr.mBytes);
+
+ return vr;
+ }
+
+ public ImportKeysListEntry[] newArray(final int size) {
+ return new ImportKeysListEntry[size];
+ }
+ };
+
+ public String getKeyIdHex() {
+ return keyIdHex;
+ }
+
+ public byte[] getBytes() {
+ return mBytes;
+ }
+
+ public void setBytes(byte[] bytes) {
+ this.mBytes = bytes;
+ }
+
+ public boolean isSelected() {
+ return mSelected;
+ }
+
+ public void setSelected(boolean selected) {
+ this.mSelected = selected;
+ }
+
+ public long getKeyId() {
+ return keyId;
+ }
+
+ public void setKeyId(long keyId) {
+ this.keyId = keyId;
+ }
+
+ public void setKeyIdHex(String keyIdHex) {
+ this.keyIdHex = keyIdHex;
+ }
+
+ public boolean isRevoked() {
+ return revoked;
+ }
+
+ public void setRevoked(boolean revoked) {
+ this.revoked = revoked;
+ }
+
+ public Date getDate() {
+ return date;
+ }
+
+ public void setDate(Date date) {
+ this.date = date;
+ }
+
+ public String getFingerPrintHex() {
+ return fingerPrintHex;
+ }
+
+ public void setFingerPrintHex(String fingerPrintHex) {
+ this.fingerPrintHex = fingerPrintHex;
+ }
+
+ public int getBitStrength() {
+ return bitStrength;
+ }
+
+ public void setBitStrength(int bitStrength) {
+ this.bitStrength = bitStrength;
+ }
+
+ public String getAlgorithm() {
+ return algorithm;
+ }
+
+ public void setAlgorithm(String algorithm) {
+ this.algorithm = algorithm;
+ }
+
+ public boolean isSecretKey() {
+ return secretKey;
+ }
+
+ public void setSecretKey(boolean secretKey) {
+ this.secretKey = secretKey;
+ }
+
+ public ArrayList<String> getUserIds() {
+ return userIds;
+ }
+
+ public void setUserIds(ArrayList<String> userIds) {
+ this.userIds = userIds;
+ }
+
+ /**
+ * Constructor for later querying from keyserver
+ */
+ public ImportKeysListEntry() {
+ // keys from keyserver are always public keys
+ secretKey = false;
+ // do not select by default
+ mSelected = false;
+ userIds = new ArrayList<String>();
+ }
+
+ /**
+ * Constructor based on key object, used for import from NFC, QR Codes, files
+ */
+ @SuppressWarnings("unchecked")
+ public ImportKeysListEntry(PGPKeyRing pgpKeyRing) {
+ // save actual key object into entry, used to import it later
+ try {
+ this.mBytes = pgpKeyRing.getEncoded();
+ } catch (IOException e) {
+ Log.e(Constants.TAG, "IOException on pgpKeyRing.getEncoded()", e);
+ }
+
+ // selected is default
+ this.mSelected = true;
+
+ if (pgpKeyRing instanceof PGPSecretKeyRing) {
+ secretKey = true;
+ } else {
+ secretKey = false;
+ }
+
+ userIds = new ArrayList<String>();
+ for (String userId : new IterableIterator<String>(pgpKeyRing.getPublicKey().getUserIDs())) {
+ userIds.add(userId);
+ }
+
+ this.keyId = pgpKeyRing.getPublicKey().getKeyID();
+ this.keyIdHex = PgpKeyHelper.convertKeyIdToHex(keyId);
+
+ this.revoked = pgpKeyRing.getPublicKey().isRevoked();
+ this.fingerPrintHex = PgpKeyHelper.convertFingerprintToHex(pgpKeyRing.getPublicKey()
+ .getFingerprint());
+ this.bitStrength = pgpKeyRing.getPublicKey().getBitStrength();
+ final int algorithm = pgpKeyRing.getPublicKey().getAlgorithm();
+ this.algorithm = getAlgorithmFromId(algorithm);
+ }
+
+ /**
+ * Based on <a href="http://tools.ietf.org/html/rfc2440#section-9.1">OpenPGP Message Format</a>
+ */
+ private static final SparseArray<String> ALGORITHM_IDS = new SparseArray<String>() {{
+ put(-1, "unknown"); // TODO: with resources
+ put(0, "unencrypted");
+ put(PGPPublicKey.RSA_GENERAL, "RSA");
+ put(PGPPublicKey.RSA_ENCRYPT, "RSA");
+ put(PGPPublicKey.RSA_SIGN, "RSA");
+ put(PGPPublicKey.ELGAMAL_ENCRYPT, "ElGamal");
+ put(PGPPublicKey.ELGAMAL_GENERAL, "ElGamal");
+ put(PGPPublicKey.DSA, "DSA");
+ put(PGPPublicKey.EC, "ECC");
+ put(PGPPublicKey.ECDSA, "ECC");
+ put(PGPPublicKey.ECDH, "ECC");
+ }};
+
+ /**
+ * Based on <a href="http://tools.ietf.org/html/rfc2440#section-9.1">OpenPGP Message Format</a>
+ */
+ public static String getAlgorithmFromId(int algorithmId) {
+ return (ALGORITHM_IDS.get(algorithmId) != null ?
+ ALGORITHM_IDS.get(algorithmId) :
+ ALGORITHM_IDS.get(-1));
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java
new file mode 100644
index 000000000..c9983213c
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java
@@ -0,0 +1,167 @@
+/*
+ * 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.ui.adapter;
+
+import android.content.Context;
+import android.support.v4.content.AsyncTaskLoader;
+import org.spongycastle.openpgp.PGPKeyRing;
+import org.spongycastle.openpgp.PGPObjectFactory;
+import org.spongycastle.openpgp.PGPUtil;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.util.InputData;
+import org.sufficientlysecure.keychain.util.Log;
+import org.sufficientlysecure.keychain.util.PositionAwareInputStream;
+
+import java.io.BufferedInputStream;
+import java.io.InputStream;
+import java.util.ArrayList;
+
+public class ImportKeysListLoader
+ extends AsyncTaskLoader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> {
+
+ public static class FileHasNoContent extends Exception {
+
+ }
+
+ public static class NonPgpPart extends Exception {
+ private int mCount;
+
+ public NonPgpPart(int count) {
+ this.mCount = count;
+ }
+
+ public int getCount() {
+ return mCount;
+ }
+ }
+
+ Context mContext;
+
+ InputData mInputData;
+
+ ArrayList<ImportKeysListEntry> mData = new ArrayList<ImportKeysListEntry>();
+ AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> mEntryListWrapper;
+
+ public ImportKeysListLoader(Context context, InputData inputData) {
+ super(context);
+ this.mContext = context;
+ this.mInputData = inputData;
+ }
+
+ @Override
+ public AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> loadInBackground() {
+
+ mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mData, null);
+
+ if (mInputData == null) {
+ Log.e(Constants.TAG, "Input data is null!");
+ return mEntryListWrapper;
+ }
+
+ generateListOfKeyrings(mInputData);
+
+ return mEntryListWrapper;
+ }
+
+ @Override
+ protected void onReset() {
+ super.onReset();
+
+ // Ensure the loader is stopped
+ onStopLoading();
+ }
+
+ @Override
+ protected void onStartLoading() {
+ forceLoad();
+ }
+
+ @Override
+ protected void onStopLoading() {
+ cancelLoad();
+ }
+
+ @Override
+ public void deliverResult(AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> data) {
+ super.deliverResult(data);
+ }
+
+ /**
+ * Reads all PGPKeyRing objects from input
+ *
+ * @param inputData
+ * @return
+ */
+ private void generateListOfKeyrings(InputData inputData) {
+
+ boolean isEmpty = true;
+ int nonPgpCounter = 0;
+
+ PositionAwareInputStream progressIn = new PositionAwareInputStream(
+ inputData.getInputStream());
+
+ // need to have access to the bufferedInput, so we can reuse it for the possible
+ // PGPObject chunks after the first one, e.g. files with several consecutive ASCII
+ // armor blocks
+ BufferedInputStream bufferedInput = new BufferedInputStream(progressIn);
+ try {
+
+ // read all available blocks... (asc files can contain many blocks with BEGIN END)
+ while (bufferedInput.available() > 0) {
+ isEmpty = false;
+ InputStream in = PGPUtil.getDecoderStream(bufferedInput);
+ PGPObjectFactory objectFactory = new PGPObjectFactory(in);
+
+ // go through all objects in this block
+ Object obj;
+ while ((obj = objectFactory.nextObject()) != null) {
+ Log.d(Constants.TAG, "Found class: " + obj.getClass());
+
+ if (obj instanceof PGPKeyRing) {
+ PGPKeyRing newKeyring = (PGPKeyRing) obj;
+ addToData(newKeyring);
+ } else {
+ Log.e(Constants.TAG, "Object not recognized as PGPKeyRing!");
+ nonPgpCounter++;
+ }
+ }
+ }
+ } catch (Exception e) {
+ Log.e(Constants.TAG, "Exception on parsing key file!", e);
+ mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mData, e);
+ nonPgpCounter = 0;
+ }
+
+ if (isEmpty) {
+ Log.e(Constants.TAG, "File has no content!", new FileHasNoContent());
+ mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>
+ (mData, new FileHasNoContent());
+ }
+
+ if (nonPgpCounter > 0) {
+ mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>
+ (mData, new NonPgpPart(nonPgpCounter));
+ }
+ }
+
+ private void addToData(PGPKeyRing keyring) {
+ ImportKeysListEntry item = new ImportKeysListEntry(keyring);
+ mData.add(item);
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListServerLoader.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListServerLoader.java
new file mode 100644
index 000000000..259e14319
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListServerLoader.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.ui.adapter;
+
+import android.content.Context;
+import android.support.v4.content.AsyncTaskLoader;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.util.HkpKeyServer;
+import org.sufficientlysecure.keychain.util.KeyServer;
+import org.sufficientlysecure.keychain.util.Log;
+
+import java.util.ArrayList;
+
+public class ImportKeysListServerLoader
+ extends AsyncTaskLoader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> {
+ Context mContext;
+
+ String mServerQuery;
+ String mKeyServer;
+
+ private ArrayList<ImportKeysListEntry> mEntryList = new ArrayList<ImportKeysListEntry>();
+ private AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> mEntryListWrapper;
+
+ public ImportKeysListServerLoader(Context context, String serverQuery, String keyServer) {
+ super(context);
+ mContext = context;
+ mServerQuery = serverQuery;
+ mKeyServer = keyServer;
+ }
+
+ @Override
+ public AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> loadInBackground() {
+
+ mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, null);
+
+ if (mServerQuery == null) {
+ Log.e(Constants.TAG, "mServerQuery is null!");
+ return mEntryListWrapper;
+ }
+
+ if (mServerQuery.startsWith("0x") && mServerQuery.length() == 42) {
+ Log.d(Constants.TAG, "This search is based on a unique fingerprint. Enforce a fingerprint check!");
+ queryServer(mServerQuery, mKeyServer, true);
+ } else {
+ queryServer(mServerQuery, mKeyServer, false);
+ }
+
+ return mEntryListWrapper;
+ }
+
+ @Override
+ protected void onReset() {
+ super.onReset();
+
+ // Ensure the loader is stopped
+ onStopLoading();
+ }
+
+ @Override
+ protected void onStartLoading() {
+ forceLoad();
+ }
+
+ @Override
+ protected void onStopLoading() {
+ cancelLoad();
+ }
+
+ @Override
+ public void deliverResult(AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> data) {
+ super.deliverResult(data);
+ }
+
+ /**
+ * Query keyserver
+ */
+ private void queryServer(String query, String keyServer, boolean enforceFingerprint) {
+ HkpKeyServer server = new HkpKeyServer(keyServer);
+ try {
+ ArrayList<ImportKeysListEntry> searchResult = server.search(query);
+
+ mEntryList.clear();
+ // add result to data
+ if (enforceFingerprint) {
+ String fingerprint = query.substring(2);
+ Log.d(Constants.TAG, "fingerprint: " + fingerprint);
+ // query must return only one result!
+ if (searchResult.size() > 0) {
+ ImportKeysListEntry uniqueEntry = searchResult.get(0);
+ /*
+ * set fingerprint explicitly after query
+ * to enforce a check when the key is imported by KeychainIntentService
+ */
+ uniqueEntry.setFingerPrintHex(fingerprint);
+ uniqueEntry.setSelected(true);
+ mEntryList.add(uniqueEntry);
+ }
+ } else {
+ mEntryList.addAll(searchResult);
+ }
+ mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, null);
+ } catch (KeyServer.InsufficientQuery e) {
+ Log.e(Constants.TAG, "InsufficientQuery", e);
+ mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, e);
+ } catch (KeyServer.QueryException e) {
+ Log.e(Constants.TAG, "QueryException", e);
+ mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, e);
+ } catch (KeyServer.TooManyResponses e) {
+ Log.e(Constants.TAG, "TooManyResponses", e);
+ mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, e);
+ }
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyValueSpinnerAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyValueSpinnerAdapter.java
new file mode 100644
index 000000000..5b5d316b6
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyValueSpinnerAdapter.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.ui.adapter;
+
+import android.content.Context;
+import android.widget.ArrayAdapter;
+
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+public class KeyValueSpinnerAdapter extends ArrayAdapter<String> {
+ private final HashMap<Integer, String> mData;
+ private final int[] mKeys;
+ private final String[] mValues;
+
+ static <K, V extends Comparable<? super V>> SortedSet<Map.Entry<K, V>> entriesSortedByValues(
+ Map<K, V> map) {
+ SortedSet<Map.Entry<K, V>> sortedEntries = new TreeSet<Map.Entry<K, V>>(
+ new Comparator<Map.Entry<K, V>>() {
+ @Override
+ public int compare(Map.Entry<K, V> e1, Map.Entry<K, V> e2) {
+ return e1.getValue().compareTo(e2.getValue());
+ }
+ });
+ sortedEntries.addAll(map.entrySet());
+ return sortedEntries;
+ }
+
+ public KeyValueSpinnerAdapter(Context context, HashMap<Integer, String> objects) {
+ // To make the drop down a simple text box
+ super(context, android.R.layout.simple_spinner_item);
+ mData = objects;
+
+ // To make the drop down view a radio button list
+ setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+
+ SortedSet<Map.Entry<Integer, String>> sorted = entriesSortedByValues(objects);
+
+ // Assign hash keys with a position so that we can present and retrieve them
+ int i = 0;
+ mKeys = new int[mData.size()];
+ mValues = new String[mData.size()];
+ for (Map.Entry<Integer, String> entry : sorted) {
+ mKeys[i] = entry.getKey();
+ mValues[i] = entry.getValue();
+ i++;
+ }
+ }
+
+ public int getCount() {
+ return mData.size();
+ }
+
+ /**
+ * Returns the value
+ */
+ @Override
+ public String getItem(int position) {
+ // return the value based on the position. This is displayed in the list.
+ return mValues[position];
+ }
+
+ /**
+ * Returns item key
+ */
+ public long getItemId(int position) {
+ // Return an id to represent the item.
+
+ return mKeys[position];
+ }
+
+ /**
+ * Find position from key
+ */
+ public int getPosition(long itemId) {
+ for (int i = 0; i < mKeys.length; i++) {
+ if ((int) itemId == mKeys[i]) {
+ return i;
+ }
+ }
+ return -1;
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/PagerTabStripAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/PagerTabStripAdapter.java
new file mode 100644
index 000000000..fd864eb09
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/PagerTabStripAdapter.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.ui.adapter;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentPagerAdapter;
+import android.support.v7.app.ActionBarActivity;
+
+import java.util.ArrayList;
+
+public class PagerTabStripAdapter extends FragmentPagerAdapter {
+ private final Context mContext;
+ private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>();
+
+ static final class TabInfo {
+ public final Class<?> clss;
+ public final Bundle args;
+ public final String title;
+
+ TabInfo(Class<?> clss, Bundle args, String title) {
+ this.clss = clss;
+ this.args = args;
+ this.title = title;
+ }
+ }
+
+ public PagerTabStripAdapter(ActionBarActivity activity) {
+ super(activity.getSupportFragmentManager());
+ mContext = activity;
+ }
+
+ public void addTab(Class<?> clss, Bundle args, String title) {
+ TabInfo info = new TabInfo(clss, args, title);
+ mTabs.add(info);
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public int getCount() {
+ return mTabs.size();
+ }
+
+ @Override
+ public Fragment getItem(int position) {
+ TabInfo info = mTabs.get(position);
+ return Fragment.instantiate(mContext, info.clss.getName(), info.args);
+ }
+
+ @Override
+ public CharSequence getPageTitle(int position) {
+ return mTabs.get(position).title;
+ }
+}
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
new file mode 100644
index 000000000..fbbb9caa4
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java
@@ -0,0 +1,165 @@
+/*
+ * 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.adapter;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CheckBox;
+import android.widget.ListView;
+import android.widget.TextView;
+import org.sufficientlysecure.keychain.Id;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
+import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
+import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
+
+
+public class SelectKeyCursorAdapter extends HighlightQueryCursorAdapter {
+
+ protected int mKeyType;
+
+ private LayoutInflater mInflater;
+ private ListView mListView;
+
+ private int mIndexUserId;
+ private int mIndexMasterKeyId;
+ private int mIndexProjectionValid;
+ private int mIndexProjectionAvailable;
+
+ public static final String PROJECTION_ROW_AVAILABLE = "available";
+ public static final String PROJECTION_ROW_VALID = "valid";
+
+ public SelectKeyCursorAdapter(Context context, Cursor c, int flags, ListView listView,
+ int keyType) {
+ super(context, c, flags);
+
+ mInflater = LayoutInflater.from(context);
+ mListView = listView;
+ mKeyType = keyType;
+ initIndex(c);
+ }
+
+ @Override
+ public Cursor swapCursor(Cursor newCursor) {
+ initIndex(newCursor);
+
+ return super.swapCursor(newCursor);
+ }
+
+ /**
+ * Get column indexes for performance reasons just once in constructor and swapCursor. For a
+ * performance comparison see http://stackoverflow.com/a/17999582
+ *
+ * @param cursor
+ */
+ private void initIndex(Cursor cursor) {
+ if (cursor != null) {
+ mIndexUserId = cursor.getColumnIndexOrThrow(UserIds.USER_ID);
+ mIndexMasterKeyId = cursor.getColumnIndexOrThrow(KeyRings.MASTER_KEY_ID);
+ mIndexProjectionValid = cursor.getColumnIndexOrThrow(PROJECTION_ROW_VALID);
+ mIndexProjectionAvailable = cursor.getColumnIndexOrThrow(PROJECTION_ROW_AVAILABLE);
+ }
+ }
+
+ public String getUserId(int position) {
+ mCursor.moveToPosition(position);
+ return mCursor.getString(mIndexUserId);
+ }
+
+ public long getMasterKeyId(int position) {
+ mCursor.moveToPosition(position);
+ return mCursor.getLong(mIndexMasterKeyId);
+ }
+
+ @Override
+ public void bindView(View view, Context context, Cursor cursor) {
+ boolean valid = cursor.getInt(mIndexProjectionValid) > 0;
+
+ TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId);
+ TextView mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest);
+ TextView keyId = (TextView) view.findViewById(R.id.keyId);
+ TextView status = (TextView) view.findViewById(R.id.status);
+
+ String userId = cursor.getString(mIndexUserId);
+ String[] userIdSplit = PgpKeyHelper.splitUserId(userId);
+
+ if (userIdSplit[0] != null) {
+ mainUserId.setText(highlightSearchQuery(userIdSplit[0]));
+ } else {
+ mainUserId.setText(R.string.user_id_no_name);
+ }
+ if (userIdSplit[1] != null) {
+ mainUserIdRest.setText(highlightSearchQuery(userIdSplit[1]));
+ } else {
+ mainUserIdRest.setText("");
+ }
+
+ // TODO: needed to key id to no?
+ keyId.setText(R.string.no_key);
+ long masterKeyId = cursor.getLong(mIndexMasterKeyId);
+ keyId.setText(PgpKeyHelper.convertKeyIdToHexShort(masterKeyId));
+
+ // TODO: needed to set unknown_status?
+ status.setText(R.string.unknown_status);
+ if (valid) {
+ if (mKeyType == Id.type.public_key) {
+ status.setText(R.string.can_encrypt);
+ } else {
+ status.setText(R.string.can_sign);
+ }
+ } else {
+ if (cursor.getInt(mIndexProjectionAvailable) > 0) {
+ // has some CAN_ENCRYPT keys, but col(ROW_VALID) = 0, so must be revoked or
+ // expired
+ status.setText(R.string.expired);
+ } else {
+ status.setText(R.string.no_key);
+ }
+ }
+
+ CheckBox selected = (CheckBox) view.findViewById(R.id.selected);
+ if (mKeyType == Id.type.public_key) {
+ selected.setVisibility(View.VISIBLE);
+
+ if (!valid) {
+ mListView.setItemChecked(cursor.getPosition(), false);
+ }
+
+ selected.setChecked(mListView.isItemChecked(cursor.getPosition()));
+ selected.setEnabled(valid);
+ } else {
+ selected.setVisibility(View.GONE);
+ }
+
+ status.setText(status.getText() + " ");
+
+ view.setEnabled(valid);
+ mainUserId.setEnabled(valid);
+ mainUserIdRest.setEnabled(valid);
+ keyId.setEnabled(valid);
+ status.setEnabled(valid);
+ }
+
+ @Override
+ public View newView(Context context, Cursor cursor, ViewGroup parent) {
+ return mInflater.inflate(R.layout.select_key_item, null);
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/TabsAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/TabsAdapter.java
new file mode 100644
index 000000000..9ddfa90be
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/TabsAdapter.java
@@ -0,0 +1,101 @@
+/*
+ * 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.adapter;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentStatePagerAdapter;
+import android.support.v4.app.FragmentTransaction;
+import android.support.v4.view.ViewPager;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.ActionBarActivity;
+
+import java.util.ArrayList;
+
+public class TabsAdapter extends FragmentStatePagerAdapter implements ActionBar.TabListener,
+ ViewPager.OnPageChangeListener {
+ private final Context mContext;
+ private final ActionBar mActionBar;
+ private final ViewPager mViewPager;
+ private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>();
+
+ static final class TabInfo {
+ public final Class<?> clss;
+ public final Bundle args;
+
+ TabInfo(Class<?> clss, Bundle args) {
+ this.clss = clss;
+ this.args = args;
+ }
+ }
+
+ public TabsAdapter(ActionBarActivity activity, ViewPager pager) {
+ super(activity.getSupportFragmentManager());
+ mContext = activity;
+ mActionBar = activity.getSupportActionBar();
+ mViewPager = pager;
+ mViewPager.setAdapter(this);
+ mViewPager.setOnPageChangeListener(this);
+ }
+
+ public void addTab(ActionBar.Tab tab, Class<?> clss, Bundle args, boolean selected) {
+ TabInfo info = new TabInfo(clss, args);
+ tab.setTag(info);
+ tab.setTabListener(this);
+ mTabs.add(info);
+ mActionBar.addTab(tab, selected);
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public int getCount() {
+ return mTabs.size();
+ }
+
+ @Override
+ public Fragment getItem(int position) {
+ TabInfo info = mTabs.get(position);
+ return Fragment.instantiate(mContext, info.clss.getName(), info.args);
+ }
+
+ public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+ }
+
+ public void onPageSelected(int position) {
+ mActionBar.setSelectedNavigationItem(position);
+ }
+
+ public void onPageScrollStateChanged(int state) {
+ }
+
+ public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) {
+ Object tag = tab.getTag();
+ for (int i = 0; i < mTabs.size(); i++) {
+ if (mTabs.get(i) == tag) {
+ mViewPager.setCurrentItem(i);
+ }
+ }
+ }
+
+ public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) {
+ }
+
+ public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) {
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyKeysAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyKeysAdapter.java
new file mode 100644
index 000000000..64b735bfa
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyKeysAdapter.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.ui.adapter;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.database.Cursor;
+import android.support.v4.widget.CursorAdapter;
+import android.text.format.DateFormat;
+import android.view.LayoutInflater;
+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.helper.OtherHelper;
+import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
+import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
+
+import java.util.Date;
+
+public class ViewKeyKeysAdapter extends CursorAdapter {
+ private LayoutInflater mInflater;
+
+ private int mIndexKeyId;
+ private int mIndexAlgorithm;
+ private int mIndexKeySize;
+ private int mIndexRank;
+ private int mIndexCanCertify;
+ private int mIndexCanEncrypt;
+ private int mIndexCanSign;
+ private int mIndexRevokedKey;
+ private int mIndexExpiry;
+
+ private ColorStateList mDefaultTextColor;
+
+ public ViewKeyKeysAdapter(Context context, Cursor c, int flags) {
+ super(context, c, flags);
+
+ mInflater = LayoutInflater.from(context);
+
+ initIndex(c);
+ }
+
+ @Override
+ public Cursor swapCursor(Cursor newCursor) {
+ initIndex(newCursor);
+
+ return super.swapCursor(newCursor);
+ }
+
+ /**
+ * Get column indexes for performance reasons just once in constructor and swapCursor. For a
+ * performance comparison see http://stackoverflow.com/a/17999582
+ *
+ * @param cursor
+ */
+ private void initIndex(Cursor cursor) {
+ if (cursor != null) {
+ mIndexKeyId = cursor.getColumnIndexOrThrow(Keys.KEY_ID);
+ mIndexAlgorithm = cursor.getColumnIndexOrThrow(Keys.ALGORITHM);
+ mIndexKeySize = cursor.getColumnIndexOrThrow(Keys.KEY_SIZE);
+ mIndexRank = cursor.getColumnIndexOrThrow(Keys.RANK);
+ mIndexCanCertify = cursor.getColumnIndexOrThrow(Keys.CAN_CERTIFY);
+ mIndexCanEncrypt = cursor.getColumnIndexOrThrow(Keys.CAN_ENCRYPT);
+ mIndexCanSign = cursor.getColumnIndexOrThrow(Keys.CAN_SIGN);
+ mIndexRevokedKey = cursor.getColumnIndexOrThrow(Keys.IS_REVOKED);
+ mIndexExpiry = cursor.getColumnIndexOrThrow(Keys.EXPIRY);
+ }
+ }
+
+ @Override
+ public void bindView(View view, Context context, Cursor cursor) {
+ TextView keyId = (TextView) view.findViewById(R.id.keyId);
+ TextView keyDetails = (TextView) view.findViewById(R.id.keyDetails);
+ TextView keyExpiry = (TextView) view.findViewById(R.id.keyExpiry);
+ ImageView masterKeyIcon = (ImageView) view.findViewById(R.id.ic_masterKey);
+ ImageView certifyIcon = (ImageView) view.findViewById(R.id.ic_certifyKey);
+ ImageView encryptIcon = (ImageView) view.findViewById(R.id.ic_encryptKey);
+ ImageView signIcon = (ImageView) view.findViewById(R.id.ic_signKey);
+ ImageView revokedKeyIcon = (ImageView) view.findViewById(R.id.ic_revokedKey);
+
+ String keyIdStr = PgpKeyHelper.convertKeyIdToHexShort(cursor.getLong(mIndexKeyId));
+ String algorithmStr = PgpKeyHelper.getAlgorithmInfo(cursor.getInt(mIndexAlgorithm),
+ cursor.getInt(mIndexKeySize));
+
+ keyId.setText(keyIdStr);
+ keyDetails.setText("(" + algorithmStr + ")");
+
+ if (cursor.getInt(mIndexRank) == 0) {
+ masterKeyIcon.setVisibility(View.INVISIBLE);
+ } else {
+ masterKeyIcon.setVisibility(View.VISIBLE);
+ }
+
+ if (cursor.getInt(mIndexCanCertify) != 1) {
+ certifyIcon.setVisibility(View.GONE);
+ } else {
+ certifyIcon.setVisibility(View.VISIBLE);
+ }
+
+ if (cursor.getInt(mIndexCanEncrypt) != 1) {
+ encryptIcon.setVisibility(View.GONE);
+ } else {
+ encryptIcon.setVisibility(View.VISIBLE);
+ }
+
+ if (cursor.getInt(mIndexCanSign) != 1) {
+ signIcon.setVisibility(View.GONE);
+ } else {
+ signIcon.setVisibility(View.VISIBLE);
+ }
+
+ boolean valid = true;
+ if (cursor.getInt(mIndexRevokedKey) > 0) {
+ revokedKeyIcon.setVisibility(View.VISIBLE);
+
+ valid = false;
+ } else {
+ keyId.setTextColor(mDefaultTextColor);
+ keyDetails.setTextColor(mDefaultTextColor);
+ keyExpiry.setTextColor(mDefaultTextColor);
+
+ revokedKeyIcon.setVisibility(View.GONE);
+ }
+
+ if (!cursor.isNull(mIndexExpiry)) {
+ Date expiryDate = new Date(cursor.getLong(mIndexExpiry) * 1000);
+
+ valid = valid && expiryDate.after(new Date());
+ keyExpiry.setText("(" +
+ context.getString(R.string.label_expiry) + ": " +
+ DateFormat.getDateFormat(context).format(expiryDate) + ")");
+
+ keyExpiry.setVisibility(View.VISIBLE);
+ } else {
+ keyExpiry.setVisibility(View.GONE);
+ }
+ // if key is expired or revoked, strike through text
+ if (!valid) {
+ keyId.setText(OtherHelper.strikeOutText(keyId.getText()));
+ keyDetails.setText(OtherHelper.strikeOutText(keyDetails.getText()));
+ keyExpiry.setText(OtherHelper.strikeOutText(keyExpiry.getText()));
+ }
+ keyId.setEnabled(valid);
+ keyDetails.setEnabled(valid);
+ keyExpiry.setEnabled(valid);
+ }
+
+ @Override
+ public View newView(Context context, Cursor cursor, ViewGroup parent) {
+ View view = mInflater.inflate(R.layout.view_key_keys_item, null);
+ if (mDefaultTextColor == null) {
+ TextView keyId = (TextView) view.findViewById(R.id.keyId);
+ mDefaultTextColor = keyId.getTextColors();
+ }
+ return view;
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyUserIdsAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyUserIdsAdapter.java
new file mode 100644
index 000000000..09137f745
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyUserIdsAdapter.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.ui.adapter;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.support.v4.widget.CursorAdapter;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.ImageView;
+import android.widget.TextView;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
+import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
+import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
+
+import java.util.ArrayList;
+
+public class ViewKeyUserIdsAdapter extends CursorAdapter implements AdapterView.OnItemClickListener {
+ private LayoutInflater mInflater;
+
+ private int mIndexUserId, mIndexRank;
+ private int mVerifiedId, mIsRevoked, mIsPrimary;
+
+ private final ArrayList<Boolean> mCheckStates;
+
+ public static final String[] USER_IDS_PROJECTION = new String[] {
+ UserIds._ID, UserIds.USER_ID, UserIds.RANK,
+ UserIds.VERIFIED, UserIds.IS_PRIMARY, UserIds.IS_REVOKED
+ };
+
+ public ViewKeyUserIdsAdapter(Context context, Cursor c, int flags, boolean showCheckBoxes) {
+ super(context, c, flags);
+
+ mInflater = LayoutInflater.from(context);
+
+ mCheckStates = showCheckBoxes ? new ArrayList<Boolean>() : null;
+
+ initIndex(c);
+ }
+ public ViewKeyUserIdsAdapter(Context context, Cursor c, int flags) {
+ this(context, c, flags, false);
+ }
+
+ @Override
+ public Cursor swapCursor(Cursor newCursor) {
+ initIndex(newCursor);
+ if (mCheckStates != null) {
+ 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++) {
+ newCursor.moveToPosition(i);
+ int verified = newCursor.getInt(mVerifiedId);
+ mCheckStates.add(verified != Certs.VERIFIED_SECRET);
+ }
+ }
+ }
+
+ return super.swapCursor(newCursor);
+ }
+
+ /**
+ * Get column indexes for performance reasons just once in constructor and swapCursor. For a
+ * performance comparison see http://stackoverflow.com/a/17999582
+ *
+ * @param cursor
+ */
+ private void initIndex(Cursor cursor) {
+ if (cursor != null) {
+ mIndexUserId = cursor.getColumnIndexOrThrow(UserIds.USER_ID);
+ mIndexRank = cursor.getColumnIndexOrThrow(UserIds.RANK);
+ mVerifiedId = cursor.getColumnIndexOrThrow(UserIds.VERIFIED);
+ mIsRevoked = cursor.getColumnIndexOrThrow(UserIds.IS_REVOKED);
+ mIsPrimary = cursor.getColumnIndexOrThrow(UserIds.IS_PRIMARY);
+ }
+ }
+
+ @Override
+ public void bindView(View view, Context context, Cursor cursor) {
+
+ TextView vRank = (TextView) view.findViewById(R.id.rank);
+ TextView vUserId = (TextView) view.findViewById(R.id.userId);
+ TextView vAddress = (TextView) view.findViewById(R.id.address);
+ ImageView vVerified = (ImageView) view.findViewById(R.id.certified);
+
+ if(cursor.getInt(mIsPrimary) > 0) {
+ vRank.setText("+");
+ } else {
+ vRank.setText(Integer.toString(cursor.getInt(mIndexRank)));
+ }
+
+ String[] userId = PgpKeyHelper.splitUserId(cursor.getString(mIndexUserId));
+ if (userId[0] != null) {
+ vUserId.setText(userId[0]);
+ } else {
+ vUserId.setText(R.string.user_id_no_name);
+ }
+ vAddress.setText(userId[1]);
+
+ if(cursor.getInt(mIsRevoked) > 0) {
+ vRank.setText(" ");
+ vVerified.setImageResource(android.R.drawable.presence_away);
+ } else {
+ int verified = cursor.getInt(mVerifiedId);
+ // TODO introduce own resources for this :)
+ if(verified == Certs.VERIFIED_SECRET)
+ vVerified.setImageResource(android.R.drawable.presence_online);
+ else if(verified == Certs.VERIFIED_SELF)
+ vVerified.setImageResource(android.R.drawable.presence_invisible);
+ else
+ vVerified.setImageResource(android.R.drawable.presence_busy);
+ }
+
+ // don't care further if checkboxes aren't shown
+ if (mCheckStates == null) {
+ return;
+ }
+
+ final CheckBox vCheckBox = (CheckBox) view.findViewById(R.id.checkBox);
+ final int position = cursor.getPosition();
+ vCheckBox.setOnCheckedChangeListener(null);
+ vCheckBox.setChecked(mCheckStates.get(position));
+ vCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
+ mCheckStates.set(position, b);
+ }
+ });
+ vCheckBox.setClickable(false);
+
+ }
+
+ public void onItemClick(AdapterView<?> adapter, View view, int position, long id) {
+ CheckBox box = ((CheckBox) view.findViewById(R.id.checkBox));
+ if(box != null) {
+ box.toggle();
+ }
+ }
+
+ public ArrayList<String> getSelectedUserIds() {
+ ArrayList<String> result = new ArrayList<String>();
+ for (int i = 0; i < mCheckStates.size(); i++) {
+ if (mCheckStates.get(i)) {
+ mCursor.moveToPosition(i);
+ result.add(mCursor.getString(mIndexUserId));
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public View newView(Context context, Cursor cursor, ViewGroup parent) {
+ View view = mInflater.inflate(R.layout.view_key_userids_item, null);
+ // only need to do this once ever, since mShowCheckBoxes is final
+ view.findViewById(R.id.checkBox).setVisibility(mCheckStates != null ? View.VISIBLE : View.GONE);
+ return view;
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/BadImportKeyDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/BadImportKeyDialogFragment.java
new file mode 100644
index 000000000..20b70658c
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/BadImportKeyDialogFragment.java
@@ -0,0 +1,67 @@
+/*
+ * 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.ui.dialog;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.support.v4.app.DialogFragment;
+import android.support.v4.app.FragmentActivity;
+import org.sufficientlysecure.keychain.R;
+
+public class BadImportKeyDialogFragment extends DialogFragment {
+ private static final String ARG_BAD_IMPORT = "bad_import";
+
+ /**
+ * Creates a new instance of this Bad Import Key DialogFragment
+ *
+ * @param bad
+ * @return
+ */
+ public static BadImportKeyDialogFragment newInstance(int bad) {
+ BadImportKeyDialogFragment frag = new BadImportKeyDialogFragment();
+ Bundle args = new Bundle();
+
+ args.putInt(ARG_BAD_IMPORT, bad);
+ frag.setArguments(args);
+
+ return frag;
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final FragmentActivity activity = getActivity();
+ final int badImport = getArguments().getInt(ARG_BAD_IMPORT);
+
+ AlertDialog.Builder alert = new AlertDialog.Builder(activity);
+ alert.setIcon(R.drawable.ic_dialog_alert_holo_light);
+ alert.setTitle(R.string.warning);
+ alert.setMessage(activity.getResources()
+ .getQuantityString(R.plurals.bad_keys_encountered, badImport, badImport));
+ alert.setPositiveButton(android.R.string.ok,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ dialog.cancel();
+ }
+ });
+ alert.setCancelable(true);
+
+ return alert.create();
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/CreateKeyDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/CreateKeyDialogFragment.java
new file mode 100644
index 000000000..ad558a81e
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/CreateKeyDialogFragment.java
@@ -0,0 +1,159 @@
+/*
+ * 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.ui.dialog;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.support.v4.app.DialogFragment;
+import android.support.v4.app.FragmentActivity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Spinner;
+import org.sufficientlysecure.keychain.Id;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.util.Choice;
+
+import java.util.ArrayList;
+
+public class CreateKeyDialogFragment extends DialogFragment {
+
+ public interface OnAlgorithmSelectedListener {
+ public void onAlgorithmSelected(Choice algorithmChoice, int keySize);
+ }
+
+ private static final String ARG_EDITOR_CHILD_COUNT = "child_count";
+
+ private int mNewKeySize;
+ private Choice mNewKeyAlgorithmChoice;
+ private OnAlgorithmSelectedListener mAlgorithmSelectedListener;
+
+ public void setOnAlgorithmSelectedListener(OnAlgorithmSelectedListener listener) {
+ mAlgorithmSelectedListener = listener;
+ }
+
+ public static CreateKeyDialogFragment newInstance(int mEditorChildCount) {
+ CreateKeyDialogFragment frag = new CreateKeyDialogFragment();
+ Bundle args = new Bundle();
+
+ args.putInt(ARG_EDITOR_CHILD_COUNT, mEditorChildCount);
+
+ frag.setArguments(args);
+
+ return frag;
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final FragmentActivity context = getActivity();
+ final LayoutInflater mInflater;
+
+ final int childCount = getArguments().getInt(ARG_EDITOR_CHILD_COUNT);
+ mInflater = context.getLayoutInflater();
+
+ AlertDialog.Builder dialog = new AlertDialog.Builder(context);
+
+ View view = mInflater.inflate(R.layout.create_key_dialog, null);
+ dialog.setView(view);
+ dialog.setTitle(R.string.title_create_key);
+
+ boolean wouldBeMasterKey = (childCount == 0);
+
+ final Spinner algorithm = (Spinner) view.findViewById(R.id.create_key_algorithm);
+ ArrayList<Choice> choices = new ArrayList<Choice>();
+ choices.add(new Choice(Id.choice.algorithm.dsa, getResources().getString(
+ R.string.dsa)));
+ if (!wouldBeMasterKey) {
+ choices.add(new Choice(Id.choice.algorithm.elgamal, getResources().getString(
+ R.string.elgamal)));
+ }
+
+ choices.add(new Choice(Id.choice.algorithm.rsa, getResources().getString(
+ R.string.rsa)));
+
+ ArrayAdapter<Choice> adapter = new ArrayAdapter<Choice>(context,
+ android.R.layout.simple_spinner_item, choices);
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ algorithm.setAdapter(adapter);
+ // make RSA the default
+ for (int i = 0; i < choices.size(); ++i) {
+ if (choices.get(i).getId() == Id.choice.algorithm.rsa) {
+ algorithm.setSelection(i);
+ break;
+ }
+ }
+
+ final Spinner keySize = (Spinner) view.findViewById(R.id.create_key_size);
+ ArrayAdapter<CharSequence> keySizeAdapter = ArrayAdapter.createFromResource(
+ context, R.array.key_size_spinner_values,
+ android.R.layout.simple_spinner_item);
+ keySizeAdapter
+ .setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ keySize.setAdapter(keySizeAdapter);
+ keySize.setSelection(3); // Default to 4096 for the key length
+ dialog.setPositiveButton(android.R.string.ok,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface di, int id) {
+ di.dismiss();
+ try {
+ final String selectedItem = (String) keySize.getSelectedItem();
+ mNewKeySize = Integer.parseInt(selectedItem);
+ } catch (NumberFormatException e) {
+ mNewKeySize = 0;
+ }
+
+ mNewKeyAlgorithmChoice = (Choice) algorithm.getSelectedItem();
+ mAlgorithmSelectedListener.onAlgorithmSelected(mNewKeyAlgorithmChoice, mNewKeySize);
+ }
+ });
+
+ dialog.setCancelable(true);
+ dialog.setNegativeButton(android.R.string.cancel,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface di, int id) {
+ di.dismiss();
+ }
+ });
+
+ final AlertDialog alertDialog = dialog.create();
+
+ final AdapterView.OnItemSelectedListener weakRsaListener = new AdapterView.OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ final Choice selectedAlgorithm = (Choice) algorithm.getSelectedItem();
+ final int selectedKeySize = Integer.parseInt((String) keySize.getSelectedItem());
+ final boolean isWeakRsa = (selectedAlgorithm.getId() == Id.choice.algorithm.rsa &&
+ selectedKeySize <= 1024);
+ alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(!isWeakRsa);
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+ }
+ };
+
+ keySize.setOnItemSelectedListener(weakRsaListener);
+ algorithm.setOnItemSelectedListener(weakRsaListener);
+
+ return alertDialog;
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteFileDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteFileDialogFragment.java
new file mode 100644
index 000000000..b4c38184c
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteFileDialogFragment.java
@@ -0,0 +1,124 @@
+/*
+ * 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.ui.dialog;
+
+import android.app.AlertDialog;
+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.support.v4.app.DialogFragment;
+import android.support.v4.app.FragmentActivity;
+import android.widget.Toast;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.service.KeychainIntentService;
+import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
+
+public class DeleteFileDialogFragment extends DialogFragment {
+ private static final String ARG_DELETE_FILE = "delete_file";
+
+ /**
+ * Creates new instance of this delete file dialog fragment
+ */
+ public static DeleteFileDialogFragment newInstance(String deleteFile) {
+ DeleteFileDialogFragment frag = new DeleteFileDialogFragment();
+ Bundle args = new Bundle();
+
+ args.putString(ARG_DELETE_FILE, deleteFile);
+
+ frag.setArguments(args);
+
+ return frag;
+ }
+
+ /**
+ * Creates dialog
+ */
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final FragmentActivity activity = getActivity();
+
+ final String deleteFile = getArguments().getString(ARG_DELETE_FILE);
+
+ AlertDialog.Builder alert = new AlertDialog.Builder(activity);
+
+
+ alert.setIcon(R.drawable.ic_dialog_alert_holo_light);
+ alert.setTitle(R.string.warning);
+ alert.setMessage(this.getString(R.string.file_delete_confirmation, deleteFile));
+
+ alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog, int id) {
+ dismiss();
+
+ // Send all information needed to service to edit key in other thread
+ Intent intent = new Intent(activity, KeychainIntentService.class);
+
+ // fill values for this action
+ Bundle data = new Bundle();
+
+ intent.setAction(KeychainIntentService.ACTION_DELETE_FILE_SECURELY);
+ data.putString(KeychainIntentService.DELETE_FILE, deleteFile);
+ intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
+
+ ProgressDialogFragment deletingDialog = ProgressDialogFragment.newInstance(
+ getString(R.string.progress_deleting_securely),
+ ProgressDialog.STYLE_HORIZONTAL,
+ false,
+ null);
+
+ // Message is received after deleting is done in KeychainIntentService
+ KeychainIntentServiceHandler saveHandler =
+ new KeychainIntentServiceHandler(activity, deletingDialog) {
+ public void handleMessage(Message message) {
+ // handle messages by standard KeychainIntentHandler first
+ super.handleMessage(message);
+
+ if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
+ Toast.makeText(activity, R.string.file_delete_successful,
+ Toast.LENGTH_SHORT).show();
+ }
+ }
+ };
+
+ // Create a new Messenger for the communication back
+ Messenger messenger = new Messenger(saveHandler);
+ intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
+
+ // show progress dialog
+ deletingDialog.show(activity.getSupportFragmentManager(), "deletingDialog");
+
+ // start service with intent
+ activity.startService(intent);
+ }
+ });
+ alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ dismiss();
+ }
+ });
+ alert.setCancelable(true);
+
+ return alert.create();
+ }
+}
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
new file mode 100644
index 000000000..72ea4c013
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java
@@ -0,0 +1,174 @@
+/*
+ * 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.AlertDialog;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.support.v4.app.DialogFragment;
+import android.support.v4.app.FragmentActivity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.CheckBox;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData;
+import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
+import org.sufficientlysecure.keychain.provider.KeychainDatabase;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+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 boolean mIsSingleSelection = false;
+
+ private TextView mMainMessage;
+ private CheckBox mCheckDeleteSecret;
+ private LinearLayout mDeleteSecretKeyView;
+ private View mInflateView;
+
+ private Messenger mMessenger;
+
+ /**
+ * 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);
+ //We don't need the key type
+
+ frag.setArguments(args);
+
+ return frag;
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+
+ final FragmentActivity activity = getActivity();
+ mMessenger = getArguments().getParcelable(ARG_MESSENGER);
+
+ final long[] masterKeyIds = getArguments().getLongArray(ARG_DELETE_MASTER_KEY_IDS);
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(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);
+
+ mDeleteSecretKeyView = (LinearLayout) mInflateView.findViewById(R.id.deleteSecretKeyView);
+ mMainMessage = (TextView) mInflateView.findViewById(R.id.mainMessage);
+ mCheckDeleteSecret = (CheckBox) mInflateView.findViewById(R.id.checkDeleteSecret);
+
+ builder.setTitle(R.string.warning);
+
+ // If only a single key has been selected
+ if (masterKeyIds.length == 1) {
+ mIsSingleSelection = true;
+
+ long masterKeyId = masterKeyIds[0];
+
+ HashMap<String, Object> data = ProviderHelper.getUnifiedData(activity, masterKeyId, new String[]{
+ KeyRings.USER_ID,
+ KeyRings.HAS_SECRET
+ }, new int[] { ProviderHelper.FIELD_TYPE_STRING, ProviderHelper.FIELD_TYPE_INTEGER });
+ String userId = (String) data.get(KeyRings.USER_ID);
+ boolean hasSecret = ((Long) data.get(KeyRings.HAS_SECRET)) == 1;
+
+ // Hide the Checkbox and TextView since this is a single selection,user will be notified through message
+ mDeleteSecretKeyView.setVisibility(View.GONE);
+ // Set message depending on which key it is.
+ mMainMessage.setText(getString(
+ hasSecret ? R.string.secret_key_deletion_confirmation
+ : R.string.public_key_deletetion_confirmation,
+ userId));
+ } else {
+ mDeleteSecretKeyView.setVisibility(View.VISIBLE);
+ mMainMessage.setText(R.string.key_deletion_confirmation_multi);
+ }
+
+ builder.setIcon(R.drawable.ic_dialog_alert_holo_light);
+ builder.setPositiveButton(R.string.btn_delete, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+
+ boolean success = false;
+ for(long masterKeyId : masterKeyIds) {
+ int count = activity.getContentResolver().delete(
+ KeyRingData.buildPublicKeyRingUri(Long.toString(masterKeyId)), null, null
+ );
+ success = count > 0;
+ }
+ if (success) {
+ sendMessageToHandler(MESSAGE_OKAY, null);
+ } else {
+ sendMessageToHandler(MESSAGE_ERROR, null);
+ }
+ dismiss();
+ }
+ });
+ builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog, int id) {
+ dismiss();
+ }
+ });
+
+ return builder.create();
+ }
+
+ /**
+ * Send message back to handler which is initialized in a activity
+ *
+ * @param what Message integer you want to send
+ */
+ private void sendMessageToHandler(Integer what, Bundle data) {
+ Message msg = Message.obtain();
+ msg.what = what;
+ 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);
+ }
+ }
+}
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
new file mode 100644
index 000000000..a4285c8e9
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java
@@ -0,0 +1,220 @@
+/*
+ * 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.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+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.view.LayoutInflater;
+import android.view.View;
+import android.widget.CheckBox;
+import android.widget.EditText;
+import android.widget.TextView;
+import com.beardedhen.androidbootstrap.BootstrapButton;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.helper.FileHelper;
+import org.sufficientlysecure.keychain.util.Log;
+
+public class FileDialogFragment 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_DEFAULT_FILE = "default_file";
+ private static final String ARG_CHECKBOX_TEXT = "checkbox_text";
+
+ public static final int MESSAGE_OKAY = 1;
+
+ public static final String MESSAGE_DATA_FILENAME = "filename";
+ public static final String MESSAGE_DATA_CHECKED = "checked";
+
+ private Messenger mMessenger;
+
+ private EditText mFilename;
+ private BootstrapButton mBrowse;
+ private CheckBox mCheckBox;
+ private TextView mMessageTextView;
+
+ private static final int REQUEST_CODE = 0x00007004;
+
+ /**
+ * Creates new instance of this file dialog fragment
+ */
+ public static FileDialogFragment newInstance(Messenger messenger, String title, String message,
+ String defaultFile, String checkboxText) {
+ FileDialogFragment frag = new FileDialogFragment();
+ Bundle args = new Bundle();
+ args.putParcelable(ARG_MESSENGER, messenger);
+
+ args.putString(ARG_TITLE, title);
+ args.putString(ARG_MESSAGE, message);
+ args.putString(ARG_DEFAULT_FILE, defaultFile);
+ args.putString(ARG_CHECKBOX_TEXT, checkboxText);
+
+ frag.setArguments(args);
+
+ return frag;
+ }
+
+ /**
+ * Creates dialog
+ */
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final Activity activity = getActivity();
+
+ mMessenger = getArguments().getParcelable(ARG_MESSENGER);
+
+ String title = getArguments().getString(ARG_TITLE);
+ String message = getArguments().getString(ARG_MESSAGE);
+ String defaultFile = getArguments().getString(ARG_DEFAULT_FILE);
+ String checkboxText = getArguments().getString(ARG_CHECKBOX_TEXT);
+
+ LayoutInflater inflater = (LayoutInflater) activity
+ .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ AlertDialog.Builder alert = new AlertDialog.Builder(activity);
+ alert.setTitle(title);
+
+ View view = inflater.inflate(R.layout.file_dialog, null);
+
+ mMessageTextView = (TextView) view.findViewById(R.id.message);
+ mMessageTextView.setText(message);
+
+ mFilename = (EditText) view.findViewById(R.id.input);
+ mFilename.setText(defaultFile);
+ mBrowse = (BootstrapButton) 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, mFilename.getText().toString(), "*/*",
+ REQUEST_CODE);
+ }
+ });
+
+ mCheckBox = (CheckBox) view.findViewById(R.id.checkbox);
+ if (checkboxText == null) {
+ mCheckBox.setEnabled(false);
+ mCheckBox.setVisibility(View.GONE);
+ } else {
+ mCheckBox.setEnabled(true);
+ mCheckBox.setVisibility(View.VISIBLE);
+ mCheckBox.setText(checkboxText);
+ }
+
+ alert.setView(view);
+
+ alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog, int id) {
+ dismiss();
+
+ boolean checked = false;
+ if (mCheckBox.isEnabled()) {
+ checked = mCheckBox.isChecked();
+ }
+
+ // return resulting data back to activity
+ Bundle data = new Bundle();
+ data.putString(MESSAGE_DATA_FILENAME, mFilename.getText().toString());
+ data.putBoolean(MESSAGE_DATA_CHECKED, checked);
+
+ sendMessageToHandler(MESSAGE_OKAY, data);
+ }
+ });
+
+ alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog, int id) {
+ dismiss();
+ }
+ });
+ return alert.create();
+ }
+
+ /**
+ * Updates filename in dialog, normally called in onActivityResult in activity using the
+ * FileDialog
+ */
+ private void setFilename(String filename) {
+ AlertDialog dialog = (AlertDialog) getDialog();
+ EditText filenameEditText = (EditText) dialog.findViewById(R.id.input);
+
+ if (filenameEditText != null) {
+ filenameEditText.setText(filename);
+ }
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ switch (requestCode & 0xFFFF) {
+ case REQUEST_CODE: {
+ if (resultCode == Activity.RESULT_OK && data != null) {
+ try {
+ String path = data.getData().getPath();
+ Log.d(Constants.TAG, "path=" + path);
+
+ // set filename used in export/import dialogs
+ setFilename(path);
+ } catch (NullPointerException e) {
+ Log.e(Constants.TAG, "Nullpointer while retrieving path!", e);
+ }
+ }
+
+ break;
+ }
+
+ default:
+ super.onActivityResult(requestCode, resultCode, data);
+
+ break;
+ }
+ }
+
+ /**
+ * Send message back to handler which is initialized in a activity
+ *
+ * @param what Message integer you want to send
+ */
+ private void sendMessageToHandler(Integer what, Bundle data) {
+ Message msg = Message.obtain();
+ msg.what = what;
+ 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);
+ }
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/PassphraseDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/PassphraseDialogFragment.java
new file mode 100644
index 000000000..a3feab959
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/PassphraseDialogFragment.java
@@ -0,0 +1,328 @@
+/*
+ * 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.ui.dialog;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+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.DialogFragment;
+import android.support.v4.app.FragmentActivity;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowManager.LayoutParams;
+import android.view.inputmethod.EditorInfo;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.TextView.OnEditorActionListener;
+import android.widget.Toast;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPPrivateKey;
+import org.spongycastle.openpgp.PGPSecretKey;
+import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
+import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.Id;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
+import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.service.PassphraseCacheService;
+import org.sufficientlysecure.keychain.util.Log;
+
+public class PassphraseDialogFragment extends DialogFragment implements OnEditorActionListener {
+ private static final String ARG_MESSENGER = "messenger";
+ private static final String ARG_SECRET_KEY_ID = "secret_key_id";
+
+ public static final int MESSAGE_OKAY = 1;
+ public static final int MESSAGE_CANCEL = 2;
+
+ public static final String MESSAGE_DATA_PASSPHRASE = "passphrase";
+
+ private Messenger mMessenger;
+ private EditText mPassphraseEditText;
+ private boolean mCanKB;
+
+ /**
+ * 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(FragmentActivity context, long keyId, Handler returnHandler) {
+ // Create a new Messenger for the communication back
+ Messenger messenger = new Messenger(returnHandler);
+
+ try {
+ PassphraseDialogFragment passphraseDialog = PassphraseDialogFragment.newInstance(context,
+ messenger, keyId);
+
+ passphraseDialog.show(context.getSupportFragmentManager(), "passphraseDialog");
+ } catch (PgpGeneralException e) {
+ Log.d(Constants.TAG, "No passphrase for this secret key, encrypt directly!");
+ // send message to handler to start encryption directly
+ returnHandler.sendEmptyMessage(PassphraseDialogFragment.MESSAGE_OKAY);
+ }
+ }
+
+ /**
+ * Creates new instance of this dialog fragment
+ *
+ * @param secretKeyId secret key id you want to use
+ * @param messenger to communicate back after caching the passphrase
+ * @return
+ * @throws PgpGeneralException
+ */
+ public static PassphraseDialogFragment newInstance(Context context, Messenger messenger,
+ long secretKeyId) throws PgpGeneralException {
+ // check if secret key has a passphrase
+ if (!(secretKeyId == Id.key.symmetric || secretKeyId == Id.key.none)) {
+ if (!PassphraseCacheService.hasPassphrase(context, secretKeyId)) {
+ throw new PgpGeneralException("No passphrase! No passphrase dialog needed!");
+ }
+ }
+
+ PassphraseDialogFragment frag = new PassphraseDialogFragment();
+ Bundle args = new Bundle();
+ args.putLong(ARG_SECRET_KEY_ID, secretKeyId);
+ args.putParcelable(ARG_MESSENGER, messenger);
+
+ frag.setArguments(args);
+
+ return frag;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ }
+
+ /**
+ * Creates dialog
+ */
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final Activity activity = getActivity();
+ final long secretKeyId = getArguments().getLong(ARG_SECRET_KEY_ID);
+ mMessenger = getArguments().getParcelable(ARG_MESSENGER);
+
+ AlertDialog.Builder alert = new AlertDialog.Builder(activity);
+
+ alert.setTitle(R.string.title_authentication);
+
+ final PGPSecretKey secretKey;
+
+ if (secretKeyId == Id.key.symmetric || secretKeyId == Id.key.none) {
+ secretKey = null;
+ alert.setMessage(R.string.passphrase_for_symmetric_encryption);
+ } else {
+ secretKey = ProviderHelper.getPGPSecretKeyRing(activity, secretKeyId).getSecretKey();
+
+ if (secretKey == null) {
+ alert.setTitle(R.string.title_key_not_found);
+ alert.setMessage(getString(R.string.key_not_found, secretKeyId));
+ alert.setPositiveButton(android.R.string.ok, new OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ dismiss();
+ }
+ });
+ alert.setCancelable(false);
+ mCanKB = false;
+ return alert.create();
+ }
+ String userId = PgpKeyHelper.getMainUserIdSafe(activity, secretKey);
+
+ Log.d(Constants.TAG, "User id: '" + userId + "'");
+ alert.setMessage(getString(R.string.passphrase_for, userId));
+ }
+
+ LayoutInflater inflater = activity.getLayoutInflater();
+ View view = inflater.inflate(R.layout.passphrase_dialog, null);
+ alert.setView(view);
+
+ mPassphraseEditText = (EditText) view.findViewById(R.id.passphrase_passphrase);
+
+ alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog, int id) {
+ dismiss();
+ long curKeyIndex = 1;
+ boolean keyOK = true;
+ String passphrase = mPassphraseEditText.getText().toString();
+ long keyId;
+ PGPSecretKey clickSecretKey = secretKey;
+
+ if (clickSecretKey != null) {
+ while (keyOK) {
+ if (clickSecretKey != null) { // check again for loop
+ try {
+ PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder()
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
+ passphrase.toCharArray());
+ PGPPrivateKey testKey = clickSecretKey
+ .extractPrivateKey(keyDecryptor);
+ if (testKey == null) {
+ if (!clickSecretKey.isMasterKey()) {
+ Toast.makeText(activity,
+ R.string.error_could_not_extract_private_key,
+ Toast.LENGTH_SHORT).show();
+
+ sendMessageToHandler(MESSAGE_CANCEL);
+ return;
+ } else {
+ clickSecretKey = PgpKeyHelper.getKeyNum(ProviderHelper
+ .getPGPSecretKeyRingWithKeyId(activity, secretKeyId),
+ curKeyIndex);
+ curKeyIndex++; // does post-increment work like C?
+ continue;
+ }
+ } else {
+ keyOK = false;
+ }
+ } catch (PGPException e) {
+ Toast.makeText(activity, R.string.wrong_passphrase,
+ Toast.LENGTH_SHORT).show();
+
+ sendMessageToHandler(MESSAGE_CANCEL);
+ return;
+ }
+ } else {
+ Toast.makeText(activity, R.string.error_could_not_extract_private_key,
+ Toast.LENGTH_SHORT).show();
+
+ sendMessageToHandler(MESSAGE_CANCEL);
+ return; // ran out of keys to try
+ }
+ }
+ keyId = secretKey.getKeyID();
+ } else {
+ keyId = Id.key.symmetric;
+ }
+
+ // cache the new passphrase
+ Log.d(Constants.TAG, "Everything okay! Caching entered passphrase");
+ PassphraseCacheService.addCachedPassphrase(activity, keyId, passphrase);
+ if (!keyOK && clickSecretKey.getKeyID() != keyId) {
+ PassphraseCacheService.addCachedPassphrase(activity, clickSecretKey.getKeyID(),
+ passphrase);
+ }
+
+ // also return passphrase back to activity
+ Bundle data = new Bundle();
+ data.putString(MESSAGE_DATA_PASSPHRASE, passphrase);
+
+ sendMessageToHandler(MESSAGE_OKAY, data);
+ }
+ });
+
+ alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog, int id) {
+ dialog.cancel();
+ }
+ });
+
+ mCanKB = true;
+ return alert.create();
+ }
+
+ @Override
+ public void onActivityCreated(Bundle arg0) {
+ super.onActivityCreated(arg0);
+ if (mCanKB) {
+ // request focus and open soft keyboard
+ mPassphraseEditText.requestFocus();
+ getDialog().getWindow().setSoftInputMode(LayoutParams.SOFT_INPUT_STATE_VISIBLE);
+
+ mPassphraseEditText.setOnEditorActionListener(this);
+ }
+ }
+
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ super.onCancel(dialog);
+
+ dismiss();
+ sendMessageToHandler(MESSAGE_CANCEL);
+ }
+
+ /**
+ * 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) {
+ if (EditorInfo.IME_ACTION_DONE == actionId) {
+ AlertDialog dialog = ((AlertDialog) getDialog());
+ Button bt = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
+
+ bt.performClick();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Send message back to handler which is initialized in a activity
+ *
+ * @param what Message integer you want to send
+ */
+ private void sendMessageToHandler(Integer what) {
+ Message msg = Message.obtain();
+ msg.what = what;
+
+ 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);
+ }
+ }
+
+ /**
+ * Send message back to handler which is initialized in a activity
+ *
+ * @param what Message integer you want to send
+ */
+ private void sendMessageToHandler(Integer what, Bundle data) {
+ Message msg = Message.obtain();
+ msg.what = what;
+ 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);
+ }
+ }
+
+}
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
new file mode 100644
index 000000000..132a2ce86
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ProgressDialogFragment.java
@@ -0,0 +1,154 @@
+/*
+ * 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.ui.dialog;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.app.ProgressDialog;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnCancelListener;
+import android.content.DialogInterface.OnKeyListener;
+import android.os.Bundle;
+import android.support.v4.app.DialogFragment;
+import android.view.KeyEvent;
+import org.sufficientlysecure.keychain.R;
+
+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 OnCancelListener mOnCancelListener;
+
+ /**
+ * Creates new instance of this fragment
+ *
+ * @param message
+ * @param style
+ * @param cancelable
+ * @return
+ */
+ public static ProgressDialogFragment newInstance(String message, int style, boolean cancelable,
+ OnCancelListener onCancelListener) {
+ ProgressDialogFragment frag = new ProgressDialogFragment();
+ Bundle args = new Bundle();
+ args.putString(ARG_MESSAGE, message);
+ args.putInt(ARG_STYLE, style);
+ args.putBoolean(ARG_CANCELABLE, cancelable);
+
+ frag.setArguments(args);
+ frag.mOnCancelListener = onCancelListener;
+
+ return frag;
+ }
+
+ /**
+ * Updates progress of dialog
+ *
+ * @param messageId
+ * @param progress
+ * @param max
+ */
+ public void setProgress(int messageId, int progress, int max) {
+ setProgress(getString(messageId), progress, max);
+ }
+
+ /**
+ * Updates progress of dialog
+ *
+ * @param progress
+ * @param max
+ */
+ public void setProgress(int progress, int max) {
+ ProgressDialog dialog = (ProgressDialog) getDialog();
+
+ dialog.setProgress(progress);
+ dialog.setMax(max);
+ }
+
+ /**
+ * Updates progress of dialog
+ *
+ * @param message
+ * @param progress
+ * @param max
+ */
+ public void setProgress(String message, int progress, int max) {
+ ProgressDialog dialog = (ProgressDialog) getDialog();
+
+ dialog.setMessage(message);
+ dialog.setProgress(progress);
+ dialog.setMax(max);
+ }
+
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ super.onCancel(dialog);
+
+ if (this.mOnCancelListener != null) {
+ this.mOnCancelListener.onCancel(dialog);
+ }
+ }
+
+ /**
+ * Creates dialog
+ */
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final Activity activity = getActivity();
+
+ ProgressDialog dialog = new ProgressDialog(activity);
+ dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
+ dialog.setCancelable(false);
+ dialog.setCanceledOnTouchOutside(false);
+
+ String message = getArguments().getString(ARG_MESSAGE);
+ int style = getArguments().getInt(ARG_STYLE);
+ boolean cancelable = getArguments().getBoolean(ARG_CANCELABLE);
+
+ dialog.setMessage(message);
+ dialog.setProgressStyle(style);
+
+ if (cancelable) {
+ dialog.setButton(DialogInterface.BUTTON_NEGATIVE,
+ activity.getString(R.string.progress_cancel),
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.cancel();
+ }
+ });
+ }
+
+ // Disable the back button
+ OnKeyListener keyListener = new OnKeyListener() {
+
+ @Override
+ public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ return true;
+ }
+ return false;
+ }
+
+ };
+ dialog.setOnKeyListener(keyListener);
+
+ return dialog;
+ }
+}
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
new file mode 100644
index 000000000..ae61c1470
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SetPassphraseDialogFragment.java
@@ -0,0 +1,186 @@
+/*
+ * 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.ui.dialog;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.support.v4.app.DialogFragment;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowManager.LayoutParams;
+import android.view.inputmethod.EditorInfo;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.TextView.OnEditorActionListener;
+import android.widget.Toast;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.util.Log;
+
+public class SetPassphraseDialogFragment extends DialogFragment implements OnEditorActionListener {
+ private static final String ARG_MESSENGER = "messenger";
+ private static final String ARG_TITLE = "title";
+
+ public static final int MESSAGE_OKAY = 1;
+
+ public static final String MESSAGE_NEW_PASSPHRASE = "new_passphrase";
+
+ private Messenger mMessenger;
+ private EditText mPassphraseEditText;
+ private EditText mPassphraseAgainEditText;
+
+ /**
+ * Creates new instance of this dialog fragment
+ *
+ * @param title title of dialog
+ * @param messenger to communicate back after setting the passphrase
+ * @return
+ */
+ public static SetPassphraseDialogFragment newInstance(Messenger messenger, int title) {
+ SetPassphraseDialogFragment frag = new SetPassphraseDialogFragment();
+ Bundle args = new Bundle();
+ args.putInt(ARG_TITLE, title);
+ args.putParcelable(ARG_MESSENGER, messenger);
+
+ frag.setArguments(args);
+
+ return frag;
+ }
+
+ /**
+ * Creates dialog
+ */
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final Activity activity = getActivity();
+
+ int title = getArguments().getInt(ARG_TITLE);
+ mMessenger = getArguments().getParcelable(ARG_MESSENGER);
+
+ AlertDialog.Builder alert = new AlertDialog.Builder(activity);
+
+ alert.setTitle(title);
+ alert.setMessage(R.string.enter_passphrase_twice);
+
+ LayoutInflater inflater = activity.getLayoutInflater();
+ View view = inflater.inflate(R.layout.passphrase_repeat_dialog, null);
+ alert.setView(view);
+
+ mPassphraseEditText = (EditText) view.findViewById(R.id.passphrase_passphrase);
+ mPassphraseAgainEditText = (EditText) view.findViewById(R.id.passphrase_passphrase_again);
+
+ alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog, int id) {
+ dismiss();
+
+ String passphrase1 = mPassphraseEditText.getText().toString();
+ String passphrase2 = mPassphraseAgainEditText.getText().toString();
+ if (!passphrase1.equals(passphrase2)) {
+ Toast.makeText(
+ activity,
+ getString(R.string.error_message,
+ getString(R.string.passphrases_do_not_match)), Toast.LENGTH_SHORT)
+ .show();
+ return;
+ }
+
+ if (passphrase1.equals("")) {
+ Toast.makeText(
+ activity,
+ getString(R.string.error_message,
+ getString(R.string.passphrase_must_not_be_empty)),
+ Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ // return resulting data back to activity
+ Bundle data = new Bundle();
+ data.putString(MESSAGE_NEW_PASSPHRASE, passphrase1);
+
+ sendMessageToHandler(MESSAGE_OKAY, data);
+ }
+ });
+
+ alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog, int id) {
+ dismiss();
+ }
+ });
+
+ return alert.create();
+ }
+
+ @Override
+ public void onActivityCreated(Bundle arg0) {
+ super.onActivityCreated(arg0);
+
+ // request focus and open soft keyboard
+ mPassphraseEditText.requestFocus();
+ getDialog().getWindow().setSoftInputMode(LayoutParams.SOFT_INPUT_STATE_VISIBLE);
+
+ mPassphraseAgainEditText.setOnEditorActionListener(this);
+ }
+
+ /**
+ * 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) {
+ if (EditorInfo.IME_ACTION_DONE == actionId) {
+ AlertDialog dialog = ((AlertDialog) getDialog());
+ Button bt = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
+
+ bt.performClick();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Send message back to handler which is initialized in a activity
+ *
+ * @param what Message integer you want to send
+ */
+ private void sendMessageToHandler(Integer what, Bundle data) {
+ Message msg = Message.obtain();
+ msg.what = what;
+ 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);
+ }
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ShareNfcDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ShareNfcDialogFragment.java
new file mode 100644
index 000000000..741530b1d
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ShareNfcDialogFragment.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.ui.dialog;
+
+import android.annotation.TargetApi;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.nfc.NfcAdapter;
+import android.os.Build;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.support.v4.app.DialogFragment;
+import android.support.v4.app.FragmentActivity;
+import org.sufficientlysecure.htmltextview.HtmlTextView;
+import org.sufficientlysecure.keychain.R;
+
+@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+public class ShareNfcDialogFragment extends DialogFragment {
+
+ /**
+ * Creates new instance of this fragment
+ */
+ public static ShareNfcDialogFragment newInstance() {
+ ShareNfcDialogFragment frag = new ShareNfcDialogFragment();
+
+ return frag;
+ }
+
+ /**
+ * Creates dialog
+ */
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final FragmentActivity activity = getActivity();
+
+ AlertDialog.Builder alert = new AlertDialog.Builder(activity);
+
+ alert.setTitle(R.string.share_nfc_dialog);
+ alert.setCancelable(true);
+
+ alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int id) {
+ dismiss();
+ }
+ });
+
+ HtmlTextView textView = new HtmlTextView(getActivity());
+ textView.setPadding(8, 8, 8, 8);
+ alert.setView(textView);
+
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
+ textView.setText(getString(R.string.error) + ": "
+ + getString(R.string.error_jelly_bean_needed));
+ } else {
+ // check if NFC Adapter is available
+ NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(getActivity());
+ if (nfcAdapter == null) {
+ textView.setText(getString(R.string.error) + ": "
+ + getString(R.string.error_nfc_needed));
+ } else {
+ // nfc works...
+ textView.setHtmlFromRawResource(getActivity(), R.raw.nfc_beam_share);
+
+ alert.setNegativeButton(R.string.menu_beam_preferences,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int id) {
+ Intent intentSettings = new Intent(
+ Settings.ACTION_NFCSHARING_SETTINGS);
+ startActivity(intentSettings);
+ }
+ });
+ }
+ }
+
+ // no flickering when clicking textview for Android < 4
+ // aboutTextView.setTextColor(getResources().getColor(android.R.color.black));
+
+ return alert.create();
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ShareQrCodeDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ShareQrCodeDialogFragment.java
new file mode 100644
index 000000000..b6ff139df
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ShareQrCodeDialogFragment.java
@@ -0,0 +1,212 @@
+/*
+ * 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.ui.dialog;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v4.app.DialogFragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
+import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.util.QrCodeUtils;
+
+import java.util.ArrayList;
+
+public class ShareQrCodeDialogFragment extends DialogFragment {
+ private static final String ARG_KEY_URI = "uri";
+ private static final String ARG_FINGERPRINT_ONLY = "fingerprint_only";
+
+ private ImageView mImage;
+ private TextView mText;
+
+ private boolean mFingerprintOnly;
+
+ private ArrayList<String> mContentList;
+ private int mCounter;
+
+ private static final int QR_CODE_SIZE = 1000;
+
+ /**
+ * Creates new instance of this dialog fragment
+ */
+ public static ShareQrCodeDialogFragment newInstance(Uri dataUri, boolean fingerprintOnly) {
+ ShareQrCodeDialogFragment frag = new ShareQrCodeDialogFragment();
+ Bundle args = new Bundle();
+ args.putParcelable(ARG_KEY_URI, dataUri);
+ args.putBoolean(ARG_FINGERPRINT_ONLY, fingerprintOnly);
+
+ frag.setArguments(args);
+
+ return frag;
+ }
+
+ /**
+ * Creates dialog
+ */
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final Activity activity = getActivity();
+
+ Uri dataUri = getArguments().getParcelable(ARG_KEY_URI);
+ mFingerprintOnly = getArguments().getBoolean(ARG_FINGERPRINT_ONLY);
+
+ AlertDialog.Builder alert = new AlertDialog.Builder(getActivity());
+
+ alert.setTitle(R.string.share_qr_code_dialog_title);
+
+ LayoutInflater inflater = activity.getLayoutInflater();
+ View view = inflater.inflate(R.layout.share_qr_code_dialog, null);
+ alert.setView(view);
+
+ mImage = (ImageView) view.findViewById(R.id.share_qr_code_dialog_image);
+ mText = (TextView) view.findViewById(R.id.share_qr_code_dialog_text);
+
+ String content = null;
+ if (mFingerprintOnly) {
+ alert.setPositiveButton(R.string.btn_okay, null);
+
+ byte[] blob = (byte[]) ProviderHelper.getGenericData(
+ getActivity(), KeyRings.buildUnifiedKeyRingUri(dataUri),
+ KeyRings.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB);
+ if(blob == null) {
+ // TODO error handling?!
+ return null;
+ }
+
+ String fingerprint = PgpKeyHelper.convertFingerprintToHex(blob);
+ mText.setText(getString(R.string.share_qr_code_dialog_fingerprint_text) + " " + fingerprint);
+ content = Constants.FINGERPRINT_SCHEME + ":" + fingerprint;
+ setQrCode(content);
+ } else {
+ mText.setText(R.string.share_qr_code_dialog_start);
+
+ // TODO works, but
+ long masterKeyId = ProviderHelper.getMasterKeyId(getActivity(), dataUri);
+ // get public keyring as ascii armored string
+ ArrayList<String> keyringArmored = ProviderHelper.getKeyRingsAsArmoredString(
+ getActivity(), new long[] { masterKeyId });
+
+ // TODO: binary?
+
+ content = keyringArmored.get(0);
+
+ // OnClickListener are set in onResume to prevent automatic dismissing of Dialogs
+ // http://bit.ly/O5vfaR
+ alert.setPositiveButton(R.string.btn_next, null);
+ alert.setNegativeButton(android.R.string.cancel, null);
+
+ mContentList = splitString(content, 1000);
+
+ // start with first
+ mCounter = 0;
+ updatePartsQrCode();
+ }
+
+ return alert.create();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ if (!mFingerprintOnly) {
+ AlertDialog alertDialog = (AlertDialog) getDialog();
+ final Button backButton = alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE);
+ final Button nextButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
+
+ backButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mCounter > 0) {
+ mCounter--;
+ updatePartsQrCode();
+ updateDialog(backButton, nextButton);
+ } else {
+ dismiss();
+ }
+ }
+ });
+ nextButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+
+ if (mCounter < mContentList.size() - 1) {
+ mCounter++;
+ updatePartsQrCode();
+ updateDialog(backButton, nextButton);
+ } else {
+ dismiss();
+ }
+ }
+ });
+ }
+ }
+
+ private void updatePartsQrCode() {
+ // Content: <counter>,<size>,<content>
+ setQrCode(mCounter + "," + mContentList.size() + "," + mContentList.get(mCounter));
+ }
+
+ private void setQrCode(String data) {
+ mImage.setImageBitmap(QrCodeUtils.getQRCodeBitmap(data, QR_CODE_SIZE));
+ }
+
+ private void updateDialog(Button backButton, Button nextButton) {
+ if (mCounter == 0) {
+ backButton.setText(android.R.string.cancel);
+ } else {
+ backButton.setText(R.string.btn_back);
+ }
+ if (mCounter == mContentList.size() - 1) {
+ nextButton.setText(android.R.string.ok);
+ } else {
+ nextButton.setText(R.string.btn_next);
+ }
+
+ mText.setText(getResources().getString(R.string.share_qr_code_dialog_progress,
+ mCounter + 1, mContentList.size()));
+ }
+
+ /**
+ * Split String by number of characters
+ *
+ * @param text
+ * @param size
+ * @return
+ */
+ private ArrayList<String> splitString(String text, int size) {
+ ArrayList<String> strings = new ArrayList<String>();
+ int index = 0;
+ while (index < text.length()) {
+ strings.add(text.substring(index, Math.min(index + size, text.length())));
+ index += size;
+ }
+
+ return strings;
+ }
+}
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
new file mode 100644
index 000000000..7b21c189d
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/Editor.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
+ *
+ * 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.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/FixedListView.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/FixedListView.java
new file mode 100644
index 000000000..da29f808a
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/FixedListView.java
@@ -0,0 +1,55 @@
+/*
+ * 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.widget;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.ListView;
+
+/**
+ * Automatically calculate height of ListView based on contained items. This enables to put this
+ * ListView into a ScrollView without messing up.
+ * <p/>
+ * from
+ * http://stackoverflow.com/questions/2419246/how-do-i-create-a-listview-thats-not-in-a-scrollview-
+ * or-has-the-scrollview-dis
+ */
+public class FixedListView extends ListView {
+
+ public FixedListView(Context context) {
+ super(context);
+ }
+
+ public FixedListView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ public FixedListView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ // Calculate height of the entire list by providing a very large
+ // height hint. But do not use the highest 2 bits of this integer;
+ // those are reserved for the MeasureSpec mode.
+ int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
+ super.onMeasure(widthMeasureSpec, expandSpec);
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/FoldableLinearLayout.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/FoldableLinearLayout.java
new file mode 100644
index 000000000..6b2f3bf06
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/FoldableLinearLayout.java
@@ -0,0 +1,203 @@
+/*
+ * 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.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import com.beardedhen.androidbootstrap.FontAwesomeText;
+import org.sufficientlysecure.keychain.R;
+
+/**
+ * Class representing a LinearLayout that can fold and hide it's content when pressed
+ * To use just add the following to your xml layout
+
+ <org.sufficientlysecure.keychain.ui.widget.FoldableLinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ custom:foldedLabel="@string/TEXT_TO_DISPLAY_WHEN_FOLDED"
+ custom:unFoldedLabel="@string/TEXT_TO_DISPLAY_WHEN_UNFOLDED"
+ custom:foldedIcon="ICON_NAME_FROM_FontAwesomeText_TO_USE_WHEN_FOLDED"
+ custom:unFoldedIcon="ICON_NAME_FROM_FontAwesomeText_TO_USE_WHEN_UNFOLDED">
+
+ <include layout="@layout/ELEMENTS_TO_BE_FOLDED"/>
+
+ </org.sufficientlysecure.keychain.ui.widget.FoldableLinearLayout>
+
+ */
+public class FoldableLinearLayout extends LinearLayout {
+
+ private FontAwesomeText mFoldableIcon;
+ private boolean mFolded;
+ private boolean mHasMigrated = false;
+ private Integer mShortAnimationDuration = null;
+ private TextView mFoldableTextView = null;
+ private LinearLayout mFoldableContainer = null;
+ private View mFoldableLayout = null;
+
+ private String mFoldedIconName;
+ private String mUnFoldedIconName;
+ private String mFoldedLabel;
+ private String mUnFoldedLabel;
+
+ public FoldableLinearLayout(Context context) {
+ super(context);
+ processAttributes(context, null);
+ }
+
+ public FoldableLinearLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ processAttributes(context, attrs);
+ }
+
+ public FoldableLinearLayout(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs);
+ processAttributes(context, attrs);
+ }
+
+ /**
+ * Load given attributes to inner variables,
+ * @param context
+ * @param attrs
+ */
+ private void processAttributes(Context context, AttributeSet attrs) {
+ if (attrs != null) {
+ TypedArray a = context.obtainStyledAttributes(attrs,
+ R.styleable.FoldableLinearLayout, 0, 0);
+ mFoldedIconName = a.getString(R.styleable.FoldableLinearLayout_foldedIcon);
+ mUnFoldedIconName = a.getString(R.styleable.FoldableLinearLayout_unFoldedIcon);
+ mFoldedLabel = a.getString(R.styleable.FoldableLinearLayout_foldedLabel);
+ mUnFoldedLabel = a.getString(R.styleable.FoldableLinearLayout_unFoldedLabel);
+ a.recycle();
+ }
+ // If any attribute isn't found then set a default one
+ mFoldedIconName = (mFoldedIconName == null) ? "fa-chevron-right" : mFoldedIconName;
+ mUnFoldedIconName = (mUnFoldedIconName == null) ? "fa-chevron-down" : mUnFoldedIconName;
+ mFoldedLabel = (mFoldedLabel == null) ? context.getString(R.id.none) : mFoldedLabel;
+ mUnFoldedLabel = (mUnFoldedLabel == null) ? context.getString(R.id.none) : mUnFoldedLabel;
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ // if the migration has already happened
+ // there is no need to move any children
+ if (!mHasMigrated) {
+ migrateChildrenToContainer();
+ mHasMigrated = true;
+ }
+
+ initialiseInnerViews();
+
+ super.onFinishInflate();
+ }
+
+ /**
+ * Migrates Child views as declared in xml to the inner foldableContainer
+ */
+ private void migrateChildrenToContainer() {
+ // Collect children of FoldableLinearLayout as declared in XML
+ int childNum = getChildCount();
+ View[] children = new View[childNum];
+
+ for (int i = 0; i < childNum; i++) {
+ children[i] = getChildAt(i);
+ }
+ if (children[0].getId() == R.id.foldableControl) {
+
+ }
+
+ // remove all of them from FoldableLinearLayout
+ detachAllViewsFromParent();
+
+ // Inflate the inner foldable_linearlayout.xml
+ LayoutInflater inflator = (LayoutInflater) getContext().getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+
+ mFoldableLayout = inflator.inflate(R.layout.foldable_linearlayout, this, true);
+ mFoldableContainer = (LinearLayout) mFoldableLayout.findViewById(R.id.foldableContainer);
+
+ // Push previously collected children into foldableContainer.
+ for (int i = 0; i < childNum; i++) {
+ addView(children[i]);
+ }
+ }
+
+ private void initialiseInnerViews() {
+ mFoldableIcon = (FontAwesomeText) mFoldableLayout.findViewById(R.id.foldableIcon);
+ mFoldableIcon.setIcon(mFoldedIconName);
+ mFoldableTextView = (TextView) mFoldableLayout.findViewById(R.id.foldableText);
+ mFoldableTextView.setText(mFoldedLabel);
+
+ // retrieve and cache the system's short animation time
+ mShortAnimationDuration = getResources().getInteger(android.R.integer.config_shortAnimTime);
+
+ LinearLayout foldableControl = (LinearLayout) mFoldableLayout.findViewById(R.id.foldableControl);
+ foldableControl.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ mFolded = !mFolded;
+ if (mFolded) {
+ mFoldableIcon.setIcon(mUnFoldedIconName);
+ mFoldableContainer.setVisibility(View.VISIBLE);
+ AlphaAnimation animation = new AlphaAnimation(0f, 1f);
+ animation.setDuration(mShortAnimationDuration);
+ mFoldableContainer.startAnimation(animation);
+ mFoldableTextView.setText(mUnFoldedLabel);
+
+ } else {
+ mFoldableIcon.setIcon(mFoldedIconName);
+ AlphaAnimation animation = new AlphaAnimation(1f, 0f);
+ animation.setDuration(mShortAnimationDuration);
+ animation.setAnimationListener(new Animation.AnimationListener() {
+ @Override
+ public void onAnimationStart(Animation animation) { }
+
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ // making sure that at the end the container is completely removed from view
+ mFoldableContainer.setVisibility(View.GONE);
+ }
+
+ @Override
+ public void onAnimationRepeat(Animation animation) { }
+ });
+ mFoldableContainer.startAnimation(animation);
+ mFoldableTextView.setText(mFoldedLabel);
+ }
+ }
+ });
+
+ }
+
+ /**
+ * Adds provided child view to foldableContainer View
+ * @param child
+ */
+ @Override
+ public void addView(View child) {
+ if (mFoldableContainer != null) {
+ mFoldableContainer.addView(child);
+ }
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/IntegerListPreference.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/IntegerListPreference.java
new file mode 100644
index 000000000..6e1e4c678
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/IntegerListPreference.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * 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.widget;
+
+import android.content.Context;
+import android.preference.ListPreference;
+import android.util.AttributeSet;
+
+/**
+ * A list preference which persists its values as integers instead of strings. Code reading the
+ * values should use {@link android.content.SharedPreferences#getInt}. When using XML-declared
+ * arrays for entry values, the arrays should be regular string arrays containing valid integer
+ * values.
+ *
+ * @author Rodrigo Damazio
+ */
+public class IntegerListPreference extends ListPreference {
+
+ public IntegerListPreference(Context context) {
+ super(context);
+
+ verifyEntryValues(null);
+ }
+
+ public IntegerListPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ verifyEntryValues(null);
+ }
+
+ @Override
+ public void setEntryValues(CharSequence[] entryValues) {
+ CharSequence[] oldValues = getEntryValues();
+ super.setEntryValues(entryValues);
+ verifyEntryValues(oldValues);
+ }
+
+ @Override
+ public void setEntryValues(int entryValuesResId) {
+ CharSequence[] oldValues = getEntryValues();
+ super.setEntryValues(entryValuesResId);
+ verifyEntryValues(oldValues);
+ }
+
+ @Override
+ protected String getPersistedString(String defaultReturnValue) {
+ // During initial load, there's no known default value
+ int defaultIntegerValue = Integer.MIN_VALUE;
+ if (defaultReturnValue != null) {
+ defaultIntegerValue = Integer.parseInt(defaultReturnValue);
+ }
+
+ // When the list preference asks us to read a string, instead read an
+ // integer.
+ int value = getPersistedInt(defaultIntegerValue);
+ return Integer.toString(value);
+ }
+
+ @Override
+ protected boolean persistString(String value) {
+ // When asked to save a string, instead save an integer
+ return persistInt(Integer.parseInt(value));
+ }
+
+ private void verifyEntryValues(CharSequence[] oldValues) {
+ CharSequence[] entryValues = getEntryValues();
+ if (entryValues == null) {
+ return;
+ }
+
+ for (CharSequence entryValue : entryValues) {
+ try {
+ Integer.parseInt(entryValue.toString());
+ } catch (NumberFormatException nfe) {
+ super.setEntryValues(oldValues);
+ throw nfe;
+ }
+ }
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyEditor.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyEditor.java
new file mode 100644
index 000000000..c7bd1c987
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyEditor.java
@@ -0,0 +1,377 @@
+/*
+ * Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
+ *
+ * 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.widget;
+
+import android.annotation.TargetApi;
+import android.app.DatePickerDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.text.format.DateUtils;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.DatePicker;
+import android.widget.LinearLayout;
+import android.widget.TableLayout;
+import android.widget.TableRow;
+import android.widget.TextView;
+
+import com.beardedhen.androidbootstrap.BootstrapButton;
+
+import org.spongycastle.bcpg.sig.KeyFlags;
+import org.spongycastle.openpgp.PGPPublicKey;
+import org.spongycastle.openpgp.PGPSecretKey;
+
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
+import org.sufficientlysecure.keychain.util.Choice;
+
+import java.text.DateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.TimeZone;
+import java.util.Vector;
+
+public class KeyEditor extends LinearLayout implements Editor, OnClickListener {
+ private PGPSecretKey mKey;
+
+ private EditorListener mEditorListener = null;
+
+ private boolean mIsMasterKey;
+ BootstrapButton mDeleteButton;
+ TextView mAlgorithm;
+ TextView mKeyId;
+ TextView mCreationDate;
+ BootstrapButton mExpiryDateButton;
+ GregorianCalendar mCreatedDate;
+ GregorianCalendar mExpiryDate;
+ GregorianCalendar mOriginalExpiryDate = null;
+ CheckBox mChkCertify;
+ CheckBox mChkSign;
+ CheckBox mChkEncrypt;
+ CheckBox mChkAuthenticate;
+ int mUsage;
+ int mOriginalUsage;
+ boolean mIsNewKey;
+
+ private CheckBox.OnCheckedChangeListener mCheckChanged = new CheckBox.OnCheckedChangeListener()
+ {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked)
+ {
+ if (mEditorListener != null) {
+ mEditorListener.onEdited();
+ }
+ }
+ };
+
+
+ private int mDatePickerResultCount = 0;
+ private DatePickerDialog.OnDateSetListener mExpiryDateSetListener =
+ new DatePickerDialog.OnDateSetListener() {
+ public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
+ // Note: Ignore results after the first one - android sends multiples.
+ if (mDatePickerResultCount++ == 0) {
+ GregorianCalendar date = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
+ date.set(year, monthOfYear, dayOfMonth);
+ if (mOriginalExpiryDate != null) {
+ long numDays = (date.getTimeInMillis() / 86400000) -
+ (mOriginalExpiryDate.getTimeInMillis() / 86400000);
+ if (numDays == 0) {
+ setExpiryDate(mOriginalExpiryDate);
+ } else {
+ setExpiryDate(date);
+ }
+ } else {
+ setExpiryDate(date);
+ }
+ if (mEditorListener != null) {
+ mEditorListener.onEdited();
+ }
+ }
+ }
+ };
+
+ public KeyEditor(Context context) {
+ super(context);
+ }
+
+ public KeyEditor(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ setDrawingCacheEnabled(true);
+ setAlwaysDrawnWithCacheEnabled(true);
+
+ mAlgorithm = (TextView) findViewById(R.id.algorithm);
+ mKeyId = (TextView) findViewById(R.id.keyId);
+ mCreationDate = (TextView) findViewById(R.id.creation);
+ mExpiryDateButton = (BootstrapButton) findViewById(R.id.expiry);
+
+ mDeleteButton = (BootstrapButton) findViewById(R.id.delete);
+ mDeleteButton.setOnClickListener(this);
+ mChkCertify = (CheckBox) findViewById(R.id.chkCertify);
+ mChkCertify.setOnCheckedChangeListener(mCheckChanged);
+ mChkSign = (CheckBox) findViewById(R.id.chkSign);
+ mChkSign.setOnCheckedChangeListener(mCheckChanged);
+ mChkEncrypt = (CheckBox) findViewById(R.id.chkEncrypt);
+ mChkEncrypt.setOnCheckedChangeListener(mCheckChanged);
+ mChkAuthenticate = (CheckBox) findViewById(R.id.chkAuthenticate);
+ mChkAuthenticate.setOnCheckedChangeListener(mCheckChanged);
+
+ setExpiryDate(null);
+
+ mExpiryDateButton.setOnClickListener(new OnClickListener() {
+ @TargetApi(11)
+ public void onClick(View v) {
+ GregorianCalendar date = mExpiryDate;
+ if (date == null) {
+ date = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
+ }
+ /*
+ * Using custom DatePickerDialog which overrides the setTitle because
+ * the DatePickerDialog title is buggy (unix warparound bug).
+ * See: https://code.google.com/p/android/issues/detail?id=49066
+ */
+ DatePickerDialog dialog = new ExpiryDatePickerDialog(getContext(),
+ mExpiryDateSetListener, date.get(Calendar.YEAR), date.get(Calendar.MONTH),
+ date.get(Calendar.DAY_OF_MONTH));
+ mDatePickerResultCount = 0;
+ dialog.setCancelable(true);
+ dialog.setButton(Dialog.BUTTON_NEGATIVE,
+ getContext().getString(R.string.btn_no_date),
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ // Note: Ignore results after the first one - android sends multiples.
+ if (mDatePickerResultCount++ == 0) {
+ setExpiryDate(null);
+ if (mEditorListener != null) {
+ mEditorListener.onEdited();
+ }
+ }
+ }
+ });
+
+ // setCalendarViewShown() is supported from API 11 onwards.
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) {
+ // Hide calendarView in tablets because of the unix warparound bug.
+ dialog.getDatePicker().setCalendarViewShown(false);
+ }
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) {
+ if (dialog != null && mCreatedDate != null) {
+ dialog.getDatePicker()
+ .setMinDate(
+ mCreatedDate.getTime().getTime() + DateUtils.DAY_IN_MILLIS);
+ } else {
+ //When created date isn't available
+ dialog.getDatePicker().setMinDate(date.getTime().getTime() + DateUtils.DAY_IN_MILLIS);
+ }
+ }
+
+ dialog.show();
+ }
+ });
+
+ super.onFinishInflate();
+ }
+
+ public void setCanBeEdited(boolean canBeEdited) {
+ if (!canBeEdited) {
+ mDeleteButton.setVisibility(View.INVISIBLE);
+ mExpiryDateButton.setEnabled(false);
+ mChkSign.setEnabled(false); //certify is always disabled
+ mChkEncrypt.setEnabled(false);
+ mChkAuthenticate.setEnabled(false);
+ }
+ }
+
+ public void setValue(PGPSecretKey key, boolean isMasterKey, int usage, boolean isNewKey) {
+ mKey = key;
+
+ mIsMasterKey = isMasterKey;
+ if (mIsMasterKey) {
+ mDeleteButton.setVisibility(View.INVISIBLE);
+ }
+
+ mAlgorithm.setText(PgpKeyHelper.getAlgorithmInfo(key));
+ String keyIdStr = PgpKeyHelper.convertKeyIdToHex(key.getKeyID());
+ mKeyId.setText(keyIdStr);
+
+ Vector<Choice> choices = new Vector<Choice>();
+ boolean isElGamalKey = (key.getPublicKey().getAlgorithm() == PGPPublicKey.ELGAMAL_ENCRYPT);
+ boolean isDSAKey = (key.getPublicKey().getAlgorithm() == PGPPublicKey.DSA);
+ if (isElGamalKey) {
+ mChkSign.setVisibility(View.INVISIBLE);
+ TableLayout table = (TableLayout) findViewById(R.id.table_keylayout);
+ TableRow row = (TableRow) findViewById(R.id.row_sign);
+ table.removeView(row);
+ }
+ if (isDSAKey) {
+ mChkEncrypt.setVisibility(View.INVISIBLE);
+ TableLayout table = (TableLayout) findViewById(R.id.table_keylayout);
+ TableRow row = (TableRow) findViewById(R.id.row_encrypt);
+ table.removeView(row);
+ }
+ if (!mIsMasterKey) {
+ mChkCertify.setVisibility(View.INVISIBLE);
+ TableLayout table = (TableLayout) findViewById(R.id.table_keylayout);
+ TableRow row = (TableRow) findViewById(R.id.row_certify);
+ table.removeView(row);
+ } else {
+ TextView mLabelUsage2 = (TextView) findViewById(R.id.label_usage2);
+ mLabelUsage2.setVisibility(View.INVISIBLE);
+ }
+
+ int selectId = 0;
+ mIsNewKey = isNewKey;
+ if (isNewKey) {
+ mUsage = usage;
+ mChkCertify.setChecked((usage & KeyFlags.CERTIFY_OTHER) == KeyFlags.CERTIFY_OTHER);
+ mChkSign.setChecked((usage & KeyFlags.SIGN_DATA) == KeyFlags.SIGN_DATA);
+ mChkEncrypt.setChecked(((usage & KeyFlags.ENCRYPT_COMMS) == KeyFlags.ENCRYPT_COMMS) ||
+ ((usage & KeyFlags.ENCRYPT_STORAGE) == KeyFlags.ENCRYPT_STORAGE));
+ mChkAuthenticate.setChecked((usage & KeyFlags.AUTHENTICATION) == KeyFlags.AUTHENTICATION);
+ } else {
+ mUsage = PgpKeyHelper.getKeyUsage(key);
+ mOriginalUsage = mUsage;
+ if (key.isMasterKey()) {
+ mChkCertify.setChecked(PgpKeyHelper.isCertificationKey(key));
+ }
+ mChkSign.setChecked(PgpKeyHelper.isSigningKey(key));
+ mChkEncrypt.setChecked(PgpKeyHelper.isEncryptionKey(key));
+ mChkAuthenticate.setChecked(PgpKeyHelper.isAuthenticationKey(key));
+ }
+
+ GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
+ cal.setTime(PgpKeyHelper.getCreationDate(key));
+ setCreatedDate(cal);
+ cal = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
+ Date expiryDate = PgpKeyHelper.getExpiryDate(key);
+ if (expiryDate == null) {
+ setExpiryDate(null);
+ } else {
+ cal.setTime(PgpKeyHelper.getExpiryDate(key));
+ setExpiryDate(cal);
+ mOriginalExpiryDate = cal;
+ }
+
+ }
+
+ public PGPSecretKey getValue() {
+ return mKey;
+ }
+
+ public void onClick(View v) {
+ final ViewGroup parent = (ViewGroup) getParent();
+ if (v == mDeleteButton) {
+ parent.removeView(this);
+ if (mEditorListener != null) {
+ mEditorListener.onDeleted(this, mIsNewKey);
+ }
+ }
+ }
+
+ public void setEditorListener(EditorListener listener) {
+ mEditorListener = listener;
+ }
+
+ private void setCreatedDate(GregorianCalendar date) {
+ mCreatedDate = date;
+ if (date == null) {
+ mCreationDate.setText(getContext().getString(R.string.none));
+ } else {
+ mCreationDate.setText(DateFormat.getDateInstance().format(date.getTime()));
+ }
+ }
+
+ private void setExpiryDate(GregorianCalendar date) {
+ mExpiryDate = date;
+ if (date == null) {
+ mExpiryDateButton.setText(getContext().getString(R.string.none));
+ } else {
+ mExpiryDateButton.setText(DateFormat.getDateInstance().format(date.getTime()));
+ }
+ }
+
+ public GregorianCalendar getExpiryDate() {
+ return mExpiryDate;
+ }
+
+ public int getUsage() {
+ mUsage = (mUsage & ~KeyFlags.CERTIFY_OTHER) |
+ (mChkCertify.isChecked() ? KeyFlags.CERTIFY_OTHER : 0);
+ mUsage = (mUsage & ~KeyFlags.SIGN_DATA) |
+ (mChkSign.isChecked() ? KeyFlags.SIGN_DATA : 0);
+ mUsage = (mUsage & ~KeyFlags.ENCRYPT_COMMS) |
+ (mChkEncrypt.isChecked() ? KeyFlags.ENCRYPT_COMMS : 0);
+ mUsage = (mUsage & ~KeyFlags.ENCRYPT_STORAGE) |
+ (mChkEncrypt.isChecked() ? KeyFlags.ENCRYPT_STORAGE : 0);
+ mUsage = (mUsage & ~KeyFlags.AUTHENTICATION) |
+ (mChkAuthenticate.isChecked() ? KeyFlags.AUTHENTICATION : 0);
+
+ return mUsage;
+ }
+
+ public boolean needsSaving() {
+ if (mIsNewKey) {
+ return true;
+ }
+
+ boolean retval = (getUsage() != mOriginalUsage);
+
+ boolean dateChanged;
+ boolean mOEDNull = (mOriginalExpiryDate == null);
+ boolean mEDNull = (mExpiryDate == null);
+ if (mOEDNull != mEDNull) {
+ dateChanged = true;
+ } else {
+ if (mOEDNull) {
+ //both null, no change
+ dateChanged = false;
+ } else {
+ dateChanged = ((mExpiryDate.compareTo(mOriginalExpiryDate)) != 0);
+ }
+ }
+ retval |= dateChanged;
+
+ return retval;
+ }
+
+ public boolean getIsNewKey() {
+ return mIsNewKey;
+ }
+}
+
+class ExpiryDatePickerDialog extends DatePickerDialog {
+
+ public ExpiryDatePickerDialog(Context context, OnDateSetListener callBack,
+ int year, int monthOfYear, int dayOfMonth) {
+ super(context, callBack, year, monthOfYear, dayOfMonth);
+ }
+
+ //Set permanent title.
+ public void setTitle(CharSequence title) {
+ super.setTitle(getContext().getString(R.string.expiry_date_dialog_title));
+ }
+}
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
new file mode 100644
index 000000000..171763672
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyServerEditor.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
+ *
+ * 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.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.LinearLayout;
+import android.widget.TextView;
+import com.beardedhen.androidbootstrap.BootstrapButton;
+import org.sufficientlysecure.keychain.R;
+
+public class KeyServerEditor extends LinearLayout implements Editor, OnClickListener {
+ private EditorListener mEditorListener = null;
+
+ BootstrapButton 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 = (BootstrapButton) 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/SectionView.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SectionView.java
new file mode 100644
index 000000000..fb59cd3b7
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SectionView.java
@@ -0,0 +1,429 @@
+/*
+ * Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
+ *
+ * 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.widget;
+
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Message;
+import android.os.Messenger;
+import android.support.v7.app.ActionBarActivity;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import com.beardedhen.androidbootstrap.BootstrapButton;
+
+import org.spongycastle.openpgp.PGPKeyFlags;
+import org.spongycastle.openpgp.PGPSecretKey;
+
+import org.sufficientlysecure.keychain.Id;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.pgp.PgpConversionHelper;
+import org.sufficientlysecure.keychain.service.KeychainIntentService;
+import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
+import org.sufficientlysecure.keychain.service.PassphraseCacheService;
+import org.sufficientlysecure.keychain.ui.dialog.CreateKeyDialogFragment;
+import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
+import org.sufficientlysecure.keychain.ui.widget.Editor.EditorListener;
+import org.sufficientlysecure.keychain.util.Choice;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Vector;
+
+public class SectionView extends LinearLayout implements OnClickListener, EditorListener, Editor {
+ private LayoutInflater mInflater;
+ private BootstrapButton mPlusButton;
+ private ViewGroup mEditors;
+ private TextView mTitle;
+ private int mType = 0;
+ private EditorListener mEditorListener = null;
+
+ private Choice mNewKeyAlgorithmChoice;
+ private int mNewKeySize;
+ private boolean mOldItemDeleted = false;
+ private ArrayList<String> mDeletedIDs = new ArrayList<String>();
+ private ArrayList<PGPSecretKey> mDeletedKeys = new ArrayList<PGPSecretKey>();
+ private boolean mCanBeEdited = true;
+
+ private ActionBarActivity mActivity;
+
+ private ProgressDialogFragment mGeneratingDialog;
+
+ public void setEditorListener(EditorListener listener) {
+ mEditorListener = listener;
+ }
+
+ public SectionView(Context context) {
+ super(context);
+ mActivity = (ActionBarActivity) context;
+ }
+
+ public SectionView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mActivity = (ActionBarActivity) context;
+ }
+
+ public ViewGroup getEditors() {
+ return mEditors;
+ }
+
+ public void setType(int type) {
+ mType = type;
+ switch (type) {
+ case Id.type.user_id: {
+ mTitle.setText(R.string.section_user_ids);
+ break;
+ }
+
+ case Id.type.key: {
+ mTitle.setText(R.string.section_keys);
+ break;
+ }
+
+ default: {
+ break;
+ }
+ }
+ }
+
+ public void setCanBeEdited(boolean canBeEdited) {
+ mCanBeEdited = canBeEdited;
+ if (!mCanBeEdited) {
+ mPlusButton.setVisibility(View.INVISIBLE);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onFinishInflate() {
+ mInflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+ setDrawingCacheEnabled(true);
+ setAlwaysDrawnWithCacheEnabled(true);
+
+ mPlusButton = (BootstrapButton) findViewById(R.id.plusbutton);
+ mPlusButton.setOnClickListener(this);
+
+ mEditors = (ViewGroup) findViewById(R.id.editors);
+ mTitle = (TextView) findViewById(R.id.title);
+
+ updateEditorsVisible();
+ super.onFinishInflate();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void onDeleted(Editor editor, boolean wasNewItem) {
+ mOldItemDeleted |= !wasNewItem;
+ if (mOldItemDeleted) {
+ if (mType == Id.type.user_id) {
+ mDeletedIDs.add(((UserIdEditor) editor).getOriginalID());
+ } else if (mType == Id.type.key) {
+ mDeletedKeys.add(((KeyEditor) editor).getValue());
+ }
+ }
+ this.updateEditorsVisible();
+ if (mEditorListener != null) {
+ mEditorListener.onEdited();
+ }
+ }
+
+ @Override
+ public void onEdited() {
+ if (mEditorListener != null) {
+ mEditorListener.onEdited();
+ }
+ }
+
+ protected void updateEditorsVisible() {
+ final boolean hasChildren = mEditors.getChildCount() > 0;
+ mEditors.setVisibility(hasChildren ? View.VISIBLE : View.GONE);
+ }
+
+ public boolean needsSaving() {
+ //check each view for needs saving, take account of deleted items
+ boolean ret = mOldItemDeleted;
+ for (int i = 0; i < mEditors.getChildCount(); ++i) {
+ Editor editor = (Editor) mEditors.getChildAt(i);
+ ret |= editor.needsSaving();
+ if (mType == Id.type.user_id) {
+ ret |= ((UserIdEditor) editor).primarySwapped();
+ }
+ }
+ return ret;
+ }
+
+ public boolean primaryChanged() {
+ boolean ret = false;
+ for (int i = 0; i < mEditors.getChildCount(); ++i) {
+ Editor editor = (Editor) mEditors.getChildAt(i);
+ if (mType == Id.type.user_id) {
+ ret |= ((UserIdEditor) editor).primarySwapped();
+ }
+ }
+ return ret;
+ }
+
+ public String getOriginalPrimaryID() {
+ //NB: this will have to change when we change how Primary IDs are chosen, and so we need to be
+ // careful about where Master key capabilities are stored... multiple primaries and
+ // revoked ones make this harder than the simple case we are continuing to assume here
+ for (int i = 0; i < mEditors.getChildCount(); ++i) {
+ Editor editor = (Editor) mEditors.getChildAt(i);
+ if (mType == Id.type.user_id) {
+ if (((UserIdEditor) editor).getIsOriginallyMainUserID()) {
+ return ((UserIdEditor) editor).getOriginalID();
+ }
+ }
+ }
+ return null;
+ }
+
+ public ArrayList<String> getOriginalIDs() {
+ ArrayList<String> orig = new ArrayList<String>();
+ if (mType == Id.type.user_id) {
+ for (int i = 0; i < mEditors.getChildCount(); ++i) {
+ UserIdEditor editor = (UserIdEditor) mEditors.getChildAt(i);
+ if (editor.isMainUserId()) {
+ orig.add(0, editor.getOriginalID());
+ } else {
+ orig.add(editor.getOriginalID());
+ }
+ }
+ return orig;
+ } else {
+ return null;
+ }
+ }
+
+ public ArrayList<String> getDeletedIDs() {
+ return mDeletedIDs;
+ }
+
+ public ArrayList<PGPSecretKey> getDeletedKeys() {
+ return mDeletedKeys;
+ }
+
+ public List<Boolean> getNeedsSavingArray() {
+ ArrayList<Boolean> mList = new ArrayList<Boolean>();
+ for (int i = 0; i < mEditors.getChildCount(); ++i) {
+ Editor editor = (Editor) mEditors.getChildAt(i);
+ mList.add(editor.needsSaving());
+ }
+ return mList;
+ }
+
+ public List<Boolean> getNewIDFlags() {
+ ArrayList<Boolean> mList = new ArrayList<Boolean>();
+ for (int i = 0; i < mEditors.getChildCount(); ++i) {
+ UserIdEditor editor = (UserIdEditor) mEditors.getChildAt(i);
+ if (editor.isMainUserId()) {
+ mList.add(0, editor.getIsNewID());
+ } else {
+ mList.add(editor.getIsNewID());
+ }
+ }
+ return mList;
+ }
+
+ public List<Boolean> getNewKeysArray() {
+ ArrayList<Boolean> mList = new ArrayList<Boolean>();
+ if (mType == Id.type.key) {
+ for (int i = 0; i < mEditors.getChildCount(); ++i) {
+ KeyEditor editor = (KeyEditor) mEditors.getChildAt(i);
+ mList.add(editor.getIsNewKey());
+ }
+ }
+ return mList;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void onClick(View v) {
+ if (mCanBeEdited) {
+ switch (mType) {
+ case Id.type.user_id: {
+ UserIdEditor view = (UserIdEditor) mInflater.inflate(
+ R.layout.edit_key_user_id_item, mEditors, false);
+ view.setEditorListener(this);
+ view.setValue("", mEditors.getChildCount() == 0, true);
+ mEditors.addView(view);
+ if (mEditorListener != null) {
+ mEditorListener.onEdited();
+ }
+ break;
+ }
+
+ case Id.type.key: {
+ CreateKeyDialogFragment mCreateKeyDialogFragment =
+ CreateKeyDialogFragment.newInstance(mEditors.getChildCount());
+ mCreateKeyDialogFragment
+ .setOnAlgorithmSelectedListener(
+ new CreateKeyDialogFragment.OnAlgorithmSelectedListener() {
+ @Override
+ public void onAlgorithmSelected(Choice algorithmChoice, int keySize) {
+ mNewKeyAlgorithmChoice = algorithmChoice;
+ mNewKeySize = keySize;
+ createKey();
+ }
+ });
+ mCreateKeyDialogFragment.show(mActivity.getSupportFragmentManager(), "createKeyDialog");
+ break;
+ }
+
+ default: {
+ break;
+ }
+ }
+ this.updateEditorsVisible();
+ }
+ }
+
+ public void setUserIds(Vector<String> list) {
+ if (mType != Id.type.user_id) {
+ return;
+ }
+
+ mEditors.removeAllViews();
+ for (String userId : list) {
+ UserIdEditor view = (UserIdEditor) mInflater.inflate(R.layout.edit_key_user_id_item,
+ mEditors, false);
+ view.setEditorListener(this);
+ view.setValue(userId, mEditors.getChildCount() == 0, false);
+ view.setCanBeEdited(mCanBeEdited);
+ mEditors.addView(view);
+ }
+
+ this.updateEditorsVisible();
+ }
+
+ public void setKeys(Vector<PGPSecretKey> list, Vector<Integer> usages, boolean newKeys) {
+ if (mType != Id.type.key) {
+ return;
+ }
+
+ mEditors.removeAllViews();
+
+ // go through all keys and set view based on them
+ for (int i = 0; i < list.size(); i++) {
+ KeyEditor view = (KeyEditor) mInflater.inflate(R.layout.edit_key_key_item, mEditors,
+ false);
+ view.setEditorListener(this);
+ boolean isMasterKey = (mEditors.getChildCount() == 0);
+ view.setValue(list.get(i), isMasterKey, usages.get(i), newKeys);
+ view.setCanBeEdited(mCanBeEdited);
+ mEditors.addView(view);
+ }
+
+ this.updateEditorsVisible();
+ }
+
+ private void createKey() {
+ // Send all information needed to service to edit key in other thread
+ final Intent intent = new Intent(mActivity, KeychainIntentService.class);
+
+ intent.setAction(KeychainIntentService.ACTION_GENERATE_KEY);
+
+ // fill values for this action
+ Bundle data = new Bundle();
+ Boolean isMasterKey;
+
+ String passphrase;
+ if (mEditors.getChildCount() > 0) {
+ PGPSecretKey masterKey = ((KeyEditor) mEditors.getChildAt(0)).getValue();
+ passphrase = PassphraseCacheService
+ .getCachedPassphrase(mActivity, masterKey.getKeyID());
+ isMasterKey = false;
+ } else {
+ passphrase = "";
+ isMasterKey = true;
+ }
+ data.putBoolean(KeychainIntentService.GENERATE_KEY_MASTER_KEY, isMasterKey);
+ data.putString(KeychainIntentService.GENERATE_KEY_SYMMETRIC_PASSPHRASE, passphrase);
+ data.putInt(KeychainIntentService.GENERATE_KEY_ALGORITHM, mNewKeyAlgorithmChoice.getId());
+ data.putInt(KeychainIntentService.GENERATE_KEY_KEY_SIZE, mNewKeySize);
+
+ intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
+
+ // show progress dialog
+ mGeneratingDialog = ProgressDialogFragment.newInstance(
+ getResources().getQuantityString(R.plurals.progress_generating, 1),
+ ProgressDialog.STYLE_SPINNER,
+ true,
+ new DialogInterface.OnCancelListener() {
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ mActivity.stopService(intent);
+ }
+ });
+
+ // Message is received after generating is done in KeychainIntentService
+ KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(mActivity,
+ mGeneratingDialog) {
+ public void handleMessage(Message message) {
+ // handle messages by standard KeychainIntentServiceHandler first
+ super.handleMessage(message);
+
+ if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
+ // get new key from data bundle returned from service
+ Bundle data = message.getData();
+ PGPSecretKey newKey = (PGPSecretKey) PgpConversionHelper
+ .BytesToPGPSecretKey(data
+ .getByteArray(KeychainIntentService.RESULT_NEW_KEY));
+ addGeneratedKeyToView(newKey);
+ }
+ }
+ };
+
+ // Create a new Messenger for the communication back
+ Messenger messenger = new Messenger(saveHandler);
+ intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
+
+ mGeneratingDialog.show(mActivity.getSupportFragmentManager(), "dialog");
+
+ // start service with intent
+ mActivity.startService(intent);
+ }
+
+ private void addGeneratedKeyToView(PGPSecretKey newKey) {
+ // add view with new key
+ KeyEditor view = (KeyEditor) mInflater.inflate(R.layout.edit_key_key_item,
+ mEditors, false);
+ view.setEditorListener(SectionView.this);
+ int usage = 0;
+ if (mEditors.getChildCount() == 0) {
+ usage = PGPKeyFlags.CAN_CERTIFY;
+ }
+ view.setValue(newKey, newKey.isMasterKey(), usage, true);
+ mEditors.addView(view);
+ SectionView.this.updateEditorsVisible();
+ if (mEditorListener != null) {
+ mEditorListener.onEdited();
+ }
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/UnderlineTextView.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/UnderlineTextView.java
new file mode 100644
index 000000000..937a48e48
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/UnderlineTextView.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2013 Eric Frohnhoefer
+ *
+ * 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.widget;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.widget.TextView;
+
+/**
+ * Copied from StickyListHeaders lib example
+ *
+ * @author Eric Frohnhoefer
+ */
+public class UnderlineTextView extends TextView {
+ private final Paint mPaint = new Paint();
+ private int mUnderlineHeight = 0;
+
+ public UnderlineTextView(Context context) {
+ this(context, null);
+ }
+
+ public UnderlineTextView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public UnderlineTextView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ init(context, attrs);
+ }
+
+ private void init(Context context, AttributeSet attrs) {
+ Resources r = getResources();
+ mUnderlineHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2,
+ r.getDisplayMetrics());
+ }
+
+ @Override
+ public void setPadding(int left, int top, int right, int bottom) {
+ super.setPadding(left, top, right, bottom + mUnderlineHeight);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ // Draw the underline the same color as the text
+ mPaint.setColor(getTextColors().getDefaultColor());
+ canvas.drawRect(0, getHeight() - mUnderlineHeight, getWidth(), getHeight(), mPaint);
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/UserIdEditor.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/UserIdEditor.java
new file mode 100644
index 000000000..2253872d5
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/UserIdEditor.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
+ *
+ * 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.widget;
+
+import android.content.Context;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.AttributeSet;
+import android.util.Patterns;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.AutoCompleteTextView;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.RadioButton;
+import com.beardedhen.androidbootstrap.BootstrapButton;
+
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.helper.ContactHelper;
+import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
+
+import java.util.regex.Matcher;
+
+public class UserIdEditor extends LinearLayout implements Editor, OnClickListener {
+ private EditorListener mEditorListener = null;
+
+ private BootstrapButton mDeleteButton;
+ private RadioButton mIsMainUserId;
+ private String mOriginalID;
+ private EditText mName;
+ private String mOriginalName;
+ private AutoCompleteTextView mEmail;
+ private String mOriginalEmail;
+ private EditText mComment;
+ private String mOriginalComment;
+ private boolean mOriginallyMainUserID;
+ private boolean mIsNewId;
+
+ public void setCanBeEdited(boolean canBeEdited) {
+ if (!canBeEdited) {
+ mDeleteButton.setVisibility(View.INVISIBLE);
+ mName.setEnabled(false);
+ mIsMainUserId.setEnabled(false);
+ mEmail.setEnabled(false);
+ mComment.setEnabled(false);
+ }
+ }
+
+ public static class InvalidEmailException extends Exception {
+ static final long serialVersionUID = 0xf812773345L;
+
+ public InvalidEmailException(String message) {
+ super(message);
+ }
+ }
+
+ public UserIdEditor(Context context) {
+ super(context);
+ }
+
+ public UserIdEditor(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ TextWatcher mTextWatcher = new TextWatcher() {
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ }
+
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ if (mEditorListener != null) {
+ mEditorListener.onEdited();
+ }
+ }
+ };
+
+ @Override
+ protected void onFinishInflate() {
+ setDrawingCacheEnabled(true);
+ setAlwaysDrawnWithCacheEnabled(true);
+
+ mDeleteButton = (BootstrapButton) findViewById(R.id.delete);
+ mDeleteButton.setOnClickListener(this);
+ mIsMainUserId = (RadioButton) findViewById(R.id.isMainUserId);
+ mIsMainUserId.setOnClickListener(this);
+
+ mName = (EditText) findViewById(R.id.name);
+ mName.addTextChangedListener(mTextWatcher);
+ mEmail = (AutoCompleteTextView) findViewById(R.id.email);
+ mComment = (EditText) findViewById(R.id.comment);
+ mComment.addTextChangedListener(mTextWatcher);
+
+
+ mEmail.setThreshold(1); // Start working from first character
+ mEmail.setAdapter(
+ new ArrayAdapter<String>
+ (this.getContext(), android.R.layout.simple_dropdown_item_1line,
+ ContactHelper.getMailAccounts(getContext())
+ ));
+ mEmail.addTextChangedListener(new TextWatcher(){
+ @Override
+ public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) { }
+
+ @Override
+ public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) { }
+
+ @Override
+ public void afterTextChanged(Editable editable) {
+ String email = editable.toString();
+ if (email.length() > 0) {
+ Matcher emailMatcher = Patterns.EMAIL_ADDRESS.matcher(email);
+ if (emailMatcher.matches()) {
+ mEmail.setCompoundDrawablesWithIntrinsicBounds(0, 0,
+ android.R.drawable.presence_online, 0);
+ } else {
+ mEmail.setCompoundDrawablesWithIntrinsicBounds(0, 0,
+ android.R.drawable.presence_offline, 0);
+ }
+ } else {
+ // remove drawable if email is empty
+ mEmail.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
+ }
+ if (mEditorListener != null) {
+ mEditorListener.onEdited();
+ }
+ }
+ });
+
+ super.onFinishInflate();
+ }
+
+ public void setValue(String userId, boolean isMainID, boolean isNewId) {
+
+ mName.setText("");
+ mOriginalName = "";
+ mComment.setText("");
+ mOriginalComment = "";
+ mEmail.setText("");
+ mOriginalEmail = "";
+ mIsNewId = isNewId;
+ mOriginalID = userId;
+
+ String[] result = PgpKeyHelper.splitUserId(userId);
+ if (result[0] != null) {
+ mName.setText(result[0]);
+ mOriginalName = result[0];
+ }
+ if (result[1] != null) {
+ mEmail.setText(result[1]);
+ mOriginalEmail = result[1];
+ }
+ if (result[2] != null) {
+ mComment.setText(result[2]);
+ mOriginalComment = result[2];
+ }
+
+ mOriginallyMainUserID = isMainID;
+ setIsMainUserId(isMainID);
+ }
+
+ public String getValue() {
+ String name = ("" + mName.getText()).trim();
+ String email = ("" + mEmail.getText()).trim();
+ String comment = ("" + mComment.getText()).trim();
+
+ String userId = name;
+ if (comment.length() > 0) {
+ userId += " (" + comment + ")";
+ }
+ if (email.length() > 0) {
+ userId += " <" + email + ">";
+ }
+
+ if (userId.equals("")) {
+ // ok, empty one...
+ return userId;
+ }
+ //TODO: check gpg accepts an entirely empty ID packet. specs say this is allowed
+ return userId;
+ }
+
+ public void onClick(View v) {
+ final ViewGroup parent = (ViewGroup) getParent();
+ if (v == mDeleteButton) {
+ boolean wasMainUserId = mIsMainUserId.isChecked();
+ parent.removeView(this);
+ if (mEditorListener != null) {
+ mEditorListener.onDeleted(this, mIsNewId);
+ }
+ if (wasMainUserId && parent.getChildCount() > 0) {
+ UserIdEditor editor = (UserIdEditor) parent.getChildAt(0);
+ editor.setIsMainUserId(true);
+ }
+ } else if (v == mIsMainUserId) {
+ for (int i = 0; i < parent.getChildCount(); ++i) {
+ UserIdEditor editor = (UserIdEditor) parent.getChildAt(i);
+ if (editor == this) {
+ editor.setIsMainUserId(true);
+ } else {
+ editor.setIsMainUserId(false);
+ }
+ }
+ if (mEditorListener != null) {
+ mEditorListener.onEdited();
+ }
+ }
+ }
+
+ public void setIsMainUserId(boolean value) {
+ mIsMainUserId.setChecked(value);
+ }
+
+ public boolean isMainUserId() {
+ return mIsMainUserId.isChecked();
+ }
+
+ public void setEditorListener(EditorListener listener) {
+ mEditorListener = listener;
+ }
+
+ @Override
+ public boolean needsSaving() {
+ boolean retval = false; //(mOriginallyMainUserID != isMainUserId());
+ retval |= !(mOriginalName.equals(("" + mName.getText()).trim()));
+ retval |= !(mOriginalEmail.equals(("" + mEmail.getText()).trim()));
+ retval |= !(mOriginalComment.equals(("" + mComment.getText()).trim()));
+ retval |= mIsNewId;
+ return retval;
+ }
+
+ public boolean getIsOriginallyMainUserID() {
+ return mOriginallyMainUserID;
+ }
+
+ public boolean primarySwapped() {
+ return (mOriginallyMainUserID != isMainUserId());
+ }
+
+ public String getOriginalID() {
+ return mOriginalID;
+ }
+
+ public boolean getIsNewID() { return mIsNewId; }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/AlgorithmNames.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/AlgorithmNames.java
new file mode 100644
index 000000000..d2f4cc003
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/AlgorithmNames.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.util;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import org.spongycastle.bcpg.HashAlgorithmTags;
+import org.spongycastle.openpgp.PGPEncryptedData;
+import org.sufficientlysecure.keychain.Id;
+import org.sufficientlysecure.keychain.R;
+
+import java.util.HashMap;
+
+@SuppressLint("UseSparseArrays")
+public class AlgorithmNames {
+ Activity mActivity;
+
+ HashMap<Integer, String> mEncryptionNames = new HashMap<Integer, String>();
+ HashMap<Integer, String> mHashNames = new HashMap<Integer, String>();
+ HashMap<Integer, String> mCompressionNames = new HashMap<Integer, String>();
+
+ public AlgorithmNames(Activity context) {
+ super();
+ this.mActivity = context;
+
+ mEncryptionNames.put(PGPEncryptedData.AES_128, "AES-128");
+ mEncryptionNames.put(PGPEncryptedData.AES_192, "AES-192");
+ mEncryptionNames.put(PGPEncryptedData.AES_256, "AES-256");
+ mEncryptionNames.put(PGPEncryptedData.BLOWFISH, "Blowfish");
+ mEncryptionNames.put(PGPEncryptedData.TWOFISH, "Twofish");
+ mEncryptionNames.put(PGPEncryptedData.CAST5, "CAST5");
+ mEncryptionNames.put(PGPEncryptedData.DES, "DES");
+ mEncryptionNames.put(PGPEncryptedData.TRIPLE_DES, "Triple DES");
+ mEncryptionNames.put(PGPEncryptedData.IDEA, "IDEA");
+
+ mHashNames.put(HashAlgorithmTags.MD5, "MD5");
+ mHashNames.put(HashAlgorithmTags.RIPEMD160, "RIPEMD-160");
+ mHashNames.put(HashAlgorithmTags.SHA1, "SHA-1");
+ mHashNames.put(HashAlgorithmTags.SHA224, "SHA-224");
+ mHashNames.put(HashAlgorithmTags.SHA256, "SHA-256");
+ mHashNames.put(HashAlgorithmTags.SHA384, "SHA-384");
+ mHashNames.put(HashAlgorithmTags.SHA512, "SHA-512");
+
+ mCompressionNames.put(Id.choice.compression.none, mActivity.getString(R.string.choice_none)
+ + " (" + mActivity.getString(R.string.compression_fast) + ")");
+ mCompressionNames.put(Id.choice.compression.zip,
+ "ZIP (" + mActivity.getString(R.string.compression_fast) + ")");
+ mCompressionNames.put(Id.choice.compression.zlib,
+ "ZLIB (" + mActivity.getString(R.string.compression_fast) + ")");
+ mCompressionNames.put(Id.choice.compression.bzip2,
+ "BZIP2 (" + mActivity.getString(R.string.compression_very_slow) + ")");
+ }
+
+ public HashMap<Integer, String> getEncryptionNames() {
+ return mEncryptionNames;
+ }
+
+ public void setEncryptionNames(HashMap<Integer, String> encryptionNames) {
+ this.mEncryptionNames = encryptionNames;
+ }
+
+ public HashMap<Integer, String> getHashNames() {
+ return mHashNames;
+ }
+
+ public void setHashNames(HashMap<Integer, String> hashNames) {
+ this.mHashNames = hashNames;
+ }
+
+ public HashMap<Integer, String> getCompressionNames() {
+ return mCompressionNames;
+ }
+
+ public void setCompressionNames(HashMap<Integer, String> compressionNames) {
+ this.mCompressionNames = compressionNames;
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Choice.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Choice.java
new file mode 100644
index 000000000..1a6184d9c
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Choice.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.sufficientlysecure.keychain.util;
+
+public class Choice {
+ private String mName;
+ private int mId;
+
+ public Choice() {
+ mId = -1;
+ mName = "";
+ }
+
+ public Choice(int id, String name) {
+ mId = id;
+ mName = name;
+ }
+
+ public int getId() {
+ return mId;
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ @Override
+ public String toString() {
+ return mName;
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/HkpKeyServer.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/HkpKeyServer.java
new file mode 100644
index 000000000..5efc732e4
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/HkpKeyServer.java
@@ -0,0 +1,353 @@
+/*
+ * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2011 Thialfihar <thi@thialfihar.org>
+ * Copyright (C) 2011 Senecaso
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.sufficientlysecure.keychain.util;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.util.EntityUtils;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.pgp.PgpHelper;
+import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.HttpURLConnection;
+import java.net.InetAddress;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.GregorianCalendar;
+import java.util.List;
+import java.util.Locale;
+import java.util.TimeZone;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class HkpKeyServer extends KeyServer {
+ private static class HttpError extends Exception {
+ private static final long serialVersionUID = 1718783705229428893L;
+ private int mCode;
+ private String mData;
+
+ public HttpError(int code, String data) {
+ super("" + code + ": " + data);
+ mCode = code;
+ mData = data;
+ }
+
+ public int getCode() {
+ return mCode;
+ }
+
+ public String getData() {
+ return mData;
+ }
+ }
+
+ private String mHost;
+ private short mPort;
+
+ /**
+ * pub:%keyid%:%algo%:%keylen%:%creationdate%:%expirationdate%:%flags%
+ * <ul>
+ * <li>%<b>keyid</b>% = this is either the fingerprint or the key ID of the key.
+ * Either the 16-digit or 8-digit key IDs are acceptable, but obviously the fingerprint is best.
+ * </li>
+ * <li>%<b>algo</b>% = the algorithm number, (i.e. 1==RSA, 17==DSA, etc).
+ * See <a href="http://tools.ietf.org/html/rfc2440#section-9.1">RFC-2440</a></li>
+ * <li>%<b>keylen</b>% = the key length (i.e. 1024, 2048, 4096, etc.)</li>
+ * <li>%<b>creationdate</b>% = creation date of the key in standard
+ * <a href="http://tools.ietf.org/html/rfc2440#section-9.1">RFC-2440</a> form (i.e. number of
+ * seconds since 1/1/1970 UTC time)</li>
+ * <li>%<b>expirationdate</b>% = expiration date of the key in standard
+ * <a href="http://tools.ietf.org/html/rfc2440#section-9.1">RFC-2440</a> form (i.e. number of
+ * seconds since 1/1/1970 UTC time)</li>
+ * <li>%<b>flags</b>% = letter codes to indicate details of the key, if any. Flags may be in any
+ * order. The meaning of "disabled" is implementation-specific. Note that individual flags may
+ * be unimplemented, so the absence of a given flag does not necessarily mean the absence of the
+ * detail.
+ * <ul>
+ * <li>r == revoked</li>
+ * <li>d == disabled</li>
+ * <li>e == expired</li>
+ * </ul>
+ * </li>
+ * </ul>
+ *
+ * @see <a href="http://tools.ietf.org/html/draft-shaw-openpgp-hkp-00#section-5.2">
+ * 5.2. Machine Readable Indexes</a>
+ * in Internet-Draft OpenPGP HTTP Keyserver Protocol Document
+ */
+ public static final Pattern PUB_KEY_LINE = Pattern
+ .compile("pub:([0-9a-fA-F]+):([0-9]+):([0-9]+):([0-9]+):([0-9]*):([rde]*)[ \n\r]*" // pub line
+ + "(uid:(.*):([0-9]+):([0-9]*):([rde]*))+", // one or more uid lines
+ Pattern.CASE_INSENSITIVE);
+
+ /**
+ * uid:%escaped uid string%:%creationdate%:%expirationdate%:%flags%
+ * <ul>
+ * <li>%<b>escaped uid string</b>% = the user ID string, with HTTP %-escaping for anything that
+ * isn't 7-bit safe as well as for the ":" character. Any other characters may be escaped, as
+ * desired.</li>
+ * <li>%<b>creationdate</b>% = creation date of the key in standard
+ * <a href="http://tools.ietf.org/html/rfc2440#section-9.1">RFC-2440</a> form (i.e. number of
+ * seconds since 1/1/1970 UTC time)</li>
+ * <li>%<b>expirationdate</b>% = expiration date of the key in standard
+ * <a href="http://tools.ietf.org/html/rfc2440#section-9.1">RFC-2440</a> form (i.e. number of
+ * seconds since 1/1/1970 UTC time)</li>
+ * <li>%<b>flags</b>% = letter codes to indicate details of the key, if any. Flags may be in any
+ * order. The meaning of "disabled" is implementation-specific. Note that individual flags may
+ * be unimplemented, so the absence of a given flag does not necessarily mean the absence of
+ * the detail.
+ * <ul>
+ * <li>r == revoked</li>
+ * <li>d == disabled</li>
+ * <li>e == expired</li>
+ * </ul>
+ * </li>
+ * </ul>
+ */
+ public static final Pattern UID_LINE = Pattern
+ .compile("uid:(.*):([0-9]+):([0-9]*):([rde]*)",
+ Pattern.CASE_INSENSITIVE);
+
+ private static final short PORT_DEFAULT = 11371;
+
+ /**
+ * @param hostAndPort may be just
+ * "<code>hostname</code>" (eg. "<code>pool.sks-keyservers.net</code>"), then it will
+ * connect using {@link #PORT_DEFAULT}. However, port may be specified after colon
+ * ("<code>hostname:port</code>", eg. "<code>p80.pool.sks-keyservers.net:80</code>").
+ */
+ public HkpKeyServer(String hostAndPort) {
+ String host = hostAndPort;
+ short port = PORT_DEFAULT;
+ final int colonPosition = hostAndPort.lastIndexOf(':');
+ if (colonPosition > 0) {
+ host = hostAndPort.substring(0, colonPosition);
+ final String portStr = hostAndPort.substring(colonPosition + 1);
+ port = Short.decode(portStr);
+ }
+ mHost = host;
+ mPort = port;
+ }
+
+ public HkpKeyServer(String host, short port) {
+ mHost = host;
+ mPort = port;
+ }
+
+ private static String readAll(InputStream in, String encoding) throws IOException {
+ ByteArrayOutputStream raw = new ByteArrayOutputStream();
+
+ byte buffer[] = new byte[1 << 16];
+ int n = 0;
+ while ((n = in.read(buffer)) != -1) {
+ raw.write(buffer, 0, n);
+ }
+
+ if (encoding == null) {
+ encoding = "utf8";
+ }
+ return raw.toString(encoding);
+ }
+
+ private String query(String request) throws QueryException, HttpError {
+ InetAddress ips[];
+ try {
+ ips = InetAddress.getAllByName(mHost);
+ } catch (UnknownHostException e) {
+ throw new QueryException(e.toString());
+ }
+ for (int i = 0; i < ips.length; ++i) {
+ try {
+ String url = "http://" + ips[i].getHostAddress() + ":" + mPort + request;
+ Log.d(Constants.TAG, "hkp keyserver query: " + url);
+ URL realUrl = new URL(url);
+ HttpURLConnection conn = (HttpURLConnection) realUrl.openConnection();
+ conn.setConnectTimeout(5000);
+ conn.setReadTimeout(25000);
+ conn.connect();
+ int response = conn.getResponseCode();
+ if (response >= 200 && response < 300) {
+ return readAll(conn.getInputStream(), conn.getContentEncoding());
+ } else {
+ String data = readAll(conn.getErrorStream(), conn.getContentEncoding());
+ throw new HttpError(response, data);
+ }
+ } catch (MalformedURLException e) {
+ // nothing to do, try next IP
+ } catch (IOException e) {
+ // nothing to do, try next IP
+ }
+ }
+
+ throw new QueryException("querying server(s) for '" + mHost + "' failed");
+ }
+
+ @Override
+ public ArrayList<ImportKeysListEntry> search(String query) throws QueryException, TooManyResponses,
+ InsufficientQuery {
+ ArrayList<ImportKeysListEntry> results = new ArrayList<ImportKeysListEntry>();
+
+ if (query.length() < 3) {
+ throw new InsufficientQuery();
+ }
+
+ String encodedQuery;
+ try {
+ encodedQuery = URLEncoder.encode(query, "utf8");
+ } catch (UnsupportedEncodingException e) {
+ return null;
+ }
+ String request = "/pks/lookup?op=index&options=mr&search=" + encodedQuery;
+
+ String data;
+ try {
+ data = query(request);
+ } catch (HttpError e) {
+ if (e.getCode() == 404) {
+ return results;
+ } else {
+ if (e.getData().toLowerCase(Locale.US).contains("no keys found")) {
+ return results;
+ } else if (e.getData().toLowerCase(Locale.US).contains("too many")) {
+ throw new TooManyResponses();
+ } else if (e.getData().toLowerCase(Locale.US).contains("insufficient")) {
+ throw new InsufficientQuery();
+ }
+ }
+ throw new QueryException("querying server(s) for '" + mHost + "' failed");
+ }
+
+ final Matcher matcher = PUB_KEY_LINE.matcher(data);
+ while (matcher.find()) {
+ final ImportKeysListEntry entry = new ImportKeysListEntry();
+
+ entry.setBitStrength(Integer.parseInt(matcher.group(3)));
+
+ final int algorithmId = Integer.decode(matcher.group(2));
+ entry.setAlgorithm(ImportKeysListEntry.getAlgorithmFromId(algorithmId));
+
+ // group 1 contains the full fingerprint (v4) or the long key id if available
+ // see http://bit.ly/1d4bxbk and http://bit.ly/1gD1wwr
+ String fingerprintOrKeyId = matcher.group(1);
+ if (fingerprintOrKeyId.length() > 16) {
+ entry.setFingerPrintHex(fingerprintOrKeyId.toLowerCase(Locale.US));
+ entry.setKeyIdHex("0x" + fingerprintOrKeyId.substring(fingerprintOrKeyId.length()
+ - 16, fingerprintOrKeyId.length()));
+ } else {
+ // set key id only
+ entry.setKeyIdHex("0x" + fingerprintOrKeyId);
+ }
+
+ final long creationDate = Long.parseLong(matcher.group(4));
+ final GregorianCalendar tmpGreg = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
+ tmpGreg.setTimeInMillis(creationDate * 1000);
+ entry.setDate(tmpGreg.getTime());
+
+ entry.setRevoked(matcher.group(6).contains("r"));
+
+ ArrayList<String> userIds = new ArrayList<String>();
+ final String uidLines = matcher.group(7);
+ final Matcher uidMatcher = UID_LINE.matcher(uidLines);
+ while (uidMatcher.find()) {
+ String tmp = uidMatcher.group(1).trim();
+ if (tmp.contains("%")) {
+ try {
+ // converts Strings like "Universit%C3%A4t" to a proper encoding form "Universität".
+ tmp = (URLDecoder.decode(tmp, "UTF8"));
+ } catch (UnsupportedEncodingException ignored) {
+ // will never happen, because "UTF8" is supported
+ }
+ }
+ userIds.add(tmp);
+ }
+ entry.setUserIds(userIds);
+
+ results.add(entry);
+ }
+ return results;
+ }
+
+ @Override
+ public String get(String keyIdHex) throws QueryException {
+ HttpClient client = new DefaultHttpClient();
+ try {
+ String query = "http://" + mHost + ":" + mPort +
+ "/pks/lookup?op=get&options=mr&search=" + keyIdHex;
+ Log.d(Constants.TAG, "hkp keyserver get: " + query);
+ HttpGet get = new HttpGet(query);
+ HttpResponse response = client.execute(get);
+ if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
+ throw new QueryException("not found");
+ }
+
+ HttpEntity entity = response.getEntity();
+ InputStream is = entity.getContent();
+ String data = readAll(is, EntityUtils.getContentCharSet(entity));
+ Matcher matcher = PgpHelper.PGP_PUBLIC_KEY.matcher(data);
+ if (matcher.find()) {
+ return matcher.group(1);
+ }
+ } catch (IOException e) {
+ // nothing to do, better luck on the next keyserver
+ } finally {
+ client.getConnectionManager().shutdown();
+ }
+
+ return null;
+ }
+
+ @Override
+ public void add(String armoredKey) throws AddKeyException {
+ HttpClient client = new DefaultHttpClient();
+ try {
+ String query = "http://" + mHost + ":" + mPort + "/pks/add";
+ HttpPost post = new HttpPost(query);
+ Log.d(Constants.TAG, "hkp keyserver add: " + query);
+ List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(2);
+ nameValuePairs.add(new BasicNameValuePair("keytext", armoredKey));
+ post.setEntity(new UrlEncodedFormEntity(nameValuePairs));
+
+ HttpResponse response = client.execute(post);
+ if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
+ throw new AddKeyException();
+ }
+ } catch (IOException e) {
+ // nothing to do, better luck on the next keyserver
+ } finally {
+ client.getConnectionManager().shutdown();
+ }
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/InputData.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/InputData.java
new file mode 100644
index 000000000..28cfa11f2
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/InputData.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.sufficientlysecure.keychain.util;
+
+import java.io.InputStream;
+
+public class InputData {
+ private PositionAwareInputStream mInputStream;
+ private long mSize;
+
+ public InputData(InputStream inputStream, long size) {
+ mInputStream = new PositionAwareInputStream(inputStream);
+ mSize = size;
+ }
+
+ public InputStream getInputStream() {
+ return mInputStream;
+ }
+
+ public long getSize() {
+ return mSize;
+ }
+
+ public long getStreamPosition() {
+ return mInputStream.position();
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/IntentIntegratorSupportV4.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/IntentIntegratorSupportV4.java
new file mode 100644
index 000000000..ae87deb31
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/IntentIntegratorSupportV4.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2012 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.sufficientlysecure.keychain.util;
+
+import com.google.zxing.integration.android.IntentIntegrator;
+
+import android.content.Intent;
+import android.support.v4.app.Fragment;
+
+/**
+ * IntentIntegrator for the V4 Android compatibility package.
+ *
+ * @author Lachezar Dobrev
+ */
+public final class IntentIntegratorSupportV4 extends IntentIntegrator {
+
+ private final Fragment mFragment;
+
+ /**
+ * @param fragment Fragment to handle activity response.
+ */
+ public IntentIntegratorSupportV4(Fragment fragment) {
+ super(fragment.getActivity());
+ this.mFragment = fragment;
+ }
+
+ @Override
+ protected void startActivityForResult(Intent intent, int code) {
+ mFragment.startActivityForResult(intent, code);
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/IterableIterator.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/IterableIterator.java
new file mode 100644
index 000000000..3af674526
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/IterableIterator.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.sufficientlysecure.keychain.util;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+public class IterableIterator<T> implements Iterable<T> {
+ private Iterator<T> mIter;
+
+ public IterableIterator(Iterator<T> iter, boolean failsafe) {
+ mIter = iter;
+ if (failsafe && mIter == null) {
+ // is there a better way?
+ mIter = new ArrayList<T>().iterator();
+ }
+ }
+ public IterableIterator(Iterator<T> iter) {
+ this(iter, false);
+ }
+
+ public Iterator<T> iterator() {
+ return mIter;
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/KeyServer.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/KeyServer.java
new file mode 100644
index 000000000..7f70867a5
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/KeyServer.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2011 Thialfihar <thi@thialfihar.org>
+ * Copyright (C) 2011 Senecaso
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.sufficientlysecure.keychain.util;
+
+import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry;
+
+import java.util.List;
+
+public abstract class KeyServer {
+ public static class QueryException extends Exception {
+ private static final long serialVersionUID = 2703768928624654512L;
+
+ public QueryException(String message) {
+ super(message);
+ }
+ }
+
+ public static class TooManyResponses extends Exception {
+ private static final long serialVersionUID = 2703768928624654513L;
+ }
+
+ public static class InsufficientQuery extends Exception {
+ private static final long serialVersionUID = 2703768928624654514L;
+ }
+
+ public static class AddKeyException extends Exception {
+ private static final long serialVersionUID = -507574859137295530L;
+ }
+
+ abstract List<ImportKeysListEntry> search(String query) throws QueryException, TooManyResponses,
+ InsufficientQuery;
+
+ abstract String get(String keyIdHex) throws QueryException;
+
+ abstract void add(String armoredKey) throws AddKeyException;
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/KeychainServiceListener.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/KeychainServiceListener.java
new file mode 100644
index 000000000..b205bd556
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/KeychainServiceListener.java
@@ -0,0 +1,22 @@
+/*
+ * 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;
+
+public interface KeychainServiceListener {
+ boolean hasServiceStopped();
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Log.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Log.java
new file mode 100644
index 000000000..f58f1757a
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Log.java
@@ -0,0 +1,83 @@
+/*
+ * 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.util;
+
+import org.sufficientlysecure.keychain.Constants;
+
+/**
+ * Wraps Android Logging to enable or disable debug output using Constants
+ */
+public final class Log {
+
+ public static void v(String tag, String msg) {
+ if (Constants.DEBUG) {
+ android.util.Log.v(tag, msg);
+ }
+ }
+
+ public static void v(String tag, String msg, Throwable tr) {
+ if (Constants.DEBUG) {
+ android.util.Log.v(tag, msg, tr);
+ }
+ }
+
+ public static void d(String tag, String msg) {
+ if (Constants.DEBUG) {
+ android.util.Log.d(tag, msg);
+ }
+ }
+
+ public static void d(String tag, String msg, Throwable tr) {
+ if (Constants.DEBUG) {
+ android.util.Log.d(tag, msg, tr);
+ }
+ }
+
+ public static void i(String tag, String msg) {
+ if (Constants.DEBUG) {
+ android.util.Log.i(tag, msg);
+ }
+ }
+
+ public static void i(String tag, String msg, Throwable tr) {
+ if (Constants.DEBUG) {
+ android.util.Log.i(tag, msg, tr);
+ }
+ }
+
+ public static void w(String tag, String msg) {
+ android.util.Log.w(tag, msg);
+ }
+
+ public static void w(String tag, String msg, Throwable tr) {
+ android.util.Log.w(tag, msg, tr);
+ }
+
+ public static void w(String tag, Throwable tr) {
+ android.util.Log.w(tag, tr);
+ }
+
+ public static void e(String tag, String msg) {
+ android.util.Log.e(tag, msg);
+ }
+
+ public static void e(String tag, String msg, Throwable tr) {
+ android.util.Log.e(tag, msg, tr);
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/PRNGFixes.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/PRNGFixes.java
new file mode 100644
index 000000000..2d8fbcd81
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/PRNGFixes.java
@@ -0,0 +1,352 @@
+/*
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will Google be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, as long as the origin is not misrepresented.
+ */
+
+package org.sufficientlysecure.keychain.util;
+
+import android.os.Build;
+import android.os.Process;
+import android.util.Log;
+
+import java.io.*;
+import java.security.*;
+
+/**
+ * Fixes for the output of the default PRNG having low entropy.
+ * <p/>
+ * The fixes need to be applied via {@link #apply()} before any use of Java Cryptography
+ * Architecture primitives. A good place to invoke them is in the application's {@code onCreate}.
+ * <p/>
+ * copied from http://android-developers.blogspot.de/2013/08/some-securerandom-thoughts.html
+ * <p/>
+ * <p/>
+ * More information on these Android bugs:
+ * http://blog.k3170makan.com/2013/08/more-details-on-android-jca-prng-flaw.html
+ * Paper: "Randomly failed! Weaknesses in Java Pseudo Random Number Generators (PRNGs)"
+ * <p/>
+ * <p/>
+ * Sep 15, 2013:
+ * On some devices /dev/urandom is non-writable!
+ * No need to seed /dev/urandom. urandom should have enough seeds from the OS and kernel.
+ * Only OpenSSL seeds are broken. See http://emboss.github.io/blog/2013/08/21/openssl-prng-is-not-really-fork-safe
+ * <p/>
+ * see also:
+ * https://github.com/k9mail/k-9/commit/dda8f64276d4d29c43f86237cd77819c28f22f21
+ * In addition to a couple of custom ROMs linking /dev/urandom to a non-writable
+ * random version, now Samsung's SELinux policy also prevents apps from opening
+ * /dev/urandom for writing. Since we shouldn't need to write to /dev/urandom anyway
+ * we now simply don't.
+ * <p/>
+ * <p/>
+ * Sep 17, 2013:
+ * Updated from official blogpost:
+ * Update: the original code sample below crashed on a small fraction of Android
+ * devices due to /dev/urandom not being writable. We have now updated the code sample to handle this case gracefully.
+ */
+public final class PRNGFixes {
+
+ private static final int VERSION_CODE_JELLY_BEAN = 16;
+ private static final int VERSION_CODE_JELLY_BEAN_MR2 = 18;
+ private static final byte[] BUILD_FINGERPRINT_AND_DEVICE_SERIAL =
+ getBuildFingerprintAndDeviceSerial();
+
+ /**
+ * Hidden constructor to prevent instantiation.
+ */
+ private PRNGFixes() {
+ }
+
+ /**
+ * Applies all fixes.
+ *
+ * @throws SecurityException if a fix is needed but could not be applied.
+ */
+ public static void apply() {
+ applyOpenSSLFix();
+ installLinuxPRNGSecureRandom();
+ }
+
+ /**
+ * Applies the fix for OpenSSL PRNG having low entropy. Does nothing if the
+ * fix is not needed.
+ *
+ * @throws SecurityException if the fix is needed but could not be applied.
+ */
+ private static void applyOpenSSLFix() throws SecurityException {
+ if ((Build.VERSION.SDK_INT < VERSION_CODE_JELLY_BEAN)
+ || (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2)) {
+ // No need to apply the fix
+ return;
+ }
+
+ try {
+ // Mix in the device- and invocation-specific seed.
+ Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto")
+ .getMethod("RAND_seed", byte[].class)
+ .invoke(null, generateSeed());
+
+ // Mix output of Linux PRNG into OpenSSL's PRNG
+ int bytesRead = (Integer) Class.forName(
+ "org.apache.harmony.xnet.provider.jsse.NativeCrypto")
+ .getMethod("RAND_load_file", String.class, long.class)
+ .invoke(null, "/dev/urandom", 1024);
+ if (bytesRead != 1024) {
+ throw new IOException(
+ "Unexpected number of bytes read from Linux PRNG: "
+ + bytesRead);
+ }
+ } catch (Exception e) {
+ throw new SecurityException("Failed to seed OpenSSL PRNG", e);
+ }
+ }
+
+ /**
+ * Installs a Linux PRNG-backed {@code SecureRandom} implementation as the
+ * default. Does nothing if the implementation is already the default or if
+ * there is not need to install the implementation.
+ *
+ * @throws SecurityException if the fix is needed but could not be applied.
+ */
+ private static void installLinuxPRNGSecureRandom()
+ throws SecurityException {
+ if (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2) {
+ // No need to apply the fix
+ return;
+ }
+
+ // Install a Linux PRNG-based SecureRandom implementation as the
+ // default, if not yet installed.
+ Provider[] secureRandomProviders =
+ Security.getProviders("SecureRandom.SHA1PRNG");
+ if ((secureRandomProviders == null)
+ || (secureRandomProviders.length < 1)
+ || (!LinuxPRNGSecureRandomProvider.class.equals(
+ secureRandomProviders[0].getClass()))) {
+ Security.insertProviderAt(new LinuxPRNGSecureRandomProvider(), 1);
+ }
+
+ // Assert that new SecureRandom() and
+ // SecureRandom.getInstance("SHA1PRNG") return a SecureRandom backed
+ // by the Linux PRNG-based SecureRandom implementation.
+ SecureRandom rng1 = new SecureRandom();
+ if (!LinuxPRNGSecureRandomProvider.class.equals(
+ rng1.getProvider().getClass())) {
+ throw new SecurityException(
+ "new SecureRandom() backed by wrong Provider: "
+ + rng1.getProvider().getClass());
+ }
+
+ SecureRandom rng2;
+ try {
+ rng2 = SecureRandom.getInstance("SHA1PRNG");
+ } catch (NoSuchAlgorithmException e) {
+ throw new SecurityException("SHA1PRNG not available", e);
+ }
+ if (!LinuxPRNGSecureRandomProvider.class.equals(
+ rng2.getProvider().getClass())) {
+ throw new SecurityException(
+ "SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong"
+ + " Provider: " + rng2.getProvider().getClass());
+ }
+ }
+
+ /**
+ * {@code Provider} of {@code SecureRandom} engines which pass through
+ * all requests to the Linux PRNG.
+ */
+ private static class LinuxPRNGSecureRandomProvider extends Provider {
+
+ public LinuxPRNGSecureRandomProvider() {
+ super("LinuxPRNG",
+ 1.0,
+ "A Linux-specific random number provider that uses"
+ + " /dev/urandom");
+ // Although /dev/urandom is not a SHA-1 PRNG, some apps
+ // explicitly request a SHA1PRNG SecureRandom and we thus need to
+ // prevent them from getting the default implementation whose output
+ // may have low entropy.
+ put("SecureRandom.SHA1PRNG", LinuxPRNGSecureRandom.class.getName());
+ put("SecureRandom.SHA1PRNG ImplementedIn", "Software");
+ }
+ }
+
+ /**
+ * {@link SecureRandomSpi} which passes all requests to the Linux PRNG
+ * ({@code /dev/urandom}).
+ */
+ public static class LinuxPRNGSecureRandom extends SecureRandomSpi {
+
+ /*
+ * IMPLEMENTATION NOTE: Requests to generate bytes and to mix in a seed
+ * are passed through to the Linux PRNG (/dev/urandom). Instances of
+ * this class seed themselves by mixing in the current time, PID, UID,
+ * build fingerprint, and hardware serial number (where available) into
+ * Linux PRNG.
+ *
+ * Concurrency: Read requests to the underlying Linux PRNG are
+ * serialized (on sLock) to ensure that multiple threads do not get
+ * duplicated PRNG output.
+ */
+
+ private static final File URANDOM_FILE = new File("/dev/urandom");
+
+ private static final Object sLock = new Object();
+
+ /**
+ * Input stream for reading from Linux PRNG or {@code null} if not yet
+ * opened.
+ *
+ * @GuardedBy("sLock")
+ */
+ private static DataInputStream sUrandomIn;
+
+ /**
+ * Output stream for writing to Linux PRNG or {@code null} if not yet
+ * opened.
+ *
+ * @GuardedBy("sLock")
+ */
+ private static OutputStream sUrandomOut;
+
+ /**
+ * Whether this engine instance has been seeded. This is needed because
+ * each instance needs to seed itself if the client does not explicitly
+ * seed it.
+ */
+ private boolean mSeeded;
+
+ @Override
+ protected void engineSetSeed(byte[] bytes) {
+ try {
+ OutputStream out;
+ synchronized (sLock) {
+ out = getUrandomOutputStream();
+ }
+ out.write(bytes);
+ out.flush();
+ } catch (IOException e) {
+ // On a small fraction of devices /dev/urandom is not writable.
+ // Log and ignore.
+ Log.w(PRNGFixes.class.getSimpleName(),
+ "Failed to mix seed into " + URANDOM_FILE);
+ } finally {
+ mSeeded = true;
+ }
+ }
+
+ @Override
+ protected void engineNextBytes(byte[] bytes) {
+ if (!mSeeded) {
+ // Mix in the device- and invocation-specific seed.
+ engineSetSeed(generateSeed());
+ }
+
+ try {
+ DataInputStream in;
+ synchronized (sLock) {
+ in = getUrandomInputStream();
+ }
+ synchronized (in) {
+ in.readFully(bytes);
+ }
+ } catch (IOException e) {
+ throw new SecurityException(
+ "Failed to read from " + URANDOM_FILE, e);
+ }
+ }
+
+ @Override
+ protected byte[] engineGenerateSeed(int size) {
+ byte[] seed = new byte[size];
+ engineNextBytes(seed);
+ return seed;
+ }
+
+ private DataInputStream getUrandomInputStream() {
+ synchronized (sLock) {
+ if (sUrandomIn == null) {
+ // NOTE: Consider inserting a BufferedInputStream between
+ // DataInputStream and FileInputStream if you need higher
+ // PRNG output performance and can live with future PRNG
+ // output being pulled into this process prematurely.
+ try {
+ sUrandomIn = new DataInputStream(
+ new FileInputStream(URANDOM_FILE));
+ } catch (IOException e) {
+ throw new SecurityException("Failed to open "
+ + URANDOM_FILE + " for reading", e);
+ }
+ }
+ return sUrandomIn;
+ }
+ }
+
+ private OutputStream getUrandomOutputStream() throws IOException {
+ synchronized (sLock) {
+ if (sUrandomOut == null) {
+ sUrandomOut = new FileOutputStream(URANDOM_FILE);
+ }
+ return sUrandomOut;
+ }
+ }
+ }
+
+ /**
+ * Generates a device- and invocation-specific seed to be mixed into the
+ * Linux PRNG.
+ */
+ private static byte[] generateSeed() {
+ try {
+ ByteArrayOutputStream seedBuffer = new ByteArrayOutputStream();
+ DataOutputStream seedBufferOut =
+ new DataOutputStream(seedBuffer);
+ seedBufferOut.writeLong(System.currentTimeMillis());
+ seedBufferOut.writeLong(System.nanoTime());
+ seedBufferOut.writeInt(Process.myPid());
+ seedBufferOut.writeInt(Process.myUid());
+ seedBufferOut.write(BUILD_FINGERPRINT_AND_DEVICE_SERIAL);
+ seedBufferOut.close();
+ return seedBuffer.toByteArray();
+ } catch (IOException e) {
+ throw new SecurityException("Failed to generate seed", e);
+ }
+ }
+
+ /**
+ * Gets the hardware serial number of this device.
+ *
+ * @return serial number or {@code null} if not available.
+ */
+ private static String getDeviceSerialNumber() {
+ // We're using the Reflection API because Build.SERIAL is only available
+ // since API Level 9 (Gingerbread, Android 2.3).
+ try {
+ return (String) Build.class.getField("SERIAL").get(null);
+ } catch (Exception ignored) {
+ return null;
+ }
+ }
+
+ private static byte[] getBuildFingerprintAndDeviceSerial() {
+ StringBuilder result = new StringBuilder();
+ String fingerprint = Build.FINGERPRINT;
+ if (fingerprint != null) {
+ result.append(fingerprint);
+ }
+ String serial = getDeviceSerialNumber();
+ if (serial != null) {
+ result.append(serial);
+ }
+ try {
+ return result.toString().getBytes("UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException("UTF-8 encoding not supported");
+ }
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/PausableThreadPoolExecutor.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/PausableThreadPoolExecutor.java
new file mode 100644
index 000000000..5ec915810
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/PausableThreadPoolExecutor.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.util;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.RejectedExecutionHandler;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Example from
+ * http://docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/ThreadPoolExecutor.html
+ */
+public class PausableThreadPoolExecutor extends ThreadPoolExecutor {
+
+ public PausableThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
+ TimeUnit unit, BlockingQueue<Runnable> workQueue,
+ RejectedExecutionHandler handler) {
+ super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
+ }
+
+ public PausableThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
+ TimeUnit unit, BlockingQueue<Runnable> workQueue,
+ ThreadFactory threadFactory,
+ RejectedExecutionHandler handler) {
+ super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
+ }
+
+ public PausableThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
+ TimeUnit unit, BlockingQueue<Runnable> workQueue,
+ ThreadFactory threadFactory) {
+ super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
+ }
+
+ public PausableThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
+ TimeUnit unit, BlockingQueue<Runnable> workQueue) {
+ super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
+ }
+
+ private boolean mIsPaused;
+ private ReentrantLock mPauseLock = new ReentrantLock();
+ private Condition mUnPaused = mPauseLock.newCondition();
+
+ protected void beforeExecute(Thread t, Runnable r) {
+ super.beforeExecute(t, r);
+ mPauseLock.lock();
+ try {
+ while (mIsPaused) {
+ mUnPaused.await();
+ }
+ } catch (InterruptedException ie) {
+ t.interrupt();
+ } finally {
+ mPauseLock.unlock();
+ }
+ }
+
+ public void pause() {
+ mPauseLock.lock();
+ try {
+ mIsPaused = true;
+ } finally {
+ mPauseLock.unlock();
+ }
+ }
+
+ public void resume() {
+ mPauseLock.lock();
+ try {
+ mIsPaused = false;
+ mUnPaused.signalAll();
+ } finally {
+ mPauseLock.unlock();
+ }
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/PositionAwareInputStream.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/PositionAwareInputStream.java
new file mode 100644
index 000000000..4fcd3047f
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/PositionAwareInputStream.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.sufficientlysecure.keychain.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public class PositionAwareInputStream extends InputStream {
+ private InputStream mStream;
+ private long mPosition;
+
+ public PositionAwareInputStream(InputStream in) {
+ mStream = in;
+ mPosition = 0;
+ }
+
+ @Override
+ public int read() throws IOException {
+ int ch = mStream.read();
+ ++mPosition;
+ return ch;
+ }
+
+ @Override
+ public int available() throws IOException {
+ return mStream.available();
+ }
+
+ @Override
+ public void close() throws IOException {
+ mStream.close();
+ }
+
+ @Override
+ public boolean markSupported() {
+ return false;
+ }
+
+ @Override
+ public int read(byte[] b) throws IOException {
+ int result = mStream.read(b);
+ mPosition += result;
+ return result;
+ }
+
+ @Override
+ public int read(byte[] b, int offset, int length) throws IOException {
+ int result = mStream.read(b, offset, length);
+ mPosition += result;
+ return result;
+ }
+
+ @Override
+ public synchronized void reset() throws IOException {
+ mStream.reset();
+ mPosition = 0;
+ }
+
+ @Override
+ public long skip(long n) throws IOException {
+ long result = mStream.skip(n);
+ mPosition += result;
+ return result;
+ }
+
+ public long position() {
+ return mPosition;
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Primes.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Primes.java
new file mode 100644
index 000000000..28a12bf37
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Primes.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.sufficientlysecure.keychain.util;
+
+import java.math.BigInteger;
+
+/**
+ * Primes for ElGamal
+ */
+public final class Primes {
+ // taken from http://www.ietf.org/rfc/rfc3526.txt
+ public static final String P1536 =
+ "FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1" +
+ "29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD" +
+ "EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245" +
+ "E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED" +
+ "EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D" +
+ "C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F" +
+ "83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D" +
+ "670C354E 4ABC9804 F1746C08 CA237327 FFFFFFFF FFFFFFFF";
+
+ public static final String P2048 =
+ "FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1" +
+ "29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD" +
+ "EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245" +
+ "E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED" +
+ "EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D" +
+ "C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F" +
+ "83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D" +
+ "670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B" +
+ "E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9" +
+ "DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510" +
+ "15728E5A 8AACAA68 FFFFFFFF FFFFFFFF";
+
+ public static final String P3072 =
+ "FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1" +
+ "29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD" +
+ "EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245" +
+ "E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED" +
+ "EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D" +
+ "C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F" +
+ "83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D" +
+ "670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B" +
+ "E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9" +
+ "DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510" +
+ "15728E5A 8AAAC42D AD33170D 04507A33 A85521AB DF1CBA64" +
+ "ECFB8504 58DBEF0A 8AEA7157 5D060C7D B3970F85 A6E1E4C7" +
+ "ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226 1AD2EE6B" +
+ "F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C" +
+ "BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31" +
+ "43DB5BFC E0FD108E 4B82D120 A93AD2CA FFFFFFFF FFFFFFFF";
+
+ public static final String P4096 =
+ "FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1" +
+ "29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD" +
+ "EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245" +
+ "E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED" +
+ "EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D" +
+ "C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F" +
+ "83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D" +
+ "670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B" +
+ "E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9" +
+ "DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510" +
+ "15728E5A 8AAAC42D AD33170D 04507A33 A85521AB DF1CBA64" +
+ "ECFB8504 58DBEF0A 8AEA7157 5D060C7D B3970F85 A6E1E4C7" +
+ "ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226 1AD2EE6B" +
+ "F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C" +
+ "BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31" +
+ "43DB5BFC E0FD108E 4B82D120 A9210801 1A723C12 A787E6D7" +
+ "88719A10 BDBA5B26 99C32718 6AF4E23C 1A946834 B6150BDA" +
+ "2583E9CA 2AD44CE8 DBBBC2DB 04DE8EF9 2E8EFC14 1FBECAA6" +
+ "287C5947 4E6BC05D 99B2964F A090C3A2 233BA186 515BE7ED" +
+ "1F612970 CEE2D7AF B81BDD76 2170481C D0069127 D5B05AA9" +
+ "93B4EA98 8D8FDDC1 86FFB7DC 90A6C08F 4DF435C9 34063199" +
+ "FFFFFFFF FFFFFFFF";
+
+ public static final String P6144 =
+ "FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1" +
+ "29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD" +
+ "EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245" +
+ "E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED" +
+ "EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D" +
+ "C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F" +
+ "83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D" +
+ "670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B" +
+ "E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9" +
+ "DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510" +
+ "15728E5A 8AAAC42D AD33170D 04507A33 A85521AB DF1CBA64" +
+ "ECFB8504 58DBEF0A 8AEA7157 5D060C7D B3970F85 A6E1E4C7" +
+ "ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226 1AD2EE6B" +
+ "F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C" +
+ "BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31" +
+ "43DB5BFC E0FD108E 4B82D120 A9210801 1A723C12 A787E6D7" +
+ "88719A10 BDBA5B26 99C32718 6AF4E23C 1A946834 B6150BDA" +
+ "2583E9CA 2AD44CE8 DBBBC2DB 04DE8EF9 2E8EFC14 1FBECAA6" +
+ "287C5947 4E6BC05D 99B2964F A090C3A2 233BA186 515BE7ED" +
+ "1F612970 CEE2D7AF B81BDD76 2170481C D0069127 D5B05AA9" +
+ "93B4EA98 8D8FDDC1 86FFB7DC 90A6C08F 4DF435C9 34028492" +
+ "36C3FAB4 D27C7026 C1D4DCB2 602646DE C9751E76 3DBA37BD" +
+ "F8FF9406 AD9E530E E5DB382F 413001AE B06A53ED 9027D831" +
+ "179727B0 865A8918 DA3EDBEB CF9B14ED 44CE6CBA CED4BB1B" +
+ "DB7F1447 E6CC254B 33205151 2BD7AF42 6FB8F401 378CD2BF" +
+ "5983CA01 C64B92EC F032EA15 D1721D03 F482D7CE 6E74FEF6" +
+ "D55E702F 46980C82 B5A84031 900B1C9E 59E7C97F BEC7E8F3" +
+ "23A97A7E 36CC88BE 0F1D45B7 FF585AC5 4BD407B2 2B4154AA" +
+ "CC8F6D7E BF48E1D8 14CC5ED2 0F8037E0 A79715EE F29BE328" +
+ "06A1D58B B7C5DA76 F550AA3D 8A1FBFF0 EB19CCB1 A313D55C" +
+ "DA56C9EC 2EF29632 387FE8D7 6E3C0468 043E8F66 3F4860EE" +
+ "12BF2D5B 0B7474D6 E694F91E 6DCC4024 FFFFFFFF FFFFFFFF";
+
+ public static final String P8192 =
+ "FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1" +
+ "29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD" +
+ "EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245" +
+ "E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED" +
+ "EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D" +
+ "C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F" +
+ "83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D" +
+ "670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B" +
+ "E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9" +
+ "DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510" +
+ "15728E5A 8AAAC42D AD33170D 04507A33 A85521AB DF1CBA64" +
+ "ECFB8504 58DBEF0A 8AEA7157 5D060C7D B3970F85 A6E1E4C7" +
+ "ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226 1AD2EE6B" +
+ "F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C" +
+ "BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31" +
+ "43DB5BFC E0FD108E 4B82D120 A9210801 1A723C12 A787E6D7" +
+ "88719A10 BDBA5B26 99C32718 6AF4E23C 1A946834 B6150BDA" +
+ "2583E9CA 2AD44CE8 DBBBC2DB 04DE8EF9 2E8EFC14 1FBECAA6" +
+ "287C5947 4E6BC05D 99B2964F A090C3A2 233BA186 515BE7ED" +
+ "1F612970 CEE2D7AF B81BDD76 2170481C D0069127 D5B05AA9" +
+ "93B4EA98 8D8FDDC1 86FFB7DC 90A6C08F 4DF435C9 34028492" +
+ "36C3FAB4 D27C7026 C1D4DCB2 602646DE C9751E76 3DBA37BD" +
+ "F8FF9406 AD9E530E E5DB382F 413001AE B06A53ED 9027D831" +
+ "179727B0 865A8918 DA3EDBEB CF9B14ED 44CE6CBA CED4BB1B" +
+ "DB7F1447 E6CC254B 33205151 2BD7AF42 6FB8F401 378CD2BF" +
+ "5983CA01 C64B92EC F032EA15 D1721D03 F482D7CE 6E74FEF6" +
+ "D55E702F 46980C82 B5A84031 900B1C9E 59E7C97F BEC7E8F3" +
+ "23A97A7E 36CC88BE 0F1D45B7 FF585AC5 4BD407B2 2B4154AA" +
+ "CC8F6D7E BF48E1D8 14CC5ED2 0F8037E0 A79715EE F29BE328" +
+ "06A1D58B B7C5DA76 F550AA3D 8A1FBFF0 EB19CCB1 A313D55C" +
+ "DA56C9EC 2EF29632 387FE8D7 6E3C0468 043E8F66 3F4860EE" +
+ "12BF2D5B 0B7474D6 E694F91E 6DBE1159 74A3926F 12FEE5E4" +
+ "38777CB6 A932DF8C D8BEC4D0 73B931BA 3BC832B6 8D9DD300" +
+ "741FA7BF 8AFC47ED 2576F693 6BA42466 3AAB639C 5AE4F568" +
+ "3423B474 2BF1C978 238F16CB E39D652D E3FDB8BE FC848AD9" +
+ "22222E04 A4037C07 13EB57A8 1A23F0C7 3473FC64 6CEA306B" +
+ "4BCBC886 2F8385DD FA9D4B7F A2C087E8 79683303 ED5BDD3A" +
+ "062B3CF5 B3A278A6 6D2A13F8 3F44F82D DF310EE0 74AB6A36" +
+ "4597E899 A0255DC1 64F31CC5 0846851D F9AB4819 5DED7EA1" +
+ "B1D510BD 7EE74D73 FAF36BC3 1ECFA268 359046F4 EB879F92" +
+ "4009438B 481C6CD7 889A002E D5EE382B C9190DA6 FC026E47" +
+ "9558E447 5677E9AA 9E3050E2 765694DF C81F56E8 80B96E71" +
+ "60C980DD 98EDD3DF FFFFFFFF FFFFFFFF";
+
+ public static BigInteger getBestPrime(int keySize) {
+ String primeString;
+ if (keySize >= (8192 + 6144) / 2) {
+ primeString = P8192;
+ } else if (keySize >= (6144 + 4096) / 2) {
+ primeString = P6144;
+ } else if (keySize >= (4096 + 3072) / 2) {
+ primeString = P4096;
+ } else if (keySize >= (3072 + 2048) / 2) {
+ primeString = P3072;
+ } else if (keySize >= (2048 + 1536) / 2) {
+ primeString = P2048;
+ } else {
+ primeString = P1536;
+ }
+
+ return new BigInteger(primeString.replaceAll(" ", ""), 16);
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ProgressDialogUpdater.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ProgressDialogUpdater.java
new file mode 100644
index 000000000..26c05ad0a
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ProgressDialogUpdater.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.sufficientlysecure.keychain.util;
+
+public interface ProgressDialogUpdater {
+ void setProgress(String message, int current, int total);
+
+ void setProgress(int resourceId, int current, int total);
+
+ void setProgress(int current, int total);
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ProgressScaler.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ProgressScaler.java
new file mode 100644
index 000000000..23961c05f
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ProgressScaler.java
@@ -0,0 +1,50 @@
+/*
+ * 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.util;
+
+/** This is a simple class that wraps a ProgressDialogUpdater, scaling the progress
+ * values into a specified range.
+ */
+public class ProgressScaler implements ProgressDialogUpdater {
+
+ final ProgressDialogUpdater mWrapped;
+ final int mFrom, mTo, mMax;
+
+ public ProgressScaler(ProgressDialogUpdater wrapped, int from, int to, int max) {
+ this.mWrapped = wrapped;
+ this.mFrom = from;
+ this.mTo = to;
+ this.mMax = max;
+ }
+
+ /**
+ * Set progressDialogUpdater of ProgressDialog by sending message to handler on UI thread
+ */
+ public void setProgress(String message, int progress, int max) {
+ mWrapped.setProgress(message, mFrom + progress * (mTo - mFrom) / max, mMax);
+ }
+
+ public void setProgress(int resourceId, int progress, int max) {
+ mWrapped.setProgress(resourceId, progress, mMax);
+ }
+
+ public void setProgress(int progress, int max) {
+ mWrapped.setProgress(progress, max);
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/QrCodeUtils.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/QrCodeUtils.java
new file mode 100644
index 000000000..af9034aa7
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/QrCodeUtils.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2011 Andreas Schildbach
+ *
+ * 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 com.google.zxing.BarcodeFormat;
+import com.google.zxing.EncodeHintType;
+import com.google.zxing.WriterException;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.qrcode.QRCodeWriter;
+import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
+
+import android.graphics.Bitmap;
+import android.graphics.Color;
+
+import org.sufficientlysecure.keychain.Constants;
+
+import java.util.Hashtable;
+
+public class QrCodeUtils {
+ public static final QRCodeWriter QR_CODE_WRITER = new QRCodeWriter();
+
+ /**
+ * Generate Bitmap with QR Code based on input.
+ *
+ * @param input
+ * @param size
+ * @return QR Code as Bitmap
+ */
+ public static Bitmap getQRCodeBitmap(final String input, final int size) {
+ try {
+ final Hashtable<EncodeHintType, Object> hints = new Hashtable<EncodeHintType, Object>();
+ hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
+ final BitMatrix result = QR_CODE_WRITER.encode(input, BarcodeFormat.QR_CODE, size,
+ size, hints);
+
+ final int width = result.getWidth();
+ final int height = result.getHeight();
+ final int[] pixels = new int[width * height];
+
+ for (int y = 0; y < height; y++) {
+ final int offset = y * width;
+ for (int x = 0; x < width; x++) {
+ pixels[offset + x] = result.get(x, y) ? Color.BLACK : Color.TRANSPARENT;
+ }
+ }
+
+ final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
+ return bitmap;
+ } catch (final WriterException e) {
+ Log.e(Constants.TAG, "QrCodeUtils", e);
+ return null;
+ }
+ }
+
+}
diff --git a/OpenKeychain/src/main/res/anim/push_left_in.xml b/OpenKeychain/src/main/res/anim/push_left_in.xml
new file mode 100644
index 000000000..45fb4875a
--- /dev/null
+++ b/OpenKeychain/src/main/res/anim/push_left_in.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <translate android:fromXDelta="100%p" android:toXDelta="0" android:duration="500"/>
+ <alpha android:fromAlpha="1.0" android:toAlpha="1.0" android:duration="500" />
+</set>
diff --git a/OpenKeychain/src/main/res/anim/push_left_out.xml b/OpenKeychain/src/main/res/anim/push_left_out.xml
new file mode 100644
index 000000000..845679f16
--- /dev/null
+++ b/OpenKeychain/src/main/res/anim/push_left_out.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <translate android:fromXDelta="0" android:toXDelta="-100%p" android:duration="500"/>
+ <alpha android:fromAlpha="1.0" android:toAlpha="1.0" android:duration="500" />
+</set> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/anim/push_right_in.xml b/OpenKeychain/src/main/res/anim/push_right_in.xml
new file mode 100644
index 000000000..09a244406
--- /dev/null
+++ b/OpenKeychain/src/main/res/anim/push_right_in.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <translate android:fromXDelta="-100%p" android:toXDelta="0" android:duration="500"/>
+ <alpha android:fromAlpha="1.0" android:toAlpha="1.0" android:duration="500" />
+</set>
diff --git a/OpenKeychain/src/main/res/anim/push_right_out.xml b/OpenKeychain/src/main/res/anim/push_right_out.xml
new file mode 100644
index 000000000..e8893a69a
--- /dev/null
+++ b/OpenKeychain/src/main/res/anim/push_right_out.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <translate android:fromXDelta="0" android:toXDelta="100%p" android:duration="500"/>
+ <alpha android:fromAlpha="1.0" android:toAlpha="1.0" android:duration="500" />
+</set> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/drawable-hdpi/certify_small.png b/OpenKeychain/src/main/res/drawable-hdpi/certify_small.png
new file mode 100644
index 000000000..9e54464ed
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-hdpi/certify_small.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-hdpi/drawer_shadow.9.png b/OpenKeychain/src/main/res/drawable-hdpi/drawer_shadow.9.png
new file mode 100644
index 000000000..236bff558
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-hdpi/drawer_shadow.9.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-hdpi/encrypted_small.png b/OpenKeychain/src/main/res/drawable-hdpi/encrypted_small.png
new file mode 100644
index 000000000..3ff8e9b97
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-hdpi/encrypted_small.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_action_add_person.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_action_add_person.png
new file mode 100644
index 000000000..5ebac9706
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-hdpi/ic_action_add_person.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_action_cancel.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_action_cancel.png
new file mode 100644
index 000000000..cde36e1fa
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-hdpi/ic_action_cancel.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_action_cloud.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_action_cloud.png
new file mode 100644
index 000000000..3daa64131
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-hdpi/ic_action_cloud.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_action_discard.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_action_discard.png
new file mode 100644
index 000000000..9c717dd32
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-hdpi/ic_action_discard.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_action_done.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_action_done.png
new file mode 100644
index 000000000..58bf97217
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-hdpi/ic_action_done.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_action_import_export.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_action_import_export.png
new file mode 100644
index 000000000..742ba271c
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-hdpi/ic_action_import_export.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_action_person.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_action_person.png
new file mode 100644
index 000000000..9fd81097b
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-hdpi/ic_action_person.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_action_save.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_action_save.png
new file mode 100644
index 000000000..c4b7783cc
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-hdpi/ic_action_save.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_action_search.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_action_search.png
new file mode 100644
index 000000000..f594b4e48
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-hdpi/ic_action_search.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_action_secure.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_action_secure.png
new file mode 100644
index 000000000..287ae2fb0
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-hdpi/ic_action_secure.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_action_select_all.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_action_select_all.png
new file mode 100644
index 000000000..fc0dd57b6
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-hdpi/ic_action_select_all.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_action_share.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_action_share.png
new file mode 100644
index 000000000..8a6cbfea2
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-hdpi/ic_action_share.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_dialog_alert_holo_light.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_dialog_alert_holo_light.png
new file mode 100644
index 000000000..1374a53e5
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-hdpi/ic_dialog_alert_holo_light.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_drawer.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_drawer.png
new file mode 100644
index 000000000..c59f601ca
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-hdpi/ic_drawer.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_menu_search.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_menu_search.png
new file mode 100644
index 000000000..1cb61faf4
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-hdpi/ic_menu_search.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_menu_search_list.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_menu_search_list.png
new file mode 100644
index 000000000..efee6dfd2
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-hdpi/ic_menu_search_list.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_next.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_next.png
new file mode 100644
index 000000000..d71058055
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-hdpi/ic_next.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_previous.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_previous.png
new file mode 100644
index 000000000..d610e4667
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-hdpi/ic_previous.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-hdpi/icon.png b/OpenKeychain/src/main/res/drawable-hdpi/icon.png
new file mode 100644
index 000000000..f5487599b
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-hdpi/icon.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-hdpi/key_small.png b/OpenKeychain/src/main/res/drawable-hdpi/key_small.png
new file mode 100644
index 000000000..6966048a1
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-hdpi/key_small.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-hdpi/overlay_error.png b/OpenKeychain/src/main/res/drawable-hdpi/overlay_error.png
new file mode 100644
index 000000000..e6d7e60ba
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-hdpi/overlay_error.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-hdpi/overlay_ok.png b/OpenKeychain/src/main/res/drawable-hdpi/overlay_ok.png
new file mode 100644
index 000000000..0672f869d
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-hdpi/overlay_ok.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-hdpi/popup_center_bright.9.png b/OpenKeychain/src/main/res/drawable-hdpi/popup_center_bright.9.png
new file mode 100644
index 000000000..c2a739c42
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-hdpi/popup_center_bright.9.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-hdpi/popup_full_bright.9.png b/OpenKeychain/src/main/res/drawable-hdpi/popup_full_bright.9.png
new file mode 100644
index 000000000..6b8aa9d52
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-hdpi/popup_full_bright.9.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-hdpi/revoked_key_small.png b/OpenKeychain/src/main/res/drawable-hdpi/revoked_key_small.png
new file mode 100644
index 000000000..75f45eb54
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-hdpi/revoked_key_small.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-hdpi/signed_large.png b/OpenKeychain/src/main/res/drawable-hdpi/signed_large.png
new file mode 100644
index 000000000..c209f4167
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-hdpi/signed_large.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-hdpi/signed_small.png b/OpenKeychain/src/main/res/drawable-hdpi/signed_small.png
new file mode 100644
index 000000000..54c4906e8
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-hdpi/signed_small.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-ldpi/encrypted_small.png b/OpenKeychain/src/main/res/drawable-ldpi/encrypted_small.png
new file mode 100644
index 000000000..5e7294a4b
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-ldpi/encrypted_small.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-ldpi/ic_next.png b/OpenKeychain/src/main/res/drawable-ldpi/ic_next.png
new file mode 100644
index 000000000..474ed8faa
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-ldpi/ic_next.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-ldpi/ic_previous.png b/OpenKeychain/src/main/res/drawable-ldpi/ic_previous.png
new file mode 100644
index 000000000..6fd885e6b
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-ldpi/ic_previous.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-ldpi/icon.png b/OpenKeychain/src/main/res/drawable-ldpi/icon.png
new file mode 100644
index 000000000..7cd482bff
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-ldpi/icon.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-ldpi/key_small.png b/OpenKeychain/src/main/res/drawable-ldpi/key_small.png
new file mode 100644
index 000000000..073b95029
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-ldpi/key_small.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-ldpi/overlay_error.png b/OpenKeychain/src/main/res/drawable-ldpi/overlay_error.png
new file mode 100644
index 000000000..e5a88e18f
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-ldpi/overlay_error.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-ldpi/overlay_ok.png b/OpenKeychain/src/main/res/drawable-ldpi/overlay_ok.png
new file mode 100644
index 000000000..63374d47f
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-ldpi/overlay_ok.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-ldpi/signed_large.png b/OpenKeychain/src/main/res/drawable-ldpi/signed_large.png
new file mode 100644
index 000000000..d2917644c
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-ldpi/signed_large.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-ldpi/signed_small.png b/OpenKeychain/src/main/res/drawable-ldpi/signed_small.png
new file mode 100644
index 000000000..19d45f8da
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-ldpi/signed_small.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-mdpi/certify_small.png b/OpenKeychain/src/main/res/drawable-mdpi/certify_small.png
new file mode 100644
index 000000000..575b2d866
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-mdpi/certify_small.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-mdpi/drawer_shadow.9.png b/OpenKeychain/src/main/res/drawable-mdpi/drawer_shadow.9.png
new file mode 100644
index 000000000..ffe3a28d7
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-mdpi/drawer_shadow.9.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-mdpi/encrypted_small.png b/OpenKeychain/src/main/res/drawable-mdpi/encrypted_small.png
new file mode 100644
index 000000000..bcd8cfc8e
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-mdpi/encrypted_small.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-mdpi/ic_action_add_person.png b/OpenKeychain/src/main/res/drawable-mdpi/ic_action_add_person.png
new file mode 100644
index 000000000..c43cf6553
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-mdpi/ic_action_add_person.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-mdpi/ic_action_cancel.png b/OpenKeychain/src/main/res/drawable-mdpi/ic_action_cancel.png
new file mode 100644
index 000000000..9f4c3d6a2
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-mdpi/ic_action_cancel.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-mdpi/ic_action_cloud.png b/OpenKeychain/src/main/res/drawable-mdpi/ic_action_cloud.png
new file mode 100644
index 000000000..266d4c21f
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-mdpi/ic_action_cloud.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-mdpi/ic_action_discard.png b/OpenKeychain/src/main/res/drawable-mdpi/ic_action_discard.png
new file mode 100644
index 000000000..9dfb7cc2c
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-mdpi/ic_action_discard.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-mdpi/ic_action_done.png b/OpenKeychain/src/main/res/drawable-mdpi/ic_action_done.png
new file mode 100644
index 000000000..cf5fab3ad
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-mdpi/ic_action_done.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-mdpi/ic_action_import_export.png b/OpenKeychain/src/main/res/drawable-mdpi/ic_action_import_export.png
new file mode 100644
index 000000000..1d6522beb
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-mdpi/ic_action_import_export.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-mdpi/ic_action_person.png b/OpenKeychain/src/main/res/drawable-mdpi/ic_action_person.png
new file mode 100644
index 000000000..359da1c12
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-mdpi/ic_action_person.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-mdpi/ic_action_save.png b/OpenKeychain/src/main/res/drawable-mdpi/ic_action_save.png
new file mode 100644
index 000000000..61304a68c
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-mdpi/ic_action_save.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-mdpi/ic_action_search.png b/OpenKeychain/src/main/res/drawable-mdpi/ic_action_search.png
new file mode 100644
index 000000000..f6719d228
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-mdpi/ic_action_search.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-mdpi/ic_action_secure.png b/OpenKeychain/src/main/res/drawable-mdpi/ic_action_secure.png
new file mode 100644
index 000000000..d49217234
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-mdpi/ic_action_secure.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-mdpi/ic_action_select_all.png b/OpenKeychain/src/main/res/drawable-mdpi/ic_action_select_all.png
new file mode 100644
index 000000000..da37d7a6e
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-mdpi/ic_action_select_all.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-mdpi/ic_action_share.png b/OpenKeychain/src/main/res/drawable-mdpi/ic_action_share.png
new file mode 100644
index 000000000..bff81179a
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-mdpi/ic_action_share.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-mdpi/ic_dialog_alert_holo_light.png b/OpenKeychain/src/main/res/drawable-mdpi/ic_dialog_alert_holo_light.png
new file mode 100644
index 000000000..9e7f0bd5f
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-mdpi/ic_dialog_alert_holo_light.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-mdpi/ic_drawer.png b/OpenKeychain/src/main/res/drawable-mdpi/ic_drawer.png
new file mode 100644
index 000000000..1ed2c56ee
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-mdpi/ic_drawer.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-mdpi/ic_menu_search.png b/OpenKeychain/src/main/res/drawable-mdpi/ic_menu_search.png
new file mode 100644
index 000000000..2369d03f3
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-mdpi/ic_menu_search.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-mdpi/ic_menu_search_list.png b/OpenKeychain/src/main/res/drawable-mdpi/ic_menu_search_list.png
new file mode 100644
index 000000000..9033f1ec2
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-mdpi/ic_menu_search_list.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-mdpi/ic_next.png b/OpenKeychain/src/main/res/drawable-mdpi/ic_next.png
new file mode 100644
index 000000000..8271c1380
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-mdpi/ic_next.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-mdpi/ic_previous.png b/OpenKeychain/src/main/res/drawable-mdpi/ic_previous.png
new file mode 100644
index 000000000..ef90db972
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-mdpi/ic_previous.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-mdpi/icon.png b/OpenKeychain/src/main/res/drawable-mdpi/icon.png
new file mode 100644
index 000000000..34f1420ac
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-mdpi/icon.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-mdpi/key_small.png b/OpenKeychain/src/main/res/drawable-mdpi/key_small.png
new file mode 100644
index 000000000..c806b6041
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-mdpi/key_small.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-mdpi/overlay_error.png b/OpenKeychain/src/main/res/drawable-mdpi/overlay_error.png
new file mode 100644
index 000000000..5fe017433
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-mdpi/overlay_error.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-mdpi/overlay_ok.png b/OpenKeychain/src/main/res/drawable-mdpi/overlay_ok.png
new file mode 100644
index 000000000..b4f332260
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-mdpi/overlay_ok.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-mdpi/signed_large.png b/OpenKeychain/src/main/res/drawable-mdpi/signed_large.png
new file mode 100644
index 000000000..ab9495e7b
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-mdpi/signed_large.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-mdpi/signed_small.png b/OpenKeychain/src/main/res/drawable-mdpi/signed_small.png
new file mode 100644
index 000000000..4202c3f97
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-mdpi/signed_small.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/drawer_shadow.9.png b/OpenKeychain/src/main/res/drawable-xhdpi/drawer_shadow.9.png
new file mode 100644
index 000000000..fabe9d965
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-xhdpi/drawer_shadow.9.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_add_person.png b/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_add_person.png
new file mode 100644
index 000000000..91434a47b
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_add_person.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_cancel.png b/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_cancel.png
new file mode 100644
index 000000000..ca7d159fd
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_cancel.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_cloud.png b/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_cloud.png
new file mode 100644
index 000000000..0769899fd
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_cloud.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_discard.png b/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_discard.png
new file mode 100644
index 000000000..db69d6c25
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_discard.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_done.png b/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_done.png
new file mode 100644
index 000000000..b8915716e
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_done.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_import_export.png b/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_import_export.png
new file mode 100644
index 000000000..5e48a9c6b
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_import_export.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_person.png b/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_person.png
new file mode 100644
index 000000000..03eeb8d6a
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_person.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_save.png b/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_save.png
new file mode 100644
index 000000000..29c5f4d3b
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_save.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_search.png b/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_search.png
new file mode 100644
index 000000000..aad535e97
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_search.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_secure.png b/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_secure.png
new file mode 100644
index 000000000..2a0898381
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_secure.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_select_all.png b/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_select_all.png
new file mode 100644
index 000000000..af37a3680
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_select_all.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_share.png b/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_share.png
new file mode 100644
index 000000000..2f6dc413b
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_share.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/ic_dialog_alert_holo_light.png b/OpenKeychain/src/main/res/drawable-xhdpi/ic_dialog_alert_holo_light.png
new file mode 100644
index 000000000..a99f0621c
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-xhdpi/ic_dialog_alert_holo_light.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/ic_drawer.png b/OpenKeychain/src/main/res/drawable-xhdpi/ic_drawer.png
new file mode 100644
index 000000000..a5fa74def
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-xhdpi/ic_drawer.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/ic_menu_search.png b/OpenKeychain/src/main/res/drawable-xhdpi/ic_menu_search.png
new file mode 100644
index 000000000..578cb24eb
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-xhdpi/ic_menu_search.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/ic_menu_search_list.png b/OpenKeychain/src/main/res/drawable-xhdpi/ic_menu_search_list.png
new file mode 100644
index 000000000..de20fa0e7
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-xhdpi/ic_menu_search_list.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/icon.png b/OpenKeychain/src/main/res/drawable-xhdpi/icon.png
new file mode 100644
index 000000000..32584f3ff
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-xhdpi/icon.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-xxhdpi/drawer_shadow.9.png b/OpenKeychain/src/main/res/drawable-xxhdpi/drawer_shadow.9.png
new file mode 100644
index 000000000..b91e9d7f2
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-xxhdpi/drawer_shadow.9.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_add_person.png b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_add_person.png
new file mode 100644
index 000000000..f18aa6144
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_add_person.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_cloud.png b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_cloud.png
new file mode 100644
index 000000000..f97084dbe
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_cloud.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_discard.png b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_discard.png
new file mode 100644
index 000000000..b522daffe
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_discard.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_import_export.png b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_import_export.png
new file mode 100644
index 000000000..f054a68e6
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_import_export.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_person.png b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_person.png
new file mode 100644
index 000000000..fd1bcdd45
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_person.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_save.png b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_save.png
new file mode 100644
index 000000000..744350049
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_save.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_search.png b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_search.png
new file mode 100644
index 000000000..9c0ea3ca0
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_search.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_secure.png b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_secure.png
new file mode 100644
index 000000000..d8c094ed8
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_secure.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_select_all.png b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_select_all.png
new file mode 100644
index 000000000..aa5937eab
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_select_all.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_share.png b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_share.png
new file mode 100644
index 000000000..3e441000f
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_share.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-xxhdpi/ic_dialog_alert_holo_light.png b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_dialog_alert_holo_light.png
new file mode 100644
index 000000000..24ec28c2e
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_dialog_alert_holo_light.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-xxhdpi/ic_drawer.png b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_drawer.png
new file mode 100644
index 000000000..9c4685d6e
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_drawer.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-xxhdpi/icon.png b/OpenKeychain/src/main/res/drawable-xxhdpi/icon.png
new file mode 100644
index 000000000..b2922309f
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-xxhdpi/icon.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-xxxhdpi/icon.png b/OpenKeychain/src/main/res/drawable-xxxhdpi/icon.png
new file mode 100644
index 000000000..93ea6b0f5
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-xxxhdpi/icon.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable/certify_small.png b/OpenKeychain/src/main/res/drawable/certify_small.png
new file mode 100644
index 000000000..575b2d866
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable/certify_small.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable/encrypted_small.png b/OpenKeychain/src/main/res/drawable/encrypted_small.png
new file mode 100644
index 000000000..7f4ab803f
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable/encrypted_small.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable/ic_next.png b/OpenKeychain/src/main/res/drawable/ic_next.png
new file mode 100644
index 000000000..8271c1380
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable/ic_next.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable/ic_previous.png b/OpenKeychain/src/main/res/drawable/ic_previous.png
new file mode 100644
index 000000000..ef90db972
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable/ic_previous.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable/key_small.png b/OpenKeychain/src/main/res/drawable/key_small.png
new file mode 100644
index 000000000..121803508
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable/key_small.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable/overlay_error.png b/OpenKeychain/src/main/res/drawable/overlay_error.png
new file mode 100644
index 000000000..2372de59e
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable/overlay_error.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable/overlay_ok.png b/OpenKeychain/src/main/res/drawable/overlay_ok.png
new file mode 100644
index 000000000..2f0005898
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable/overlay_ok.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable/revoked_key_small.png b/OpenKeychain/src/main/res/drawable/revoked_key_small.png
new file mode 100644
index 000000000..f9ed0596f
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable/revoked_key_small.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable/section_header.xml b/OpenKeychain/src/main/res/drawable/section_header.xml
new file mode 100644
index 000000000..a4468484e
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable/section_header.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle" >
+
+ <size
+ android:height="2dp"
+ android:width="1000dp" />
+
+ <solid android:color="@color/emphasis" />
+
+</shape> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/drawable/selector_transparent_button.xml b/OpenKeychain/src/main/res/drawable/selector_transparent_button.xml
new file mode 100644
index 000000000..a2cacf0ad
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable/selector_transparent_button.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Borderless Buttons for API < 11, see http://stackoverflow.com/a/14663170 -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android" android:exitFadeDuration="@android:integer/config_shortAnimTime">
+ <item android:state_pressed="true" android:drawable="@color/emphasis" />
+ <item android:drawable="@android:color/transparent" />
+</selector> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/drawable/signed_large.png b/OpenKeychain/src/main/res/drawable/signed_large.png
new file mode 100644
index 000000000..92e64dc51
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable/signed_large.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable/signed_small.png b/OpenKeychain/src/main/res/drawable/signed_small.png
new file mode 100644
index 000000000..590220281
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable/signed_small.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/layout-large/api_apps_list_activity.xml b/OpenKeychain/src/main/res/layout-large/api_apps_list_activity.xml
new file mode 100644
index 000000000..c0021261e
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout-large/api_apps_list_activity.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal" >
+ <android.support.v4.widget.DrawerLayout
+ android:id="@+id/drawer_layout"
+
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+
+ <include layout="@layout/drawer_list"/>
+
+ </android.support.v4.widget.DrawerLayout>
+
+ <include layout="@layout/api_apps_list_content"/>
+
+</FrameLayout> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout-large/decrypt_activity.xml b/OpenKeychain/src/main/res/layout-large/decrypt_activity.xml
new file mode 100644
index 000000000..26aed0831
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout-large/decrypt_activity.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <android.support.v4.widget.DrawerLayout
+ xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/drawer_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <include layout="@layout/drawer_list"/>
+
+ </android.support.v4.widget.DrawerLayout>
+
+ <include layout="@layout/decrypt_content"/>
+
+</FrameLayout> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout-large/encrypt_activity.xml b/OpenKeychain/src/main/res/layout-large/encrypt_activity.xml
new file mode 100644
index 000000000..7d0d44074
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout-large/encrypt_activity.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <android.support.v4.widget.DrawerLayout
+ xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
+ xmlns:fontawesometext="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/drawer_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <include layout="@layout/drawer_list"/>
+
+ </android.support.v4.widget.DrawerLayout>
+
+ <include layout="@layout/encrypt_content"/>
+</FrameLayout> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout-large/import_keys_activity.xml b/OpenKeychain/src/main/res/layout-large/import_keys_activity.xml
new file mode 100644
index 000000000..2cb408441
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout-large/import_keys_activity.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <android.support.v4.widget.DrawerLayout
+ xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/drawer_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+
+ <include layout="@layout/drawer_list"/>
+
+ </android.support.v4.widget.DrawerLayout>
+
+ <include layout="@layout/import_keys_content"/>
+
+</FrameLayout> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout-large/key_list_activity.xml b/OpenKeychain/src/main/res/layout-large/key_list_activity.xml
new file mode 100644
index 000000000..6636f12ff
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout-large/key_list_activity.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <android.support.v4.widget.DrawerLayout
+ android:id="@+id/drawer_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <include layout="@layout/drawer_list"/>
+
+ </android.support.v4.widget.DrawerLayout>
+
+ <include layout="@layout/key_list_content"/>
+
+</FrameLayout> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/actionbar_custom_view_done.xml b/OpenKeychain/src/main/res/layout/actionbar_custom_view_done.xml
new file mode 100644
index 000000000..50134f4e7
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/actionbar_custom_view_done.xml
@@ -0,0 +1,27 @@
+<!--
+ Copyright 2013 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:dividerPadding="12dp"
+ android:orientation="horizontal"
+ android:divider="@drawable/abc_list_divider_holo_light"
+ android:showDividers="end" >
+
+ <include layout="@layout/actionbar_include_done_button" />
+
+</LinearLayout> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/actionbar_custom_view_done_cancel.xml b/OpenKeychain/src/main/res/layout/actionbar_custom_view_done_cancel.xml
new file mode 100644
index 000000000..1d0476361
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/actionbar_custom_view_done_cancel.xml
@@ -0,0 +1,29 @@
+<!--
+ Copyright 2013 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:dividerPadding="12dp"
+ android:divider="@drawable/abc_list_divider_holo_light"
+ android:orientation="horizontal"
+ android:showDividers="middle">
+
+ <include layout="@layout/actionbar_include_cancel_button" />
+
+ <include layout="@layout/actionbar_include_done_button" />
+
+</LinearLayout> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/actionbar_include_cancel_button.xml b/OpenKeychain/src/main/res/layout/actionbar_include_cancel_button.xml
new file mode 100644
index 000000000..5fd36286b
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/actionbar_include_cancel_button.xml
@@ -0,0 +1,36 @@
+<!--
+ Copyright 2013 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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/actionbar_cancel"
+ style="@style/Widget.AppCompat.ActionButton"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1">
+
+ <TextView
+ android:id="@+id/actionbar_cancel_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:drawableLeft="@drawable/ic_action_cancel"
+ android:drawablePadding="8dp"
+ android:gravity="center_vertical"
+ android:paddingRight="20dp"
+ style="@style/Widget.AppCompat.Light.ActionBar.TabText"
+ android:text="Cancel (set in-code!)" />
+
+</FrameLayout> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/actionbar_include_done_button.xml b/OpenKeychain/src/main/res/layout/actionbar_include_done_button.xml
new file mode 100644
index 000000000..e51f63c80
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/actionbar_include_done_button.xml
@@ -0,0 +1,36 @@
+<!--
+ Copyright 2013 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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/actionbar_done"
+ style="@style/Widget.AppCompat.ActionButton"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1">
+
+ <TextView
+ android:id="@+id/actionbar_done_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:drawableLeft="@drawable/ic_action_done"
+ android:drawablePadding="8dp"
+ android:gravity="center_vertical"
+ android:paddingRight="20dp"
+ style="@style/Widget.AppCompat.Light.ActionBar.TabText"
+ android:text="Done (set in-code!)" />
+
+</FrameLayout> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/api_account_settings_activity.xml b/OpenKeychain/src/main/res/layout/api_account_settings_activity.xml
new file mode 100644
index 000000000..3557c1f00
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/api_account_settings_activity.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="16dp"
+ android:orientation="vertical">
+
+ <fragment
+ android:id="@+id/api_account_settings_fragment"
+ android:name="org.sufficientlysecure.keychain.remote.ui.AccountSettingsFragment"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
+ </LinearLayout>
+</ScrollView>
diff --git a/OpenKeychain/src/main/res/layout/api_account_settings_fragment.xml b/OpenKeychain/src/main/res/layout/api_account_settings_fragment.xml
new file mode 100644
index 000000000..32843eb29
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/api_account_settings_fragment.xml
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ xmlns:custom="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="?android:attr/listPreferredItemHeight"
+ android:layout_marginBottom="4dp"
+ android:layout_marginTop="4dp"
+ android:gravity="center_horizontal"
+ android:orientation="horizontal">
+
+ <ImageView
+ android:id="@+id/api_account_settings_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentBottom="true"
+ android:layout_alignParentTop="true"
+ android:layout_marginRight="6dp"
+ android:src="@drawable/ic_action_person" />
+
+ <TextView
+ android:id="@+id/api_account_settings_acc_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ android:layout_toRightOf="@+id/api_account_settings_icon"
+ android:gravity="center_vertical"
+ android:orientation="vertical"
+ android:text="Name (set in-code)"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+ </RelativeLayout>
+
+ <fragment
+ android:id="@+id/api_account_settings_select_key_fragment"
+ android:name="org.sufficientlysecure.keychain.ui.SelectSecretKeyLayoutFragment"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ tools:layout="@layout/select_secret_key_layout_fragment" />
+
+ <com.beardedhen.androidbootstrap.BootstrapButton
+ android:id="@+id/api_account_settings_create_key"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_marginBottom="4dp"
+ android:layout_marginRight="4dp"
+ android:layout_marginTop="4dp"
+ android:text="@string/api_settings_create_key"
+ bootstrapbutton:bb_icon_left="fa-key"
+ bootstrapbutton:bb_size="default"
+ bootstrapbutton:bb_type="default" />
+
+ <org.sufficientlysecure.keychain.ui.widget.FoldableLinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ custom:foldedLabel="@string/api_settings_show_advanced"
+ custom:unFoldedLabel="@string/api_settings_hide_advanced"
+ custom:foldedIcon="fa-chevron-right"
+ custom:unFoldedIcon="fa-chevron-down">
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/label_encryption_algorithm"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+ <Spinner
+ android:id="@+id/api_account_settings_encryption_algorithm"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/label_hash_algorithm"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+ <Spinner
+ android:id="@+id/api_account_settings_hash_algorithm"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/label_message_compression"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+ <Spinner
+ android:id="@+id/api_account_settings_compression"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
+ </org.sufficientlysecure.keychain.ui.widget.FoldableLinearLayout>
+
+</LinearLayout> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/api_accounts_adapter_list_item.xml b/OpenKeychain/src/main/res/layout/api_accounts_adapter_list_item.xml
new file mode 100644
index 000000000..d31ae52d7
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/api_accounts_adapter_list_item.xml
@@ -0,0 +1,27 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:gravity="center_vertical"
+ android:singleLine="true"
+ android:orientation="horizontal">
+
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingLeft="8dp"
+ android:paddingRight="4dp"
+ android:paddingTop="4dp"
+ android:paddingBottom="4dp"
+ android:id="@+id/imageView"
+ android:src="@drawable/ic_action_person" />
+
+ <TextView
+ android:id="@+id/api_accounts_adapter_item_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="8dp"
+ android:text="Account Name"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+</LinearLayout> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/api_app_settings_activity.xml b/OpenKeychain/src/main/res/layout/api_app_settings_activity.xml
new file mode 100644
index 000000000..1377acf0e
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/api_app_settings_activity.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:padding="16dp"
+ android:orientation="vertical">
+
+ <fragment
+ android:id="@+id/api_app_settings_fragment"
+ android:name="org.sufficientlysecure.keychain.remote.ui.AppSettingsFragment"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ tools:layout="@layout/api_app_settings_fragment" />
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/api_settings_accounts"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+ <FrameLayout
+ android:id="@+id/api_accounts_list_fragment"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical" />
+
+ </LinearLayout>
+</ScrollView>
diff --git a/OpenKeychain/src/main/res/layout/api_app_settings_fragment.xml b/OpenKeychain/src/main/res/layout/api_app_settings_fragment.xml
new file mode 100644
index 000000000..96271d418
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/api_app_settings_fragment.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ xmlns:custom="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="?android:attr/listPreferredItemHeight"
+ android:layout_marginBottom="4dp"
+ android:layout_marginTop="4dp"
+ android:gravity="center_horizontal"
+ android:orientation="horizontal">
+
+ <ImageView
+ android:id="@+id/api_app_settings_app_icon"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_alignParentBottom="true"
+ android:layout_alignParentTop="true"
+ android:layout_marginRight="6dp"
+ android:src="@drawable/icon" />
+
+ <TextView
+ android:id="@+id/api_app_settings_app_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ android:layout_toRightOf="@+id/api_app_settings_app_icon"
+ android:gravity="center_vertical"
+ android:orientation="vertical"
+ android:text="Name (set in-code)"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+ </RelativeLayout>
+
+ <org.sufficientlysecure.keychain.ui.widget.FoldableLinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ custom:foldedLabel="@string/api_settings_show_info"
+ custom:unFoldedLabel="@string/api_settings_hide_info"
+ custom:foldedIcon="fa-chevron-right"
+ custom:unFoldedIcon="fa-chevron-down">
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/api_settings_package_name"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+ <TextView
+ android:id="@+id/api_app_settings_package_name"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="com.example"
+ android:textAppearance="?android:attr/textAppearanceSmall" />
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/api_settings_package_signature"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+ <TextView
+ android:id="@+id/api_app_settings_package_signature"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="Base64 encoded signature"
+ android:textAppearance="?android:attr/textAppearanceSmall" />
+
+ </org.sufficientlysecure.keychain.ui.widget.FoldableLinearLayout>
+
+</LinearLayout> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/api_apps_adapter_list_item.xml b/OpenKeychain/src/main/res/layout/api_apps_adapter_list_item.xml
new file mode 100644
index 000000000..e70a79589
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/api_apps_adapter_list_item.xml
@@ -0,0 +1,26 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:paddingTop="4dp"
+ android:paddingBottom="4dp">
+
+ <ImageView
+ android:id="@+id/api_apps_adapter_item_icon"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_marginLeft="8dp"
+ android:layout_centerVertical="true"
+ android:src="@drawable/icon" />
+
+ <TextView
+ android:id="@+id/api_apps_adapter_item_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="8dp"
+ android:text="Application Name"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_centerVertical="true"
+ android:layout_toRightOf="@+id/api_apps_adapter_item_icon" />
+
+</RelativeLayout> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/api_apps_list_activity.xml b/OpenKeychain/src/main/res/layout/api_apps_list_activity.xml
new file mode 100644
index 000000000..9f95e9f3b
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/api_apps_list_activity.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/drawer_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" >
+
+ <include layout="@layout/api_apps_list_content"/>
+
+ <include layout="@layout/drawer_list" />
+
+</android.support.v4.widget.DrawerLayout> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/api_apps_list_content.xml b/OpenKeychain/src/main/res/layout/api_apps_list_content.xml
new file mode 100644
index 000000000..9f9b99045
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/api_apps_list_content.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/content_frame"
+ android:layout_marginLeft="@dimen/drawer_content_padding"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <fragment
+ android:id="@+id/crypto_consumers_list_fragment"
+ android:name="org.sufficientlysecure.keychain.remote.ui.AppsListFragment"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
+</FrameLayout> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/api_remote_create_account.xml b/OpenKeychain/src/main/res/layout/api_remote_create_account.xml
new file mode 100644
index 000000000..3aee9094f
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/api_remote_create_account.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="16dp"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/api_remote_create_account_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingBottom="3dip"
+ android:text="@string/api_create_account_text"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+ <fragment
+ android:id="@+id/api_account_settings_fragment"
+ android:name="org.sufficientlysecure.keychain.remote.ui.AccountSettingsFragment"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ tools:layout="@layout/api_app_settings_fragment" />
+
+ </LinearLayout>
+</ScrollView>
diff --git a/OpenKeychain/src/main/res/layout/api_remote_error_message.xml b/OpenKeychain/src/main/res/layout/api_remote_error_message.xml
new file mode 100644
index 000000000..48aa89d4f
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/api_remote_error_message.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical" >
+
+ <org.sufficientlysecure.htmltextview.HtmlTextView
+ android:id="@+id/api_app_error_message_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="8dp"
+ android:paddingBottom="0dip"
+ android:text="Set in-code!"
+ android:textAppearance="?android:attr/textAppearanceLarge" />
+
+</LinearLayout> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/api_remote_register_app.xml b/OpenKeychain/src/main/res/layout/api_remote_register_app.xml
new file mode 100644
index 000000000..f85f3b8f7
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/api_remote_register_app.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="16dp"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/api_register_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingBottom="3dip"
+ android:text="@string/api_register_text"
+ android:textAppearance="?android:attr/textAppearanceLarge" />
+
+ <fragment
+ android:id="@+id/api_app_settings_fragment"
+ android:name="org.sufficientlysecure.keychain.remote.ui.AppSettingsFragment"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ tools:layout="@layout/api_app_settings_fragment" />
+
+ </LinearLayout>
+</ScrollView>
diff --git a/OpenKeychain/src/main/res/layout/api_remote_select_pub_keys.xml b/OpenKeychain/src/main/res/layout/api_remote_select_pub_keys.xml
new file mode 100644
index 000000000..a10592607
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/api_remote_select_pub_keys.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical" >
+
+ <org.sufficientlysecure.htmltextview.HtmlTextView
+ android:id="@+id/api_select_pub_keys_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="8dp"
+ android:paddingBottom="0dip"
+ android:text="Set in-code!"
+ android:textAppearance="?android:attr/textAppearanceSmall" />
+
+ <FrameLayout
+ android:id="@+id/api_select_pub_keys_fragment_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+</LinearLayout> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/certify_key_activity.xml b/OpenKeychain/src/main/res/layout/certify_key_activity.xml
new file mode 100644
index 000000000..3fa0468de
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/certify_key_activity.xml
@@ -0,0 +1,156 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent" >
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingLeft="16dp"
+ android:paddingRight="16dp"
+ android:orientation="vertical" >
+
+ <TextView
+ style="@style/SectionHeader"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="4dp"
+ android:layout_marginTop="14dp"
+ android:text="@string/section_certification_key" />
+
+ <fragment
+ android:id="@+id/sign_key_select_key_fragment"
+ android:name="org.sufficientlysecure.keychain.ui.SelectSecretKeyLayoutFragment"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="4dp"
+ android:layout_marginTop="4dp"
+ tools:layout="@layout/select_secret_key_layout_fragment" />
+
+ <TextView
+ style="@style/SectionHeader"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="4dp"
+ android:layout_marginTop="14dp"
+ android:text="KEY TO SIGN" />
+
+ <TableLayout
+ android:layout_width="wrap_content"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:shrinkColumns="1">
+
+ <TableRow
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:paddingRight="10dip"
+ android:text="@string/label_key_id" />
+
+ <TextView
+ android:id="@+id/key_id"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingRight="5dip"
+ android:text=""
+ android:typeface="monospace" />
+ </TableRow>
+
+ <TableRow
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:paddingRight="10dip"
+ android:text="@string/label_main_user_id" />
+
+ <TextView
+ android:id="@+id/main_user_id"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:typeface="monospace" />
+
+ </TableRow>
+
+ <TableRow
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:paddingRight="10dip"
+ android:text="@string/label_fingerprint" />
+
+ <TextView
+ android:id="@+id/fingerprint"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:typeface="monospace" />
+
+ </TableRow>
+
+ </TableLayout>
+
+ <TextView
+ style="@style/SectionHeader"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="4dp"
+ android:layout_marginTop="14dp"
+ android:text="@string/section_uids_to_sign" />
+
+ <org.sufficientlysecure.keychain.ui.widget.FixedListView
+ android:id="@+id/user_ids"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:descendantFocusability="blocksDescendants" />
+
+ <TextView
+ style="@style/SectionHeader"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="4dp"
+ android:layout_marginTop="14dp"
+ android:text="@string/section_upload_key" />
+
+ <CheckBox
+ android:id="@+id/sign_key_upload_checkbox"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="4dp"
+ android:layout_marginTop="4dp"
+ android:checked="false"
+ android:text="@string/label_send_key" />
+
+ <Spinner
+ android:id="@+id/sign_key_keyserver"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="4dp"
+ android:layout_marginTop="4dp"
+ android:enabled="false" />
+
+ <com.beardedhen.androidbootstrap.BootstrapButton
+ android:id="@+id/sign_key_sign_button"
+ android:layout_width="match_parent"
+ android:layout_height="60dp"
+ android:layout_marginBottom="4dp"
+ android:layout_marginTop="14dp"
+ android:text="@string/btn_certify"
+ bootstrapbutton:bb_icon_left="fa-pencil"
+ bootstrapbutton:bb_type="info" />
+ </LinearLayout>
+
+</ScrollView> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/create_key_dialog.xml b/OpenKeychain/src/main/res/layout/create_key_dialog.xml
new file mode 100644
index 000000000..57a1b865f
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/create_key_dialog.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" >
+
+ <TableLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingLeft="16dp"
+ android:paddingRight="16dp"
+ android:stretchColumns="1" >
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:padding="4dp"
+ android:text="@string/key_creation_el_gamal_info" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:padding="4dp"
+ android:text="@string/key_creation_weak_rsa_info" />
+
+ <TableRow>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:padding="4dp"
+ android:text="@string/label_algorithm" />
+
+ <Spinner
+ android:id="@+id/create_key_algorithm"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="4dp" />
+ </TableRow>
+
+ <TableRow>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:padding="4dp"
+ android:text="@string/label_key_size" />
+
+ <Spinner
+ android:id="@+id/create_key_size"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="right"
+ android:padding="4dp" />
+ </TableRow>
+ </TableLayout>
+
+</ScrollView> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/decrypt_activity.xml b/OpenKeychain/src/main/res/layout/decrypt_activity.xml
new file mode 100644
index 000000000..c4709a67e
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/decrypt_activity.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/drawer_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <include layout="@layout/decrypt_content"/>
+
+ <include layout="@layout/drawer_list" />
+
+</android.support.v4.widget.DrawerLayout> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/decrypt_content.xml b/OpenKeychain/src/main/res/layout/decrypt_content.xml
new file mode 100644
index 000000000..a496d8b9d
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/decrypt_content.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/content_frame"
+ android:layout_marginLeft="@dimen/drawer_content_padding"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <android.support.v4.view.ViewPager
+ android:id="@+id/decrypt_pager"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <android.support.v4.view.PagerTabStrip
+ android:id="@+id/decrypt_pager_tab_strip"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top"
+ android:background="@color/emphasis"
+ android:textColor="#fff" />
+ </android.support.v4.view.ViewPager>
+
+</LinearLayout> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/decrypt_file_fragment.xml b/OpenKeychain/src/main/res/layout/decrypt_file_fragment.xml
new file mode 100644
index 000000000..633c9c832
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/decrypt_file_fragment.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:fillViewport="true">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="4dp"
+ android:paddingLeft="16dp"
+ android:paddingRight="16dp"
+ android:orientation="vertical">
+
+ <include layout="@layout/decrypt_signature_include" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <EditText
+ android:id="@+id/decrypt_file_filename"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:gravity="top|left"
+ android:inputType="textMultiLine|textUri"
+ android:lines="4"
+ android:maxLines="10"
+ android:minLines="2"
+ android:scrollbars="vertical" />
+
+ <com.beardedhen.androidbootstrap.BootstrapButton
+ android:id="@+id/decrypt_file_browse"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="4dp"
+ bootstrapbutton:bb_icon_left="fa-folder-open"
+ bootstrapbutton:bb_roundedCorners="true"
+ bootstrapbutton:bb_size="default"
+ bootstrapbutton:bb_type="default" />
+ </LinearLayout>
+
+ <CheckBox
+ android:id="@+id/decrypt_file_delete_after_decryption"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/label_delete_after_decryption" />
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <TextView
+ style="@style/SectionHeader"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="4dp"
+ android:text="@string/section_decrypt_verify"
+ android:layout_above="@+id/decrypt_file_action_decrypt"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentStart="true" />
+
+ <com.beardedhen.androidbootstrap.BootstrapButton
+ android:id="@+id/decrypt_file_action_decrypt"
+ android:layout_width="match_parent"
+ android:layout_height="50dp"
+ android:layout_marginTop="4dp"
+ android:layout_marginBottom="4dp"
+ android:text="@string/btn_decrypt_verify"
+ bootstrapbutton:bb_icon_left="fa-unlock"
+ bootstrapbutton:bb_type="info"
+ android:layout_alignParentBottom="true"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentStart="true" />
+
+ </RelativeLayout>
+ </LinearLayout>
+</ScrollView> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/decrypt_message_fragment.xml b/OpenKeychain/src/main/res/layout/decrypt_message_fragment.xml
new file mode 100644
index 000000000..dfe1bf64a
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/decrypt_message_fragment.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:fillViewport="true">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="4dp"
+ android:paddingLeft="16dp"
+ android:paddingRight="16dp"
+ android:orientation="vertical">
+
+ <include layout="@layout/decrypt_signature_include" />
+
+ <EditText
+ android:id="@+id/message"
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:gravity="top"
+ android:hint="@string/decrypt_content_edit_text_hint"
+ android:inputType="text|textCapSentences|textMultiLine|textLongMessage"
+ android:scrollHorizontally="true"
+ android:layout_weight="1" />
+
+ <TextView
+ style="@style/SectionHeader"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/section_decrypt_verify"
+ android:id="@+id/decrypt_message_section" />
+
+ <LinearLayout
+ android:id="@+id/decrypt_buttons"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <com.beardedhen.androidbootstrap.BootstrapButton
+ android:id="@+id/action_decrypt"
+ android:layout_width="match_parent"
+ android:layout_height="50dp"
+ android:layout_marginRight="4dp"
+ android:layout_marginTop="4dp"
+ android:layout_marginBottom="4dp"
+ android:layout_weight="1"
+ android:text="@string/btn_decrypt_verify"
+ bootstrapbutton:bb_icon_left="fa-unlock"
+ bootstrapbutton:bb_type="info" />
+
+ <com.beardedhen.androidbootstrap.BootstrapButton
+ android:id="@+id/action_decrypt_from_clipboard"
+ android:layout_width="match_parent"
+ android:layout_height="50dp"
+ android:layout_marginLeft="4dp"
+ android:layout_marginTop="4dp"
+ android:layout_marginBottom="4dp"
+ android:layout_weight="1"
+ android:text="@string/btn_decrypt_verify_clipboard"
+ bootstrapbutton:bb_icon_left="fa-clipboard"
+ bootstrapbutton:bb_type="info" />
+ </LinearLayout>
+ </LinearLayout>
+</ScrollView>
diff --git a/OpenKeychain/src/main/res/layout/decrypt_signature_include.xml b/OpenKeychain/src/main/res/layout/decrypt_signature_include.xml
new file mode 100644
index 000000000..3e0d35c9b
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/decrypt_signature_include.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/signature"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clickable="true"
+ android:orientation="horizontal"
+ android:padding="4dp"
+ android:paddingLeft="10dp"
+ android:paddingRight="10dp">
+
+ <RelativeLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/relativeLayout">
+
+ <ImageView
+ android:id="@+id/ic_signature"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/signed_large" />
+
+ <ImageView
+ android:id="@+id/ic_signature_status"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/overlay_error" />
+ </RelativeLayout>
+
+ <TextView
+ android:id="@+id/mainUserId"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="left"
+ android:text="@string/label_main_user_id"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_toRightOf="@+id/relativeLayout" />
+
+ <TextView
+ android:id="@+id/mainUserIdRest"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="left"
+ android:text="Main User Id Rest"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:layout_below="@+id/mainUserId"
+ android:layout_toRightOf="@+id/relativeLayout" />
+
+ <com.beardedhen.androidbootstrap.BootstrapButton
+ android:id="@+id/lookup_key"
+ android:layout_width="wrap_content"
+ android:layout_height="50dp"
+ android:padding="4dp"
+ android:text="@string/btn_lookup_key"
+ bootstrapbutton:bb_icon_left="fa-download"
+ bootstrapbutton:bb_type="info"
+ bootstrapbutton:bb_size="small"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentRight="true"
+ android:layout_alignParentEnd="true" />
+</RelativeLayout> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/drawer_list.xml b/OpenKeychain/src/main/res/layout/drawer_list.xml
new file mode 100644
index 000000000..ab00c0073
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/drawer_list.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ android:layout_gravity="start" tells DrawerLayout to treat
+ this as a sliding drawer on the left side for left-to-right
+ languages and on the right side for right-to-left languages.
+ The drawer is given a fixed width in dp and extends the full height of
+ the container. A solid background is used for contrast
+ with the content view.
+-->
+<ListView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/left_drawer"
+ android:layout_width="@dimen/drawer_size"
+ android:layout_height="match_parent"
+ android:layout_gravity="start"
+ android:background="@color/white"
+ android:choiceMode="singleChoice"
+ android:divider="@color/bg_gray"
+ android:dividerHeight="1dp" />
diff --git a/OpenKeychain/src/main/res/layout/drawer_list_item.xml b/OpenKeychain/src/main/res/layout/drawer_list_item.xml
new file mode 100644
index 000000000..72f4fec50
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/drawer_list_item.xml
@@ -0,0 +1,33 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:fontawesometext="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <com.beardedhen.androidbootstrap.FontAwesomeText
+ android:id="@+id/drawer_item_icon"
+ android:layout_width="30dp"
+ android:layout_height="wrap_content"
+ android:gravity="center_vertical"
+ android:textSize="24sp"
+ android:layout_marginLeft="8dp"
+ fontawesometext:fa_icon="fa-github"
+ android:layout_centerVertical="true"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentStart="true" />
+
+ <TextView
+ android:id="@+id/drawer_item_text"
+ android:text="Test"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_vertical"
+ android:layout_marginLeft="8dp"
+ android:paddingBottom="16dp"
+ android:paddingRight="16dp"
+ android:paddingTop="16dp"
+ android:textAppearance="@android:style/TextAppearance.Medium"
+ android:textColor="#111"
+ android:layout_alignParentTop="true"
+ android:layout_toRightOf="@+id/drawer_item_icon" />
+
+</RelativeLayout>
diff --git a/OpenKeychain/src/main/res/layout/edit_key_activity.xml b/OpenKeychain/src/main/res/layout/edit_key_activity.xml
new file mode 100644
index 000000000..fc4422cf0
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/edit_key_activity.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:fillViewport="true"
+ android:orientation="vertical" >
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingLeft="16dp"
+ android:paddingRight="16dp" >
+
+ <TextView
+ style="@style/SectionHeader"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="4dp"
+ android:text="@string/label_passphrase" />
+
+ <CheckBox
+ android:id="@+id/edit_key_no_passphrase"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/label_no_passphrase" />
+
+ <com.beardedhen.androidbootstrap.BootstrapButton
+ android:id="@+id/edit_key_btn_change_passphrase"
+ android:layout_width="match_parent"
+ android:layout_height="60dp"
+ android:padding="4dp"
+ android:text="@string/btn_set_passphrase"
+ bootstrapbutton:bb_icon_left="fa-pencil"
+ bootstrapbutton:bb_type="info" />
+
+ <LinearLayout
+ android:id="@+id/edit_key_container"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" >
+ </LinearLayout>
+ </LinearLayout>
+
+</ScrollView> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/edit_key_key_item.xml b/OpenKeychain/src/main/res/layout/edit_key_key_item.xml
new file mode 100644
index 000000000..090115d62
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/edit_key_key_item.xml
@@ -0,0 +1,176 @@
+<?xml version="1.0" encoding="utf-8"?>
+<org.sufficientlysecure.keychain.ui.widget.KeyEditor xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" >
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+
+ <TableLayout
+ android:id="@+id/table_keylayout"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:stretchColumns="1" >
+
+ <TableRow>
+
+ <TextView
+ android:id="@+id/label_keyId"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:paddingRight="10dip"
+ android:text="@string/label_key_id" />
+
+ <TextView
+ android:id="@+id/keyId"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingRight="5dip"
+ android:text="00000000 00000000"
+ android:typeface="monospace" />
+ </TableRow>
+
+ <TableRow>
+
+ <TextView
+ android:id="@+id/label_algorithm"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:paddingRight="10dip"
+ android:text="@string/label_algorithm" />
+
+ <TextView
+ android:id="@+id/algorithm"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingRight="5dip"
+ android:text="@string/label_name" />
+ </TableRow>
+
+ <TableRow>
+
+ <TextView
+ android:id="@+id/label_creation"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:paddingRight="10dip"
+ android:text="@string/label_creation" />
+
+ <TextView
+ android:id="@+id/creation"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+ </TableRow>
+
+ <TableRow>
+
+ <TextView
+ android:id="@+id/label_expiry"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:paddingRight="10dip"
+ android:text="@string/label_expiry" />
+
+ <com.beardedhen.androidbootstrap.BootstrapButton
+ android:id="@+id/expiry"
+ android:layout_width="match_parent"
+ android:layout_height="40dp"
+ android:text="@string/none"
+ bootstrapbutton:bb_size="small"
+ bootstrapbutton:bb_type="default" />
+ </TableRow>
+
+ <TableRow
+ android:id="@+id/row_certify">
+
+ <TextView
+ android:id="@+id/label_usage"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:paddingRight="10dip"
+ android:text="@string/label_usage" />
+ <CheckBox
+ android:id="@+id/chkCertify"
+ android:enabled = "false"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/flag_certify" />
+ </TableRow>
+
+ <TableRow
+ android:id="@+id/row_sign">
+
+ <TextView
+ android:id="@+id/label_usage2"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:paddingRight="10dip"
+ android:text="@string/label_usage" />
+ <CheckBox
+ android:id="@+id/chkSign"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/flag_sign" />
+ </TableRow>
+
+ <TableRow
+ android:id="@+id/row_encrypt">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:paddingRight="10dip" />
+ <CheckBox
+ android:id="@+id/chkEncrypt"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/flag_encrypt" />
+ </TableRow>
+
+ <TableRow
+ android:id="@+id/row_authenticate">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:paddingRight="10dip" />
+ <CheckBox
+ android:id="@+id/chkAuthenticate"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/flag_authenticate" />
+ </TableRow>
+ </TableLayout>
+
+ <com.beardedhen.androidbootstrap.BootstrapButton
+ android:id="@+id/delete"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="10dp"
+ bootstrapbutton:bb_icon_left="fa-minus"
+ bootstrapbutton:bb_roundedCorners="true"
+ bootstrapbutton:bb_size="small"
+ bootstrapbutton:bb_type="danger" />
+ </LinearLayout>
+
+ <View
+ android:id="@+id/separator"
+ android:layout_width="match_parent"
+ android:layout_height="1dip"
+ android:background="?android:attr/listDivider" />
+
+</org.sufficientlysecure.keychain.ui.widget.KeyEditor>
+
diff --git a/OpenKeychain/src/main/res/layout/edit_key_section.xml b/OpenKeychain/src/main/res/layout/edit_key_section.xml
new file mode 100644
index 000000000..9f10ff8c1
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/edit_key_section.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<org.sufficientlysecure.keychain.ui.widget.SectionView xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" >
+
+ <LinearLayout
+ android:id="@+id/header"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_vertical"
+ android:orientation="horizontal" >
+
+ <TextView
+ android:id="@+id/title"
+ style="@style/SectionHeader"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:ellipsize="marquee"
+ android:fadingEdge="horizontal"
+ android:singleLine="true"
+ android:text="Section Name" />
+
+ <com.beardedhen.androidbootstrap.BootstrapButton
+ android:id="@+id/plusbutton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="10dp"
+ bootstrapbutton:bb_icon_left="fa-plus"
+ bootstrapbutton:bb_roundedCorners="true"
+ bootstrapbutton:bb_size="small"
+ bootstrapbutton:bb_type="success" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/editors"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingBottom="6dip" />
+
+</org.sufficientlysecure.keychain.ui.widget.SectionView> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/edit_key_user_id_item.xml b/OpenKeychain/src/main/res/layout/edit_key_user_id_item.xml
new file mode 100644
index 000000000..a8d1dc674
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/edit_key_user_id_item.xml
@@ -0,0 +1,99 @@
+<?xml version="1.0" encoding="utf-8"?>
+<org.sufficientlysecure.keychain.ui.widget.UserIdEditor xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" >
+
+ <RadioButton
+ android:id="@+id/isMainUserId"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/label_main_user_id" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+
+ <TableLayout
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1" >
+
+ <TableRow>
+
+ <TextView
+ android:id="@+id/label_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:paddingRight="5dip"
+ android:text="@string/label_name" />
+
+ <EditText
+ android:id="@+id/name"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:inputType="textPersonName|textCapWords" />
+ </TableRow>
+
+ <TableRow>
+
+ <TextView
+ android:id="@+id/label_email"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:paddingRight="5dip"
+ android:text="@string/label_email" />
+
+ <AutoCompleteTextView
+ android:id="@+id/email"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:inputType="textEmailAddress" />
+ </TableRow>
+
+ <TableRow>
+
+ <TextView
+ android:id="@+id/label_comment"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:paddingRight="5dip"
+ android:text="@string/label_comment" />
+
+ <EditText
+ android:id="@+id/comment"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:inputType="text"/>
+ </TableRow>
+ </TableLayout>
+
+ <com.beardedhen.androidbootstrap.BootstrapButton
+ android:id="@+id/delete"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_margin="10dp"
+ android:layout_marginLeft="4dip"
+ android:layout_marginRight="6dip"
+ bootstrapbutton:bb_icon_left="fa-minus"
+ bootstrapbutton:bb_roundedCorners="true"
+ bootstrapbutton:bb_size="small"
+ bootstrapbutton:bb_type="danger" />
+ </LinearLayout>
+
+ <View
+ android:id="@+id/separator"
+ android:layout_width="match_parent"
+ android:layout_height="1dip"
+ android:background="?android:attr/listDivider" />
+
+</org.sufficientlysecure.keychain.ui.widget.UserIdEditor> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/encrypt_activity.xml b/OpenKeychain/src/main/res/layout/encrypt_activity.xml
new file mode 100644
index 000000000..6484c9b7b
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/encrypt_activity.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
+ xmlns:fontawesometext="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/drawer_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <include layout="@layout/encrypt_content"/>
+
+ <include layout="@layout/drawer_list" />
+
+</android.support.v4.widget.DrawerLayout> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/encrypt_asymmetric_fragment.xml b/OpenKeychain/src/main/res/layout/encrypt_asymmetric_fragment.xml
new file mode 100644
index 000000000..fa1b03889
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/encrypt_asymmetric_fragment.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingTop="4dp"
+ android:paddingBottom="4dp"
+ android:paddingRight="16dp"
+ android:paddingLeft="16dp">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <CheckBox
+ android:id="@+id/sign"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:text="@string/label_sign" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingLeft="16dp"
+ android:paddingRight="4dip">
+
+ <TextView
+ android:id="@+id/mainUserId"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="right"
+ android:ellipsize="end"
+ android:singleLine="true"
+ android:text=""
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+ <TextView
+ android:id="@+id/mainUserIdRest"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="right"
+ android:ellipsize="end"
+ android:singleLine="true"
+ android:text=""
+ android:textAppearance="?android:attr/textAppearanceSmall" />
+ </LinearLayout>
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <TextView
+ android:id="@+id/label_selectPublicKeys"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_weight="1"
+ android:text="@string/label_select_public_keys"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+ <com.beardedhen.androidbootstrap.BootstrapButton
+ android:id="@+id/btn_selectEncryptKeys"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_margin="4dp"
+ android:text="@string/select_keys_button_default"
+ bootstrapbutton:bb_icon_left="fa-user"
+ bootstrapbutton:bb_size="default"
+ bootstrapbutton:bb_type="default" />
+ </LinearLayout>
+</LinearLayout> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/encrypt_content.xml b/OpenKeychain/src/main/res/layout/encrypt_content.xml
new file mode 100644
index 000000000..e719d07e1
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/encrypt_content.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/content_frame"
+ android:layout_marginLeft="@dimen/drawer_content_padding"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <android.support.v4.view.ViewPager
+ android:id="@+id/encrypt_pager_mode"
+ android:layout_width="match_parent"
+ android:layout_height="150dp">
+
+ <android.support.v4.view.PagerTabStrip
+ android:id="@+id/encrypt_pager_tab_strip_mode"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top"
+ android:background="@color/emphasis"
+ android:textColor="#fff" />
+ </android.support.v4.view.ViewPager>
+
+ <android.support.v4.view.ViewPager
+ android:id="@+id/encrypt_pager_content"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <android.support.v4.view.PagerTabStrip
+ android:id="@+id/encrypt_pager_tab_strip_content"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top"
+ android:background="@color/emphasis"
+ android:textColor="#fff" />
+ </android.support.v4.view.ViewPager>
+
+</LinearLayout> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/encrypt_content_adv_settings.xml b/OpenKeychain/src/main/res/layout/encrypt_content_adv_settings.xml
new file mode 100644
index 000000000..ac990653a
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/encrypt_content_adv_settings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <TextView
+ android:id="@+id/label_fileCompression"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_weight="1"
+ android:paddingRight="10dip"
+ android:text="@string/label_file_compression"
+ android:textAppearance="?android:attr/textAppearanceSmall"/>
+
+ <Spinner
+ android:id="@+id/fileCompression"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <CheckBox
+ android:id="@+id/deleteAfterEncryption"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:text="@string/label_delete_after_encryption"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <CheckBox
+ android:id="@+id/shareAfterEncryption"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:text="@string/label_share_after_encryption"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <CheckBox
+ android:id="@+id/asciiArmor"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:text="@string/label_ascii_armor"/>
+ </LinearLayout>
+</merge>
diff --git a/OpenKeychain/src/main/res/layout/encrypt_file_fragment.xml b/OpenKeychain/src/main/res/layout/encrypt_file_fragment.xml
new file mode 100644
index 000000000..efc4b4641
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/encrypt_file_fragment.xml
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
+ xmlns:custom="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:fillViewport="true">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="4dp"
+ android:paddingLeft="16dp"
+ android:paddingRight="16dp"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <EditText
+ android:id="@+id/filename"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:gravity="top|left"
+ android:inputType="textMultiLine|textUri"
+ android:lines="4"
+ android:maxLines="10"
+ android:minLines="2"
+ android:scrollbars="vertical" />
+
+ <com.beardedhen.androidbootstrap.BootstrapButton
+ android:id="@+id/btn_browse"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="4dp"
+ bootstrapbutton:bb_icon_left="fa-folder-open"
+ bootstrapbutton:bb_roundedCorners="true"
+ bootstrapbutton:bb_size="default"
+ bootstrapbutton:bb_type="default" />
+ </LinearLayout>
+
+ <org.sufficientlysecure.keychain.ui.widget.FoldableLinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ custom:foldedLabel="@string/btn_encryption_advanced_settings_show"
+ custom:unFoldedLabel="@string/btn_encryption_advanced_settings_hide"
+ custom:foldedIcon="fa-chevron-right"
+ custom:unFoldedIcon="fa-chevron-down">
+
+ <include layout="@layout/encrypt_content_adv_settings" />
+
+ </org.sufficientlysecure.keychain.ui.widget.FoldableLinearLayout>
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <TextView
+ style="@style/SectionHeader"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="4dp"
+ android:text="@string/section_encrypt_and_or_sign"
+ android:layout_above="@+id/action_encrypt_file"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentStart="true" />
+
+ <com.beardedhen.androidbootstrap.BootstrapButton
+ android:id="@+id/action_encrypt_file"
+ android:layout_width="match_parent"
+ android:layout_height="50dp"
+ android:layout_marginTop="4dp"
+ android:layout_marginBottom="4dp"
+ android:text="@string/btn_encrypt_file"
+ bootstrapbutton:bb_icon_left="fa-lock"
+ bootstrapbutton:bb_type="info"
+ android:layout_alignParentBottom="true"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentStart="true" />
+
+ </RelativeLayout>
+ </LinearLayout>
+</ScrollView> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/encrypt_message_fragment.xml b/OpenKeychain/src/main/res/layout/encrypt_message_fragment.xml
new file mode 100644
index 000000000..1fa338426
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/encrypt_message_fragment.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:fillViewport="true">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="4dp"
+ android:paddingLeft="16dp"
+ android:paddingRight="16dp"
+ android:orientation="vertical">
+
+ <EditText
+ android:id="@+id/message"
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:gravity="top"
+ android:inputType="text|textCapSentences|textMultiLine|textLongMessage"
+ android:hint="@string/encrypt_content_edit_text_hint"
+ android:layout_weight="1" />
+
+ <TextView
+ style="@style/SectionHeader"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/section_encrypt_and_or_sign" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <com.beardedhen.androidbootstrap.BootstrapButton
+ android:id="@+id/action_encrypt_share"
+ android:layout_width="match_parent"
+ android:layout_height="50dp"
+ android:layout_marginRight="4dp"
+ android:layout_marginTop="4dp"
+ android:layout_marginBottom="4dp"
+ android:layout_weight="1"
+ android:text="@string/btn_share"
+ bootstrapbutton:bb_icon_left="fa-share-square"
+ bootstrapbutton:bb_type="info" />
+
+ <com.beardedhen.androidbootstrap.BootstrapButton
+ android:id="@+id/action_encrypt_clipboard"
+ android:layout_width="match_parent"
+ android:layout_height="50dp"
+ android:layout_marginLeft="4dp"
+ android:layout_marginTop="4dp"
+ android:layout_marginBottom="4dp"
+ android:layout_weight="1"
+ android:text="@string/btn_clipboard"
+ bootstrapbutton:bb_icon_left="fa-clipboard"
+ bootstrapbutton:bb_type="info" />
+
+ </LinearLayout>
+ </LinearLayout>
+</ScrollView>
diff --git a/OpenKeychain/src/main/res/layout/encrypt_symmetric_fragment.xml b/OpenKeychain/src/main/res/layout/encrypt_symmetric_fragment.xml
new file mode 100644
index 000000000..89381e499
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/encrypt_symmetric_fragment.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingTop="4dp"
+ android:paddingBottom="4dp"
+ android:paddingLeft="16dp"
+ android:paddingRight="16dp"
+ android:orientation="vertical">
+
+ <TableLayout
+ android:id="@+id/modeSymmetric"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:stretchColumns="1"
+ android:layout_centerVertical="true">
+
+ <TableRow>
+
+ <TextView
+ android:id="@+id/label_passphrase"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingRight="8dp"
+ android:text="@string/label_passphrase"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+ <EditText
+ android:id="@+id/passphrase"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:inputType="textPassword" />
+ </TableRow>
+
+ <TableRow>
+
+ <TextView
+ android:id="@+id/label_passphraseAgain"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingRight="8dp"
+ android:text="@string/label_passphrase_again"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+ <EditText
+ android:id="@+id/passphraseAgain"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:inputType="textPassword" />
+ </TableRow>
+ </TableLayout>
+</RelativeLayout> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/file_dialog.xml b/OpenKeychain/src/main/res/layout/file_dialog.xml
new file mode 100644
index 000000000..83d697001
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/file_dialog.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingLeft="5dip"
+ android:paddingRight="5dip">
+
+ <TextView
+ android:id="@+id/message"
+ android:layout_marginBottom="8dp"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <EditText
+ android:id="@+id/input"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:gravity="top|left"
+ android:inputType="textMultiLine|textUri"
+ android:lines="4"
+ android:maxLines="10"
+ android:minLines="2"
+ android:scrollbars="vertical" />
+
+ <com.beardedhen.androidbootstrap.BootstrapButton
+ android:id="@+id/btn_browse"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_margin="4dp"
+ android:contentDescription="@string/filemanager_title_open"
+ bootstrapbutton:bb_icon_left="fa-folder-open"
+ bootstrapbutton:bb_roundedCorners="true"
+ bootstrapbutton:bb_size="default"
+ bootstrapbutton:bb_type="default" />
+ </LinearLayout>
+
+ <CheckBox
+ android:id="@+id/checkbox"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+</LinearLayout> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/foldable_linearlayout.xml b/OpenKeychain/src/main/res/layout/foldable_linearlayout.xml
new file mode 100644
index 000000000..2b863d52b
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/foldable_linearlayout.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:fontawesometext="http://schemas.android.com/apk/res-auto"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <LinearLayout
+ android:id="@+id/foldableControl"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:clickable="true">
+
+ <com.beardedhen.androidbootstrap.FontAwesomeText
+ android:id="@+id/foldableIcon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginRight="10dp"
+ android:textSize="12sp"
+ android:paddingTop="@dimen/padding_medium"
+ android:paddingBottom="@dimen/padding_medium"
+ fontawesometext:fa_icon="fa-chevron-right"/>
+
+ <TextView
+ android:id="@+id/foldableText"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/none"
+ android:paddingTop="@dimen/padding_medium"
+ android:paddingBottom="@dimen/padding_medium"
+ android:textColor="@color/emphasis"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/foldableContainer"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:visibility="gone"/>
+</LinearLayout> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/help_about_fragment.xml b/OpenKeychain/src/main/res/layout/help_about_fragment.xml
new file mode 100644
index 000000000..71788e720
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/help_about_fragment.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" >
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:isScrollContainer="true"
+ android:orientation="vertical"
+ android:paddingLeft="16dp"
+ android:paddingRight="16dp"
+ android:paddingTop="16dp"
+ android:scrollbars="vertical" >
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal" >
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:orientation="vertical" >
+
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top"
+ android:layout_marginRight="10dp"
+ android:src="@drawable/icon" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical" >
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/app_name"
+ android:textSize="18sp"
+ android:textStyle="bold" />
+
+ <TextView
+ android:id="@+id/help_about_version"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@android:style/TextAppearance.Small" />
+ </LinearLayout>
+ </LinearLayout>
+
+ <org.sufficientlysecure.htmltextview.HtmlTextView
+ android:id="@+id/help_about_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ android:textAppearance="@android:style/TextAppearance.Small" />
+ </LinearLayout>
+
+</ScrollView> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/help_activity.xml b/OpenKeychain/src/main/res/layout/help_activity.xml
new file mode 100644
index 000000000..58e4919dc
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/help_activity.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical" >
+
+ <android.support.v4.view.ViewPager
+ android:id="@+id/pager"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+</LinearLayout> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/import_keys_activity.xml b/OpenKeychain/src/main/res/layout/import_keys_activity.xml
new file mode 100644
index 000000000..c82607a33
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/import_keys_activity.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/drawer_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" >
+
+ <include layout="@layout/import_keys_content"/>
+
+ <include layout="@layout/drawer_list" />
+
+</android.support.v4.widget.DrawerLayout> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/import_keys_clipboard_fragment.xml b/OpenKeychain/src/main/res/layout/import_keys_clipboard_fragment.xml
new file mode 100644
index 000000000..046768495
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/import_keys_clipboard_fragment.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+
+ <com.beardedhen.androidbootstrap.BootstrapButton
+ android:id="@+id/import_clipboard_button"
+ android:layout_width="match_parent"
+ android:layout_height="70dp"
+ android:layout_margin="10dp"
+ android:text="@string/import_clipboard_button"
+ bootstrapbutton:bb_icon_left="fa-clipboard"
+ bootstrapbutton:bb_size="default"
+ bootstrapbutton:bb_type="default" />
+
+</LinearLayout> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/import_keys_content.xml b/OpenKeychain/src/main/res/layout/import_keys_content.xml
new file mode 100644
index 000000000..eb1333704
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/import_keys_content.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/content_frame"
+ android:layout_marginLeft="@dimen/drawer_content_padding"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerHorizontal="true">
+
+ <FrameLayout
+ android:id="@+id/import_navigation_fragment"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true"
+ android:orientation="vertical"
+ android:paddingLeft="4dp"
+ android:paddingRight="4dp" />
+
+ <LinearLayout
+ android:id="@+id/import_footer"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentBottom="true"
+ android:orientation="vertical"
+ android:paddingLeft="16dp"
+ android:paddingRight="16dp">
+
+ <com.beardedhen.androidbootstrap.BootstrapButton
+ android:id="@+id/import_import"
+ android:layout_width="match_parent"
+ android:layout_height="50dp"
+ android:layout_marginTop="4dp"
+ android:layout_marginBottom="4dp"
+ android:text="@string/import_import"
+ bootstrapbutton:bb_icon_left="fa-download"
+ bootstrapbutton:bb_type="info" />
+
+ </LinearLayout>
+
+ <FrameLayout
+ android:id="@+id/import_keys_list_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_above="@+id/import_footer"
+ android:layout_alignParentLeft="true"
+ android:layout_below="@+id/import_navigation_fragment"
+ android:orientation="vertical"
+ android:paddingLeft="4dp"
+ android:paddingRight="4dp" />
+</RelativeLayout> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/import_keys_file_fragment.xml b/OpenKeychain/src/main/res/layout/import_keys_file_fragment.xml
new file mode 100644
index 000000000..0a49571d1
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/import_keys_file_fragment.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <com.beardedhen.androidbootstrap.BootstrapButton
+ android:id="@+id/import_keys_file_browse"
+ android:layout_width="match_parent"
+ android:layout_height="70dp"
+ android:layout_margin="10dp"
+ android:text="@string/filemanager_title_open"
+ android:contentDescription="@string/filemanager_title_open"
+ bootstrapbutton:bb_icon_left="fa-folder-open"
+ bootstrapbutton:bb_size="default"
+ bootstrapbutton:bb_type="default" />
+
+</LinearLayout> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/import_keys_list_entry.xml b/OpenKeychain/src/main/res/layout/import_keys_list_entry.xml
new file mode 100644
index 000000000..f5a39f115
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/import_keys_list_entry.xml
@@ -0,0 +1,110 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
+
+ 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingLeft="3dip"
+ android:paddingRight="?android:attr/scrollbarSize"
+ android:singleLine="true" >
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+
+ <CheckBox
+ android:id="@+id/selected"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:clickable="false"
+ android:focusable="false"
+ android:focusableInTouchMode="false" />
+ <!-- focusable and clickable MUST be false to handle click and longClick in ListView Activity -->
+
+ <LinearLayout
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:orientation="vertical"
+ android:paddingLeft="5dip"
+ android:paddingRight="5dip" >
+
+ <TextView
+ android:id="@+id/mainUserId"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/label_main_user_id"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+ <TextView
+ android:id="@+id/mainUserIdRest"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="&lt;user@somewhere.com>"
+ android:textAppearance="?android:attr/textAppearanceSmall" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="right"
+ android:minWidth="90dip"
+ android:orientation="vertical"
+ android:paddingLeft="3dip" >
+
+ <TextView
+ android:id="@+id/keyId"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="0xBBBBBBBBBBBBBBBB"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:typeface="monospace"
+ android:layout_weight="1" />
+
+ <TextView
+ android:id="@+id/algorithm"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceSmall" />
+
+ <TextView
+ android:id="@+id/fingerprint"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="fingerprint"
+ android:typeface="monospace"
+ android:textAppearance="?android:attr/textAppearanceSmall" />
+
+ <TextView
+ android:id="@+id/status"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="#e00" />
+ </LinearLayout>
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/list"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="36dip"
+ android:orientation="vertical" >
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/OpenKeychain/src/main/res/layout/import_keys_list_entry_user_id.xml b/OpenKeychain/src/main/res/layout/import_keys_list_entry_user_id.xml
new file mode 100644
index 000000000..9d3a4a1ab
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/import_keys_list_entry_user_id.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
+
+ 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.
+-->
+
+<TextView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_marginRight="?android:attr/scrollbarSize"
+ android:singleLine="true"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:paddingRight="3dip">
+
+</TextView>
diff --git a/OpenKeychain/src/main/res/layout/import_keys_nfc_fragment.xml b/OpenKeychain/src/main/res/layout/import_keys_nfc_fragment.xml
new file mode 100644
index 000000000..2a8e74fc2
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/import_keys_nfc_fragment.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:padding="10dp"
+ android:orientation="horizontal" >
+
+ <com.beardedhen.androidbootstrap.BootstrapButton
+ android:id="@+id/import_nfc_button"
+ android:layout_width="wrap_content"
+ android:layout_height="70dp"
+ android:layout_alignParentRight="true"
+ android:layout_alignParentTop="true"
+ android:layout_marginLeft="10dp"
+ android:text="@string/import_nfc_help_button"
+ bootstrapbutton:bb_icon_left="fa-question"
+ bootstrapbutton:bb_size="default"
+ bootstrapbutton:bb_type="default" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ android:layout_toLeftOf="@+id/import_nfc_button"
+ android:text="@string/import_nfc_text" />
+
+</RelativeLayout> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/import_keys_qr_code_fragment.xml b/OpenKeychain/src/main/res/layout/import_keys_qr_code_fragment.xml
new file mode 100644
index 000000000..472c05e65
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/import_keys_qr_code_fragment.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" >
+
+ <com.beardedhen.androidbootstrap.BootstrapButton
+ android:id="@+id/import_qrcode_button"
+ android:layout_width="match_parent"
+ android:layout_height="70dp"
+ android:layout_margin="10dp"
+ android:text="@string/import_qr_scan_button"
+ bootstrapbutton:bb_icon_left="fa-barcode"
+ bootstrapbutton:bb_size="default"
+ bootstrapbutton:bb_type="default" />
+
+ <TextView
+ android:id="@+id/import_qrcode_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingLeft="10dp"
+ android:paddingRight="10dp"
+ android:visibility="gone" />
+
+ <ProgressBar
+ android:id="@+id/import_qrcode_progress"
+ style="?android:attr/progressBarStyleHorizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingLeft="10dp"
+ android:paddingRight="10dp"
+ android:progress="0"
+ android:visibility="gone" />
+
+</LinearLayout> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/import_keys_server_fragment.xml b/OpenKeychain/src/main/res/layout/import_keys_server_fragment.xml
new file mode 100644
index 000000000..2438dd785
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/import_keys_server_fragment.xml
@@ -0,0 +1,45 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:padding="10dp"
+ android:orientation="vertical">
+
+ <Spinner
+ android:id="@+id/import_server_spinner"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <EditText
+ android:id="@+id/import_server_query"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:gravity="top|left"
+ android:hint="@string/hint_public_keys"
+ android:imeOptions="actionSearch"
+ android:inputType="textNoSuggestions"
+ android:singleLine="true"
+ android:lines="1"
+ android:maxLines="1"
+ android:minLines="1"
+ android:layout_gravity="center_vertical" />
+
+ <com.beardedhen.androidbootstrap.BootstrapButton
+ android:id="@+id/import_server_search"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_marginLeft="10dp"
+ bootstrapbutton:bb_icon_left="fa-search"
+ bootstrapbutton:bb_roundedCorners="true"
+ bootstrapbutton:bb_size="default"
+ bootstrapbutton:bb_type="default" />
+ </LinearLayout>
+
+</LinearLayout> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/key_list_activity.xml b/OpenKeychain/src/main/res/layout/key_list_activity.xml
new file mode 100644
index 000000000..fcb376fa8
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/key_list_activity.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/drawer_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" >
+
+ <include layout="@layout/key_list_content"/>
+
+ <include layout="@layout/drawer_list" />
+
+</android.support.v4.widget.DrawerLayout> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/key_list_content.xml b/OpenKeychain/src/main/res/layout/key_list_content.xml
new file mode 100644
index 000000000..e58e42961
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/key_list_content.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/content_frame"
+ android:layout_marginLeft="@dimen/drawer_content_padding"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <fragment
+ android:id="@+id/key_list_fragment"
+ android:name="org.sufficientlysecure.keychain.ui.KeyListFragment"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
+</FrameLayout> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/key_list_fragment.xml b/OpenKeychain/src/main/res/layout/key_list_fragment.xml
new file mode 100644
index 000000000..f2430f213
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/key_list_fragment.xml
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <!--rebuild functionality of ListFragment -->
+ <LinearLayout
+ android:id="@+id/key_list_progress_container"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="visible"
+ android:gravity="center">
+
+ <ProgressBar
+ style="?android:attr/progressBarStyleLarge"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:text=""
+ android:paddingTop="4dip"
+ android:singleLine="true" />
+
+ </LinearLayout>
+
+ <FrameLayout
+ android:id="@+id/key_list_list_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <se.emilsjolander.stickylistheaders.StickyListHeadersListView
+ android:id="@+id/key_list_list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipToPadding="false"
+ android:drawSelectorOnTop="true"
+ android:fastScrollEnabled="true"
+ android:paddingBottom="16dp"
+ android:paddingLeft="16dp"
+ android:paddingRight="32dp"
+ android:scrollbarStyle="outsideOverlay" />
+
+ <LinearLayout
+ android:id="@+id/key_list_empty"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:orientation="vertical"
+ android:visibility="gone">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:text="@string/key_list_empty_text1"
+ android:textAppearance="?android:attr/textAppearanceLarge" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:text=""
+ android:textAppearance="?android:attr/textAppearanceLarge" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="4dp"
+ android:gravity="center"
+ android:text="@string/key_list_empty_text2"
+ android:textAppearance="?android:attr/textAppearanceSmall" />
+
+ <com.beardedhen.androidbootstrap.BootstrapButton
+ android:id="@+id/key_list_empty_button_create"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="4dp"
+ android:text="@string/key_list_empty_button_create"
+ bootstrapbutton:bb_icon_left="fa-plus"
+ bootstrapbutton:bb_type="default" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="4dp"
+ android:gravity="center"
+ android:text="@string/key_list_empty_text3"
+ android:textAppearance="?android:attr/textAppearanceSmall" />
+
+ <com.beardedhen.androidbootstrap.BootstrapButton
+ android:id="@+id/key_list_empty_button_import"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="4dp"
+ android:text="@string/key_list_empty_button_import"
+ bootstrapbutton:bb_icon_left="fa-download"
+ bootstrapbutton:bb_type="default" />
+ </LinearLayout>
+
+ </FrameLayout>
+
+
+</FrameLayout>
diff --git a/OpenKeychain/src/main/res/layout/key_list_header.xml b/OpenKeychain/src/main/res/layout/key_list_header.xml
new file mode 100644
index 000000000..09ac1c856
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/key_list_header.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" >
+
+ <org.sufficientlysecure.keychain.ui.widget.UnderlineTextView
+ android:id="@+id/stickylist_header_text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="start|left"
+ android:padding="8dp"
+ android:textColor="@color/emphasis"
+ android:textSize="17sp"
+ android:textStyle="bold"
+ android:text="header text" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:text="contact count"
+ android:id="@+id/contacts_num"
+ android:layout_centerVertical="true"
+ android:layout_alignParentRight="true"
+ android:layout_alignParentEnd="true"
+ android:layout_marginRight="10px"
+ android:visibility="visible"
+ android:textColor="@android:color/darker_gray" />
+
+</RelativeLayout> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/key_list_item.xml b/OpenKeychain/src/main/res/layout/key_list_item.xml
new file mode 100644
index 000000000..84ad9f9b5
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/key_list_item.xml
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:gravity="center_vertical"
+ android:singleLine="true"
+ android:orientation="horizontal"
+ android:descendantFocusability="blocksDescendants"
+ android:focusable="false">
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_weight="1"
+ android:focusable="true"
+ android:orientation="vertical"
+ android:paddingLeft="8dp"
+ android:paddingRight="4dp"
+ android:paddingTop="4dp"
+ android:paddingBottom="4dp">
+
+ <TextView
+ android:id="@+id/mainUserId"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/label_main_user_id"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+ <TextView
+ android:id="@+id/mainUserIdRest"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:text="user@example.com"
+ android:textAppearance="?android:attr/textAppearanceSmall" />
+ </LinearLayout>
+
+ <View
+ android:id="@+id/status_divider"
+ android:layout_width="1dip"
+ android:layout_height="match_parent"
+ android:layout_marginBottom="8dp"
+ android:layout_marginTop="8dp"
+ android:background="?android:attr/listDivider" />
+
+ <FrameLayout
+ android:id="@+id/status_layout"
+ android:layout_width="80dp"
+ android:layout_height="match_parent">
+
+ <Button
+ android:background="@drawable/selector_transparent_button"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:id="@+id/edit"
+ android:focusable="false"
+ android:enabled="true"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="@color/black"
+ android:text="@string/edit" />
+
+ <TextView
+ android:id="@+id/revoked"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingLeft="8dp"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:text="@string/revoked"
+ android:textColor="#e00"
+ android:layout_gravity="center" />
+
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/verified"
+ android:layout_gravity="center"
+ android:src="@android:drawable/presence_online"
+ android:paddingLeft="25dp" />
+ </FrameLayout>
+
+</LinearLayout>
diff --git a/OpenKeychain/src/main/res/layout/key_server_editor.xml b/OpenKeychain/src/main/res/layout/key_server_editor.xml
new file mode 100644
index 000000000..950978a0e
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/key_server_editor.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<org.sufficientlysecure.keychain.ui.widget.KeyServerEditor xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" >
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="3dip"
+ android:orientation="horizontal" >
+
+ <EditText
+ android:id="@+id/server"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:inputType="textUri" />
+
+ <com.beardedhen.androidbootstrap.BootstrapButton
+ android:id="@+id/delete"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_margin="10dp"
+ android:layout_marginRight="3dip"
+ bootstrapbutton:bb_icon_left="fa-minus"
+ bootstrapbutton:bb_roundedCorners="true"
+ bootstrapbutton:bb_size="small"
+ bootstrapbutton:bb_type="danger" />
+ </LinearLayout>
+
+ <View
+ android:id="@+id/separator"
+ android:layout_width="match_parent"
+ android:layout_height="1dip"
+ android:background="?android:attr/listDivider" />
+
+</org.sufficientlysecure.keychain.ui.widget.KeyServerEditor> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/key_server_export.xml b/OpenKeychain/src/main/res/layout/key_server_export.xml
new file mode 100644
index 000000000..6031bf7c7
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/key_server_export.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" >
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingLeft="16dp"
+ android:paddingRight="16dp"
+ android:orientation="vertical" >
+
+ <TextView
+ style="@style/SectionHeader"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="4dp"
+ android:layout_marginTop="14dp"
+ android:text="@string/section_key_server" />
+
+ <Spinner
+ android:id="@+id/sign_key_keyserver"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="4dp"
+ android:layout_marginTop="4dp" />
+
+ <com.beardedhen.androidbootstrap.BootstrapButton
+ android:id="@+id/btn_export_to_server"
+ android:layout_width="match_parent"
+ android:layout_height="60dp"
+ android:layout_marginBottom="4dp"
+ android:layout_marginTop="14dp"
+ android:text="@string/btn_export_to_server"
+ bootstrapbutton:bb_icon_left="fa-upload"
+ bootstrapbutton:bb_type="info" />
+ </LinearLayout>
+
+</ScrollView> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/key_server_preference.xml b/OpenKeychain/src/main/res/layout/key_server_preference.xml
new file mode 100644
index 000000000..b8897a7b3
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/key_server_preference.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical" >
+
+ <LinearLayout
+ android:id="@+id/text_layout"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_vertical"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:orientation="horizontal" >
+
+ <RelativeLayout
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="6sp"
+ android:layout_marginLeft="16sp"
+ android:layout_marginRight="6sp"
+ android:layout_marginTop="6sp"
+ android:layout_weight="1"
+ android:background="@android:drawable/menuitem_background"
+ android:focusable="true" >
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:focusable="true"
+ android:singleLine="true"
+ android:textAppearance="?android:attr/textAppearanceLarge" />
+
+ <TextView
+ android:id="@+id/summary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignLeft="@android:id/title"
+ android:layout_below="@android:id/title"
+ android:maxLines="2"
+ android:textAppearance="?android:attr/textAppearanceSmall" />
+ </RelativeLayout>
+
+ <com.beardedhen.androidbootstrap.BootstrapButton
+ android:id="@+id/add"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_margin="10dp"
+ android:layout_marginLeft="4dip"
+ android:layout_marginRight="6dip"
+ bootstrapbutton:bb_icon_left="fa-plus"
+ bootstrapbutton:bb_roundedCorners="true"
+ bootstrapbutton:bb_size="small"
+ bootstrapbutton:bb_type="success" />
+ </LinearLayout>
+
+ <View
+ android:id="@+id/separator"
+ android:layout_width="fill_parent"
+ android:layout_height="1dip"
+ android:background="?android:attr/listDivider" />
+
+ <ScrollView
+ android:layout_width="fill_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1"
+ android:orientation="vertical" >
+
+ <LinearLayout
+ android:id="@+id/editors"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" />
+ </ScrollView>
+
+</LinearLayout>
diff --git a/OpenKeychain/src/main/res/layout/passphrase_dialog.xml b/OpenKeychain/src/main/res/layout/passphrase_dialog.xml
new file mode 100644
index 000000000..4b331f0f2
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/passphrase_dialog.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingLeft="16dp"
+ android:paddingRight="16dp" >
+
+ <TextView
+ android:id="@+id/passphrase_label_passphrase"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:padding="4dp"
+ android:text="@string/label_passphrase" />
+
+ <EditText
+ android:id="@+id/passphrase_passphrase"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:imeOptions="actionDone"
+ android:inputType="textPassword"
+ android:padding="4dp" />
+
+</LinearLayout> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/passphrase_repeat_dialog.xml b/OpenKeychain/src/main/res/layout/passphrase_repeat_dialog.xml
new file mode 100644
index 000000000..ae523762c
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/passphrase_repeat_dialog.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingLeft="16dp"
+ android:paddingRight="16dp"
+ android:stretchColumns="1" >
+
+ <TableRow
+ android:layout_marginBottom="5dip"
+ >
+
+ <TextView
+ android:id="@+id/passphrase_label_passphrase"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:padding="4dp"
+ android:text="@string/label_passphrase" />
+
+ <EditText
+ android:id="@+id/passphrase_passphrase"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:inputType="textPassword"
+ android:padding="4dp" />
+ </TableRow>
+
+ <TableRow
+ android:layout_marginBottom="10dip"
+ >
+
+ <TextView
+ android:id="@+id/passphrase_label_passphrase_again"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:padding="4dp"
+ android:text="@string/label_passphrase_again" />
+
+ <EditText
+ android:id="@+id/passphrase_passphrase_again"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:imeOptions="actionDone"
+ android:inputType="textPassword"
+ android:padding="4dp" />
+ </TableRow>
+
+</TableLayout> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/select_key_item.xml b/OpenKeychain/src/main/res/layout/select_key_item.xml
new file mode 100644
index 000000000..08c161ec6
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/select_key_item.xml
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="?android:attr/listPreferredItemHeight"
+ android:paddingLeft="3dip"
+ android:paddingRight="?android:attr/scrollbarSize"
+ android:singleLine="true" >
+
+ <CheckBox
+ android:id="@+id/selected"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:clickable="false"
+ android:focusable="false"
+ android:focusableInTouchMode="false" />
+
+ <LinearLayout
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:orientation="vertical"
+ android:paddingLeft="5dip" >
+
+ <TextView
+ android:id="@+id/mainUserId"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/label_main_user_id"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+ <TextView
+ android:id="@+id/mainUserIdRest"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="&lt;user@example.com>"
+ android:textAppearance="?android:attr/textAppearanceSmall" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="right"
+ android:minWidth="90dip"
+ android:orientation="vertical"
+ android:paddingLeft="3dip" >
+
+ <TextView
+ android:id="@+id/keyId"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:text="BBBBBBBB"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:typeface="monospace" />
+
+ <TextView
+ android:id="@+id/status"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="expired"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textStyle="italic" />
+ </LinearLayout>
+
+</LinearLayout> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/select_public_key_activity.xml b/OpenKeychain/src/main/res/layout/select_public_key_activity.xml
new file mode 100644
index 000000000..a18ce46fc
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/select_public_key_activity.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_centerHorizontal="true" >
+
+ <FrameLayout
+ android:id="@+id/select_public_key_fragment_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+</RelativeLayout> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/select_secret_key_activity.xml b/OpenKeychain/src/main/res/layout/select_secret_key_activity.xml
new file mode 100644
index 000000000..c4cdd7576
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/select_secret_key_activity.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_centerHorizontal="true" >
+
+ <FrameLayout
+ android:id="@+id/select_secret_key_fragment_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+</RelativeLayout> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/select_secret_key_layout_fragment.xml b/OpenKeychain/src/main/res/layout/select_secret_key_layout_fragment.xml
new file mode 100644
index 000000000..408c0c54e
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/select_secret_key_layout_fragment.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <com.beardedhen.androidbootstrap.BootstrapButton
+ android:id="@+id/select_secret_key_select_key_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_marginBottom="4dp"
+ android:layout_marginRight="4dp"
+ android:layout_marginTop="4dp"
+ android:text="@string/api_settings_select_key"
+ bootstrapbutton:bb_icon_left="fa-key"
+ bootstrapbutton:bb_size="default"
+ bootstrapbutton:bb_type="default" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="4dp"
+ android:layout_marginLeft="4dp"
+ android:layout_marginTop="4dp"
+ android:orientation="vertical"
+ android:paddingLeft="4dp">
+
+ <!-- Has been made focusable to display error messages with setError -->
+ <TextView
+ android:id="@+id/select_secret_key_user_id"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="left"
+ android:focusable="true"
+ android:focusableInTouchMode="true"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:visibility="gone"
+ android:layout_marginRight="5dip"
+ android:text=""
+ android:textAppearance="?android:attr/textAppearanceSmall" />
+
+ <TextView
+ android:id="@+id/select_secret_key_user_id_rest"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="left"
+ android:ellipsize="end"
+ android:singleLine="true"
+ android:layout_marginRight="5dip"
+ android:text=""
+ android:visibility="gone"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:paddingLeft="10dp" />
+
+ <TextView
+ android:id="@+id/select_secret_key_master_key_hex"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:visibility="gone"
+ android:layout_marginRight="15dip" />
+
+ <TextView
+ android:id="@+id/no_key_selected"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:text="@string/api_settings_no_key"
+ android:layout_marginTop="15dp" />
+
+
+ </LinearLayout>
+
+</LinearLayout> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/share_qr_code_dialog.xml b/OpenKeychain/src/main/res/layout/share_qr_code_dialog.xml
new file mode 100644
index 000000000..0b58ae72f
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/share_qr_code_dialog.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" >
+
+ <TextView
+ android:id="@+id/share_qr_code_dialog_text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="8dp"
+ android:textAppearance="@android:style/TextAppearance.Medium" />
+
+ <ImageView
+ android:id="@+id/share_qr_code_dialog_image"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
+</LinearLayout> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/view_cert_activity.xml b/OpenKeychain/src/main/res/layout/view_cert_activity.xml
new file mode 100644
index 000000000..95b8ffc8d
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/view_cert_activity.xml
@@ -0,0 +1,210 @@
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <!-- focusable and related properties to workaround http://stackoverflow.com/q/16182331-->
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:focusable="true"
+ android:focusableInTouchMode="true"
+ android:descendantFocusability="beforeDescendants"
+ android:orientation="vertical"
+ android:paddingLeft="16dp"
+ android:paddingRight="16dp">
+
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:layout_marginTop="14dp">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Verification Status" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="ok"
+ android:id="@+id/status"
+ android:layout_marginLeft="30dp" />
+ </LinearLayout>
+
+ <TextView
+ style="@style/SectionHeader"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="4dp"
+ android:layout_marginTop="14dp"
+ android:text="@string/section_cert" />
+
+ <TableLayout
+ android:layout_width="wrap_content"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:stretchColumns="1">
+
+ <TableRow>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top"
+ android:paddingRight="10dip"
+ android:text="@string/label_key_id" />
+
+ <TextView
+ android:id="@+id/signee_key"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:paddingRight="5dip" />
+ </TableRow>
+
+ <TableRow>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top"
+ android:paddingRight="10dip"
+ android:text="@string/label_user_id" />
+
+ <TextView
+ android:id="@+id/signee_uid"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:paddingRight="5dip" />
+ </TableRow>
+
+ <TableRow
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top"
+ android:paddingRight="10dip"
+ android:text="@string/label_algorithm" />
+
+ <TextView
+ android:id="@+id/algorithm"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:paddingRight="5dip" />
+ </TableRow>
+
+ <TableRow
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top"
+ android:paddingRight="10dip"
+ android:text="Type" />
+
+ <TextView
+ android:id="@+id/signature_type"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:paddingRight="5dip" />
+ </TableRow>
+
+ <TableRow
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:id="@+id/row_reason">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top"
+ android:paddingRight="10dip"
+ android:text="Revocation Reason" />
+
+ <TextView
+ android:id="@+id/reason"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:paddingRight="5dip" />
+ </TableRow>
+
+ <TableRow>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top"
+ android:paddingRight="10dip"
+ android:text="Creation" />
+
+ <TextView
+ android:id="@+id/creation"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:paddingRight="5dip" />
+ </TableRow>
+
+ </TableLayout>
+
+ <TextView
+ style="@style/SectionHeader"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="4dp"
+ android:layout_marginTop="14dp"
+ android:text="@string/section_signer_id" />
+
+ <TableLayout
+ android:layout_width="wrap_content"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:stretchColumns="1">
+
+ <TableRow>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:paddingRight="10dip"
+ android:text="@string/label_key_id" />
+
+ <TextView
+ android:id="@+id/signer_key_id"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingRight="5dip"
+ android:text=""
+ android:typeface="monospace" />
+ </TableRow>
+
+ <TableRow>
+
+ <TextView
+ android:id="@+id/label_algorithm"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:paddingRight="10dip"
+ android:text="@string/label_email" />
+
+ <TextView
+ android:id="@+id/signer_uid"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingRight="5dip"
+ android:text="" />
+ </TableRow>
+
+ </TableLayout>
+
+ </LinearLayout>
+
+</ScrollView>
diff --git a/OpenKeychain/src/main/res/layout/view_key_activity.xml b/OpenKeychain/src/main/res/layout/view_key_activity.xml
new file mode 100644
index 000000000..58e4919dc
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/view_key_activity.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical" >
+
+ <android.support.v4.view.ViewPager
+ android:id="@+id/pager"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+</LinearLayout> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/view_key_certs_fragment.xml b/OpenKeychain/src/main/res/layout/view_key_certs_fragment.xml
new file mode 100644
index 000000000..33042c541
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/view_key_certs_fragment.xml
@@ -0,0 +1,34 @@
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:fillViewport="true">
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <view
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ class="se.emilsjolander.stickylistheaders.StickyListHeadersListView"
+ android:id="@+id/list"
+ android:paddingRight="32dp"
+ android:paddingLeft="16dp"
+ android:layout_alignParentStart="false"
+ android:layout_alignParentEnd="false"
+ android:layout_below="@+id/list" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:text="@string/empty_certs"
+ android:id="@+id/empty"
+ android:visibility="gone"
+ android:layout_centerInParent="true"
+ android:paddingBottom="32dp" />
+
+ </RelativeLayout>
+
+</ScrollView>
diff --git a/OpenKeychain/src/main/res/layout/view_key_certs_header.xml b/OpenKeychain/src/main/res/layout/view_key_certs_header.xml
new file mode 100644
index 000000000..04e7b8097
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/view_key_certs_header.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" >
+
+ <org.sufficientlysecure.keychain.ui.widget.UnderlineTextView
+ android:id="@+id/stickylist_header_text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="start|left"
+ android:padding="8dp"
+ android:textColor="@color/emphasis"
+ android:textSize="14sp"
+ android:textStyle="bold"
+ android:text="header text" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:text="certification count"
+ android:id="@+id/certs_num"
+ android:layout_centerVertical="true"
+ android:layout_alignParentRight="true"
+ android:layout_alignParentEnd="true"
+ android:layout_marginRight="10px"
+ android:visibility="visible"
+ android:textColor="@android:color/darker_gray" />
+
+</RelativeLayout> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/view_key_certs_item.xml b/OpenKeychain/src/main/res/layout/view_key_certs_item.xml
new file mode 100644
index 000000000..de7570818
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/view_key_certs_item.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:gravity="center_vertical"
+ android:paddingLeft="8dp"
+ android:paddingTop="4dp"
+ android:paddingBottom="4dp"
+ android:singleLine="true"
+ android:descendantFocusability="blocksDescendants"
+ android:focusable="false">
+
+ <TextView
+ android:id="@+id/signerKeyId"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="signer key id"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentStart="true" />
+
+ <TextView
+ android:id="@+id/signerUserId"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="&lt;user@example.com>"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:layout_below="@+id/signerKeyId"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentStart="true" />
+
+ <TextView
+ android:id="@+id/signStatus"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:text="status"
+ android:visibility="visible"
+ android:layout_above="@+id/signerUserId"
+ android:layout_alignParentRight="true"
+ android:layout_alignParentEnd="true"
+ android:layout_marginRight="10dp" />
+
+</RelativeLayout>
diff --git a/OpenKeychain/src/main/res/layout/view_key_delete_fragment.xml b/OpenKeychain/src/main/res/layout/view_key_delete_fragment.xml
new file mode 100644
index 000000000..ef31f7690
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/view_key_delete_fragment.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/mainMessage"
+ android:layout_margin="4dp"
+ android:textAppearance="?android:textAppearanceMedium" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:weightSum="1"
+ android:id="@+id/deleteSecretKeyView">
+
+ <CheckBox
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="0.1"
+ android:layout_margin="4dp"
+ android:id="@+id/checkDeleteSecret" />
+
+ <TextView
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_margin="4dp"
+ android:textAppearance="?android:textAppearanceMedium"
+ android:layout_weight="0.9"
+ android:text="@string/secret_key_delete_text" />
+
+ </LinearLayout>
+
+</LinearLayout> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/view_key_keys_item.xml b/OpenKeychain/src/main/res/layout/view_key_keys_item.xml
new file mode 100644
index 000000000..aecedc39b
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/view_key_keys_item.xml
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:paddingLeft="8dip"
+ android:paddingRight="3dip" >
+
+ <ImageView
+ android:id="@+id/ic_masterKey"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:paddingRight="6dip"
+ android:src="@drawable/key_small" />
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingBottom="2dip"
+ android:paddingTop="2dip" >
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+ <TextView
+ android:id="@+id/keyId"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingRight="2dip"
+ android:text="@string/label_key_id"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:typeface="monospace" />
+
+ <TextView
+ android:id="@+id/keyDetails"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingRight="5dip"
+ android:text="(RSA, 1024bit)"
+ android:textAppearance="?android:attr/textAppearanceSmall" />
+ </LinearLayout>
+ <TextView
+ android:id="@+id/keyExpiry"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="right"
+ android:text="@string/label_expiry"
+ android:textAppearance="?android:attr/textAppearanceSmall" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:gravity="right"
+ android:paddingBottom="2dip"
+ android:paddingTop="2dip" >
+
+ <ImageView android:id="@+id/ic_revokedKey"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/revoked_key_small"/>
+ <ImageView
+ android:id="@+id/ic_certifyKey"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/certify_small" />
+
+ <ImageView
+ android:id="@+id/ic_encryptKey"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/encrypted_small" />
+
+ <ImageView
+ android:id="@+id/ic_signKey"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/signed_small" />
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/OpenKeychain/src/main/res/layout/view_key_main_fragment.xml b/OpenKeychain/src/main/res/layout/view_key_main_fragment.xml
new file mode 100644
index 000000000..e3448c39f
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/view_key_main_fragment.xml
@@ -0,0 +1,265 @@
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <!-- focusable and related properties to workaround http://stackoverflow.com/q/16182331-->
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:focusable="true"
+ android:focusableInTouchMode="true"
+ android:descendantFocusability="beforeDescendants"
+ android:orientation="vertical"
+ android:paddingLeft="16dp"
+ android:paddingRight="16dp"
+ android:id="@+id/container">
+
+ <TextView
+ style="@style/SectionHeader"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="4dp"
+ android:layout_marginTop="14dp"
+ android:text="@string/section_master_user_id" />
+
+ <TableLayout
+ android:layout_width="wrap_content"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:stretchColumns="1">
+
+ <TableRow>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top"
+ android:paddingRight="10dip"
+ android:text="@string/label_name" />
+
+ <TextView
+ android:id="@+id/name"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:paddingRight="5dip"
+ android:text="" />
+ </TableRow>
+
+ <TableRow>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top"
+ android:paddingRight="10dip"
+ android:text="@string/label_email" />
+
+ <TextView
+ android:id="@+id/email"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:paddingRight="5dip"
+ android:text="" />
+ </TableRow>
+
+ <TableRow>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top"
+ android:paddingRight="10dip"
+ android:text="@string/label_comment" />
+
+ <TextView
+ android:id="@+id/comment"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:paddingRight="5dip"
+ android:text="" />
+ </TableRow>
+ </TableLayout>
+
+ <TextView
+ style="@style/SectionHeader"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="4dp"
+ android:layout_marginTop="14dp"
+ android:text="@string/section_master_key" />
+
+ <TableLayout
+ android:layout_width="wrap_content"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:shrinkColumns="1">
+
+ <TableRow>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:paddingRight="10dip"
+ android:text="@string/label_key_id" />
+
+ <TextView
+ android:id="@+id/key_id"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingRight="5dip"
+ android:text=""
+ android:typeface="monospace" />
+ </TableRow>
+
+ <TableRow>
+
+ <TextView
+ android:id="@+id/label_algorithm"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:paddingRight="10dip"
+ android:text="@string/label_algorithm" />
+
+ <TextView
+ android:id="@+id/algorithm"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingRight="5dip"
+ android:text="" />
+ </TableRow>
+
+ <TableRow
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:id="@+id/tableRow">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:paddingRight="10dip"
+ android:text="@string/label_fingerprint" />
+
+ <TextView
+ android:id="@+id/fingerprint"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:typeface="monospace" />
+ </TableRow>
+
+ <TableRow>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:paddingRight="10dip"
+ android:text="@string/label_creation" />
+
+ <TextView
+ android:id="@+id/creation"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+ </TableRow>
+
+ <TableRow>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:paddingRight="10dip"
+ android:text="@string/label_expiry" />
+
+ <TextView
+ android:id="@+id/expiry"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+ </TableRow>
+
+ <TableRow>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:paddingRight="10dip"
+ android:text="@string/label_secret_key" />
+
+ <TextView
+ android:id="@+id/secret_key"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+ </TableRow>
+ </TableLayout>
+
+ <TextView
+ style="@style/SectionHeader"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="4dp"
+ android:layout_marginTop="14dp"
+ android:text="@string/section_user_ids" />
+
+ <org.sufficientlysecure.keychain.ui.widget.FixedListView
+ android:id="@+id/user_ids"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
+ <TextView
+ style="@style/SectionHeader"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="4dp"
+ android:layout_marginTop="14dp"
+ android:text="@string/section_keys" />
+
+ <org.sufficientlysecure.keychain.ui.widget.FixedListView
+ android:id="@+id/keys"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
+ <TextView
+ style="@style/SectionHeader"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="14dp"
+ android:text="@string/section_actions" />
+
+ <com.beardedhen.androidbootstrap.BootstrapButton
+ android:id="@+id/action_edit"
+ android:layout_width="match_parent"
+ android:layout_height="50dp"
+ android:layout_marginTop="4dp"
+ android:layout_marginBottom="4dp"
+ android:text="@string/key_view_action_edit"
+ bootstrapbutton:bb_icon_left="fa-key"
+ bootstrapbutton:bb_type="info"
+ android:visibility="gone" />
+
+ <com.beardedhen.androidbootstrap.BootstrapButton
+ android:id="@+id/action_encrypt"
+ android:layout_width="match_parent"
+ android:layout_height="50dp"
+ android:layout_marginTop="4dp"
+ android:layout_marginBottom="4dp"
+ android:text="@string/key_view_action_encrypt"
+ bootstrapbutton:bb_icon_left="fa-lock"
+ bootstrapbutton:bb_type="info" />
+
+ <com.beardedhen.androidbootstrap.BootstrapButton
+ android:id="@+id/action_certify"
+ android:layout_width="match_parent"
+ android:layout_height="50dp"
+ android:layout_marginTop="4dp"
+ android:layout_marginBottom="4dp"
+ android:text="@string/key_view_action_certify"
+ bootstrapbutton:bb_icon_left="fa-pencil"
+ bootstrapbutton:bb_type="info" />
+
+ </LinearLayout>
+
+</ScrollView>
diff --git a/OpenKeychain/src/main/res/layout/view_key_userids_item.xml b/OpenKeychain/src/main/res/layout/view_key_userids_item.xml
new file mode 100644
index 000000000..e47f591cb
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/view_key_userids_item.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:paddingRight="3dip"
+ android:singleLine="true">
+
+ <CheckBox
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/checkBox"
+ android:clickable="false"
+ android:focusable="false" />
+
+ <TextView
+ android:id="@+id/rank"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:text="0"
+ android:paddingLeft="10dp"
+ android:paddingRight="10dp"
+ android:layout_gravity="center_vertical"
+ android:width="30sp" />
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1">
+
+ <TextView
+ android:id="@+id/userId"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/user_id_no_name"
+ android:textAppearance="?android:attr/textAppearanceSmall" />
+
+ <TextView
+ android:id="@+id/address"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/label_email"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:paddingLeft="10dp" />
+
+ </LinearLayout>
+
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:id="@+id/certified"
+ android:src="@android:drawable/presence_invisible"
+ android:layout_marginLeft="5dp"
+ android:layout_marginRight="5dp" />
+
+</LinearLayout>
diff --git a/OpenKeychain/src/main/res/menu/api_account_settings.xml b/OpenKeychain/src/main/res/menu/api_account_settings.xml
new file mode 100644
index 000000000..d08fc7f42
--- /dev/null
+++ b/OpenKeychain/src/main/res/menu/api_account_settings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+
+ <item
+ android:id="@+id/menu_account_settings_delete"
+ android:title="@string/api_settings_delete_account"
+ app:showAsAction="never" />
+ <item
+ android:id="@+id/menu_account_settings_cancel"
+ android:title="@string/api_settings_cancel"
+ app:showAsAction="never" />
+
+</menu> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/menu/api_app_settings.xml b/OpenKeychain/src/main/res/menu/api_app_settings.xml
new file mode 100644
index 000000000..a21db6708
--- /dev/null
+++ b/OpenKeychain/src/main/res/menu/api_app_settings.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+
+ <item
+ android:id="@+id/menu_api_settings_revoke"
+ android:title="@string/api_settings_revoke"
+ app:showAsAction="never" />
+
+</menu> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/menu/key_list.xml b/OpenKeychain/src/main/res/menu/key_list.xml
new file mode 100644
index 000000000..3f80b616d
--- /dev/null
+++ b/OpenKeychain/src/main/res/menu/key_list.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+
+ <item
+ android:id="@+id/menu_key_list_search"
+ android:title="@string/menu_search"
+ android:icon="@drawable/ic_action_search"
+ app:actionViewClass="android.support.v7.widget.SearchView"
+ app:showAsAction="collapseActionView|ifRoom" />
+
+ <item
+ android:id="@+id/menu_key_list_import"
+ app:showAsAction="ifRoom|withText"
+ android:icon="@drawable/ic_action_add_person"
+ android:title="@string/menu_add_keys" />
+
+ <item
+ android:id="@+id/menu_key_list_export"
+ app:showAsAction="ifRoom|withText"
+ android:icon="@drawable/ic_action_import_export"
+ android:title="@string/menu_export_all_keys" />
+
+ <item
+ android:id="@+id/menu_key_list_create"
+ app:showAsAction="never"
+ android:title="@string/menu_create_key" />
+
+ <item
+ android:id="@+id/menu_key_list_create_expert"
+ app:showAsAction="never"
+ android:title="@string/menu_create_key_expert" />
+
+</menu>
diff --git a/OpenKeychain/src/main/res/menu/key_list_multi.xml b/OpenKeychain/src/main/res/menu/key_list_multi.xml
new file mode 100644
index 000000000..087521177
--- /dev/null
+++ b/OpenKeychain/src/main/res/menu/key_list_multi.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item
+ android:id="@+id/menu_key_list_multi_export"
+ android:icon="@drawable/ic_action_import_export"
+ android:title="@string/menu_export_key" />
+
+ <item
+ android:id="@+id/menu_key_list_multi_encrypt"
+ android:icon="@drawable/ic_action_secure"
+ android:title="@string/menu_encrypt_to" />
+
+ <item
+ android:id="@+id/menu_key_list_multi_delete"
+ android:icon="@drawable/ic_action_discard"
+ android:title="@string/menu_delete_key" />
+
+ <item
+ android:id="@+id/menu_key_list_multi_select_all"
+ android:icon="@drawable/ic_action_select_all"
+ android:title="@string/menu_select_all" />
+
+</menu>
diff --git a/OpenKeychain/src/main/res/menu/key_view.xml b/OpenKeychain/src/main/res/menu/key_view.xml
new file mode 100644
index 000000000..2f5697a0f
--- /dev/null
+++ b/OpenKeychain/src/main/res/menu/key_view.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+
+ <item
+ android:id="@+id/menu_key_view_share"
+ android:icon="@drawable/ic_action_share"
+ app:showAsAction="always"
+ android:title="@string/menu_share">
+ <menu>
+ <item
+ android:id="@+id/menu_key_view_share_fingerprint_title"
+ app:showAsAction="never"
+ android:title="@string/menu_share_title_fingerprint">
+ <menu>
+ <item
+ android:id="@+id/menu_key_view_share_default_fingerprint"
+ app:showAsAction="never"
+ android:title="@string/menu_share_default_fingerprint" />
+ <item
+ android:id="@+id/menu_key_view_share_qr_code_fingerprint"
+ app:showAsAction="never"
+ android:title="@string/menu_share_qr_code_fingerprint" />
+ </menu>
+ </item>
+ <item
+ android:id="@+id/menu_key_view_share_title"
+ app:showAsAction="never"
+ android:title="@string/menu_share_title">
+ <menu>
+ <item
+ android:id="@+id/menu_key_view_share_default"
+ app:showAsAction="never"
+ android:title="@string/menu_share_default" />
+ <item
+ android:id="@+id/menu_key_view_share_qr_code"
+ app:showAsAction="never"
+ android:title="@string/menu_share_qr_code" />
+ <item
+ android:id="@+id/menu_key_view_share_nfc"
+ app:showAsAction="never"
+ android:title="@string/menu_share_nfc" />
+ <item
+ android:id="@+id/menu_key_view_share_clipboard"
+ app:showAsAction="never"
+ android:title="@string/menu_copy_to_clipboard" />
+ </menu>
+ </item>
+ </menu>
+ </item>
+
+ <item
+ android:id="@+id/menu_key_keyserver"
+ android:icon="@drawable/ic_action_cloud"
+ app:showAsAction="always"
+ android:title="@string/menu_key_server">
+ <menu>
+ <item
+ android:id="@+id/menu_key_view_update"
+ app:showAsAction="never"
+ android:title="@string/menu_update_key" />
+ <item
+ android:id="@+id/menu_key_view_export_keyserver"
+ app:showAsAction="never"
+ android:title="@string/menu_export_key_to_server" />
+ </menu>
+ </item>
+
+ <item
+ android:id="@+id/menu_key_view_export_file"
+ android:icon="@drawable/ic_action_import_export"
+ app:showAsAction="ifRoom"
+ android:title="@string/menu_export_key" />
+
+ <item
+ android:id="@+id/menu_key_view_delete"
+ android:icon="@drawable/ic_action_discard"
+ app:showAsAction="ifRoom"
+ android:title="@string/menu_delete_key" />
+
+</menu> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/menu/view_cert.xml b/OpenKeychain/src/main/res/menu/view_cert.xml
new file mode 100644
index 000000000..8c8e455c7
--- /dev/null
+++ b/OpenKeychain/src/main/res/menu/view_cert.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+
+ <item
+ android:id="@+id/menu_view_cert_view_signer"
+ app:showAsAction="never"
+ android:title="View signing key" />
+</menu> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/raw-cs-rCZ/help_about.html b/OpenKeychain/src/main/res/raw-cs-rCZ/help_about.html
new file mode 100644
index 000000000..99977f75d
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-cs-rCZ/help_about.html
@@ -0,0 +1,49 @@
+<html>
+<head></head>
+<body>
+<p><a href="http://www.openkeychain.org">http://www.openkeychain.org</a></p>
+<p><a href="http://www.openkeychain.org">OpenKeychain</a> is an OpenPGP implementation for Android.</p>
+<p>Licence: GPLv3+</p>
+
+<h2>Developers OpenKeychain</h2>
+<ul>
+<li>Dominik Schürmann (Hlavní vývojář)</li>
+<li>Ash Hughes (crypto patches)</li>
+<li>Brian C. Barnes</li>
+<li>Bahtiar 'kalkin' Gadimov (UI)</li>
+<li>Daniel Hammann</li>
+<li>Daniel Haß</li>
+<li>Greg Witczak</li>
+<li>Miroojin Bakshi</li>
+<li>Nikhil Peter Raj</li>
+<li>Paul Sarbinowski</li>
+<li>Sreeram Boyapati</li>
+<li>Vincent Breitmoser</li>
+</ul>
+<h2>Developers APG 1.x</h2>
+<ul>
+<li>Thialfihar (Lead developer)</li>
+<li>'Senecaso' (QRCode, sign key, upload key)</li>
+<li>Markus Doits</li>
+</ul>
+<h2>Libraries</h2>
+<ul>
+<li>
+<a href="http://developer.android.com/tools/support-library/index.html">Android Support Library v4</a> (Apache License v2)</li>
+<li>
+<a href="http://developer.android.com/tools/support-library/index.html">Android Support Library v7 'appcompat'</a> (Apache License v2)</li>
+<li>
+<a href="https://github.com/emilsjolander/StickyListHeaders">StickyListHeaders</a> (Apache License v2)</li>
+<li>
+<a href="https://github.com/Bearded-Hen/Android-Bootstrap">Android-Bootstrap</a> (MIT License)</li>
+<li>
+<a href="http://code.google.com/p/zxing/">ZXing</a> (Apache License v2)</li>
+<li>
+<a href="http://rtyley.github.com/spongycastle/">SpongyCastle</a> (MIT X11 License)</li>
+<li>
+<a href="https://github.com/dschuermann/html-textview">HtmlTextView</a> (Apache License v2)</li>
+<li>
+<a href="https://github.com/johnkil/Android-AppMsg">Android AppMsg Library</a> (Apache License v2)</li>
+</ul>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-cs-rCZ/help_changelog.html b/OpenKeychain/src/main/res/raw-cs-rCZ/help_changelog.html
new file mode 100644
index 000000000..694c7b046
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-cs-rCZ/help_changelog.html
@@ -0,0 +1,136 @@
+<html>
+<head></head>
+<body>
+<h2>2.5</h2>
+<ul>
+<li>fix decryption of symmetric pgp messages/files</li>
+<li>refactored edit key screen (thanks to Ash Hughes)</li>
+<li>new modern design for encrypt/decrypt screens</li>
+<li>OpenPGP API version 3 (multiple api accounts, internal fixes, key lookup)</li>
+</ul>
+<h2>2.4</h2>
+<p>Thanks to all applicants of Google Summer of Code 2014 who made this release feature rich and bug free!
+Besides several small patches, a notable number of patches are made by the following people (in alphabetical order):
+Daniel Hammann, Daniel Haß, Greg Witczak, Miroojin Bakshi, Nikhil Peter Raj, Paul Sarbinowski, Sreeram Boyapati, Vincent Breitmoser.</p>
+<ul>
+<li>new unified key list</li>
+<li>colorized key fingerprint</li>
+<li>support for keyserver ports</li>
+<li>deactivate possibility to generate weak keys</li>
+<li>much more internal work on the API</li>
+<li>certify user ids</li>
+<li>keyserver query based on machine-readable output</li>
+<li>lock navigation drawer on tablets</li>
+<li>suggestions for emails on creation of keys</li>
+<li>search in public key lists</li>
+<li>and much more improvements and fixes…</li>
+</ul>
+<h2>2.3.1</h2>
+<ul>
+<li>hotfix for crash when upgrading from old versions</li>
+</ul>
+<h2>2.3</h2>
+<ul>
+<li>remove unnecessary export of public keys when exporting secret key (thanks to Ash Hughes)</li>
+<li>fix setting expiry dates on keys (thanks to Ash Hughes)</li>
+<li>more internal fixes when editing keys (thanks to Ash Hughes)</li>
+<li>querying keyservers directly from the import screen</li>
+<li>fix layout and dialog style on Android 2.2-3.0</li>
+<li>fix crash on keys with empty user ids</li>
+<li>fix crash and empty lists when coming back from signing screen</li>
+<li>Bouncy Castle (cryptography library) updated from 1.47 to 1.50 and build from source</li>
+<li>fix upload of key from signing screen</li>
+</ul>
+<h2>2.2</h2>
+<ul>
+<li>new design with navigation drawer</li>
+<li>new public key list design</li>
+<li>new public key view</li>
+<li>bug fixes for importing of keys</li>
+<li>key cross-certification (thanks to Ash Hughes)</li>
+<li>handle UTF-8 passwords properly (thanks to Ash Hughes)</li>
+<li>first version with new languages (thanks to the contributors on Transifex)</li>
+<li>sharing of keys via QR Codes fixed and improved</li>
+<li>package signature verification for API</li>
+</ul>
+<h2>2.1.1</h2>
+<ul>
+<li>API Updates, preparation for K-9 Mail integration</li>
+</ul>
+<h2>2.1</h2>
+<ul>
+<li>lots of bug fixes</li>
+<li>new API for developers</li>
+<li>PRNG bug fix by Google</li>
+</ul>
+<h2>2.0</h2>
+<ul>
+<li>complete redesign</li>
+<li>share public keys via qr codes, nfc beam</li>
+<li>sign keys</li>
+<li>upload keys to server</li>
+<li>fixes import issues</li>
+<li>new AIDL API</li>
+</ul>
+<h2>1.0.8</h2>
+<ul>
+<li>basic keyserver support</li>
+<li>app2sd</li>
+<li>more choices for pass phrase cache: 1, 2, 4, 8, hours</li>
+<li>translations: Norwegian (thanks, Sander Danielsen), Chinese (thanks, Zhang Fredrick)</li>
+<li>bugfixes</li>
+<li>optimizations</li>
+</ul>
+<h2>1.0.7</h2>
+<ul>
+<li>fixed problem with signature verification of texts with trailing newline</li>
+<li>more options for pass phrase cache time to live (20, 40, 60 mins)</li>
+</ul>
+<h2>1.0.6</h2>
+<ul>
+<li>account adding crash on Froyo fixed</li>
+<li>secure file deletion</li>
+<li>option to delete key file after import</li>
+<li>stream encryption/decryption (gallery, etc.)</li>
+<li>new options (language, force v3 signatures)</li>
+<li>interface changes</li>
+<li>bugfixes</li>
+</ul>
+<h2>1.0.5</h2>
+<ul>
+<li>German and Italian translation</li>
+<li>much smaller package, due to reduced BC sources</li>
+<li>new preferences GUI</li>
+<li>layout adjustment for localization</li>
+<li>signature bugfix</li>
+</ul>
+<h2>1.0.4</h2>
+<ul>
+<li>fixed another crash caused by some SDK bug with query builder</li>
+</ul>
+<h2>1.0.3</h2>
+<ul>
+<li>fixed crashes during encryption/signing and possibly key export</li>
+</ul>
+<h2>1.0.2</h2>
+<ul>
+<li>filterable key lists</li>
+<li>smarter pre-selection of encryption keys</li>
+<li>new Intent handling for VIEW and SEND, allows files to be encrypted/decrypted out of file managers</li>
+<li>fixes and additional features (key preselection) for K-9 Mail, new beta build available</li>
+</ul>
+<h2>1.0.1</h2>
+<ul>
+<li>GMail account listing was broken in 1.0.0, fixed again</li>
+</ul>
+<h2>1.0.0</h2>
+<ul>
+<li>K-9 Mail integration, APG supporting beta build of K-9 Mail</li>
+<li>support of more file managers (including ASTRO)</li>
+<li>Slovenian translation</li>
+<li>new database, much faster, less memory usage</li>
+<li>defined Intents and content provider for other apps</li>
+<li>bugfixes</li>
+</ul>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-cs-rCZ/help_nfc_beam.html b/OpenKeychain/src/main/res/raw-cs-rCZ/help_nfc_beam.html
new file mode 100644
index 000000000..88492731c
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-cs-rCZ/help_nfc_beam.html
@@ -0,0 +1,12 @@
+<html>
+<head></head>
+<body>
+<h2>How to receive keys</h2>
+<ol>
+<li>Go to your partners contacts and open the contact you want to share.</li>
+<li>Hold the two devices back to back (they have to be almost touching) and you’ll feel a vibration.</li>
+<li>After it vibrates you’ll see the content on your partners device turn into a card-like object with Star Trek warp speed-looking animation in the background.</li>
+<li>Tap the card and the content will then load on the your device.</li>
+</ol>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-cs-rCZ/help_start.html b/OpenKeychain/src/main/res/raw-cs-rCZ/help_start.html
new file mode 100644
index 000000000..0e60c17a7
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-cs-rCZ/help_start.html
@@ -0,0 +1,19 @@
+<html>
+<head></head>
+<body>
+<h2>Getting started</h2>
+<p>First you need a personal key pair. Create one via the option menus in "Contacts" or import existing key pairs via "Import Keys". Afterwards, you can download your friends' keys or exchange them via QR Codes or NFC.</p>
+
+<p>It is recommended that you install <a href="market://details?id=org.openintents.filemanager">OI File Manager</a> for enhanced file selection and <a href="market://details?id=com.google.zxing.client.android">Barcode Scanner</a> to scan generated QR Codes. Clicking on the links will open Google Play Store or F-Droid for installation.</p>
+
+<h2>I found a bug in OpenKeychain!</h2>
+<p>Please report the bug using the <a href="https://github.com/openpgp-keychain/openpgp-keychain/issues">issue tracker of OpenKeychain</a>.</p>
+
+<h2>Contribute</h2>
+<p>If you want to help us developing OpenKeychain by contributing code <a href="https://github.com/openpgp-keychain/openpgp-keychain#contribute-code">follow our small guide on Github</a>.</p>
+
+<h2>Translations</h2>
+<p>Help translating OpenKeychain! Everybody can participate at <a href="https://www.transifex.com/projects/p/openpgp-keychain/">OpenKeychain on Transifex</a>.</p>
+
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-cs-rCZ/nfc_beam_share.html b/OpenKeychain/src/main/res/raw-cs-rCZ/nfc_beam_share.html
new file mode 100644
index 000000000..083e055c7
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-cs-rCZ/nfc_beam_share.html
@@ -0,0 +1,11 @@
+<html>
+<head></head>
+<body>
+<ol>
+<li>Make sure that NFC is turned on in Settings &gt; More &gt; NFC and make sure that Android Beam is also on in the same section.</li>
+<li>Hold the two devices back to back (they have to be almost touching) and you'll feel a vibration.</li>
+<li>After it vibrates you'll see the content on your device turn into a card-like object with Star Trek warp speed-looking animation in the background.</li>
+<li>Tap the card and the content will then load on the other person’s device.</li>
+</ol>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-de/help_about.html b/OpenKeychain/src/main/res/raw-de/help_about.html
new file mode 100644
index 000000000..7dc0ee7d9
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-de/help_about.html
@@ -0,0 +1,49 @@
+<html>
+<head></head>
+<body>
+<p><a href="http://www.openkeychain.org">http://www.openkeychain.org</a></p>
+<p><a href="http://www.openkeychain.org">OpenKeychain</a> ist eine OpenPGP implementation für Android.</p>
+<p>Lizenz: GPLv3+</p>
+
+<h2>Entwickler OpenKeychain</h2>
+<ul>
+<li>Dominik Schürmann (Leitender Entwickler)</li>
+<li>Ash Hughes (crypto patches)</li>
+<li>Brian C. Barnes</li>
+<li>Bahtiar 'kalkin' Gadimov (UI)</li>
+<li>Daniel Hammann</li>
+<li>Daniel Haß</li>
+<li>Greg Witczak</li>
+<li>Miroojin Bakshi</li>
+<li>Nikhil Peter Raj</li>
+<li>Paul Sarbinowski</li>
+<li>Sreeram Boyapati</li>
+<li>Vincent Breitmoser</li>
+</ul>
+<h2>Entwickler APG 1.x</h2>
+<ul>
+<li>Thialfihar (Lead developer)</li>
+<li>'Senecaso' (QR-Code, Schlüssel signtieren, Schlüssel hochladen)</li>
+<li>Markus Doits</li>
+</ul>
+<h2>Bibliotheken</h2>
+<ul>
+<li>
+<a href="http://developer.android.com/tools/support-library/index.html">Android Support Bibliothek v4</a> (Apache Lizenz v2)</li>
+<li>
+<a href="http://developer.android.com/tools/support-library/index.html">Android Support Bibliothek v7 'appcompat'</a> (Apache Lizenz v2)</li>
+<li>
+<a href="https://github.com/emilsjolander/StickyListHeaders">StickyListHeaders</a> (Apache Lizenz v2)</li>
+<li>
+<a href="https://github.com/Bearded-Hen/Android-Bootstrap">Android-Bootstrap</a> (MIT Lizenz)</li>
+<li>
+<a href="http://code.google.com/p/zxing/">ZXing</a> (Apache Lizenz v2)</li>
+<li>
+<a href="http://rtyley.github.com/spongycastle/">SpongyCastle</a> (MIT X11 Lizenz)</li>
+<li>
+<a href="https://github.com/dschuermann/html-textview">HtmlTextView</a> (Apache Lizenz v2)</li>
+<li>
+<a href="https://github.com/johnkil/Android-AppMsg">Android AppMsg Bibliothek</a> (Apache Lizenz v2)</li>
+</ul>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-de/help_changelog.html b/OpenKeychain/src/main/res/raw-de/help_changelog.html
new file mode 100644
index 000000000..58199a9c8
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-de/help_changelog.html
@@ -0,0 +1,136 @@
+<html>
+<head></head>
+<body>
+<h2>2.5</h2>
+<ul>
+<li>fix decryption of symmetric pgp messages/files</li>
+<li>refactored edit key screen (thanks to Ash Hughes)</li>
+<li>new modern design for encrypt/decrypt screens</li>
+<li>OpenPGP API version 3 (multiple api accounts, internal fixes, key lookup)</li>
+</ul>
+<h2>2.4</h2>
+<p>Thanks to all applicants of Google Summer of Code 2014 who made this release feature rich and bug free!
+Besides several small patches, a notable number of patches are made by the following people (in alphabetical order):
+Daniel Hammann, Daniel Haß, Greg Witczak, Miroojin Bakshi, Nikhil Peter Raj, Paul Sarbinowski, Sreeram Boyapati, Vincent Breitmoser.</p>
+<ul>
+<li>new unified key list</li>
+<li>colorized key fingerprint</li>
+<li>support for keyserver ports</li>
+<li>deactivate possibility to generate weak keys</li>
+<li>much more internal work on the API</li>
+<li>certify user ids</li>
+<li>keyserver query based on machine-readable output</li>
+<li>lock navigation drawer on tablets</li>
+<li>suggestions for emails on creation of keys</li>
+<li>search in public key lists</li>
+<li>and much more improvements and fixes…</li>
+</ul>
+<h2>2.3.1</h2>
+<ul>
+<li>hotfix for crash when upgrading from old versions</li>
+</ul>
+<h2>2.3</h2>
+<ul>
+<li>remove unnecessary export of public keys when exporting secret key (thanks to Ash Hughes)</li>
+<li>fix setting expiry dates on keys (thanks to Ash Hughes)</li>
+<li>more internal fixes when editing keys (thanks to Ash Hughes)</li>
+<li>querying keyservers directly from the import screen</li>
+<li>fix layout and dialog style on Android 2.2-3.0</li>
+<li>Absturz bei leeren Nutzer IDs behoben </li>
+<li>fix crash and empty lists when coming back from signing screen</li>
+<li>Bouncy Castle (cryptography library) updated from 1.47 to 1.50 and build from source</li>
+<li>fix upload of key from signing screen</li>
+</ul>
+<h2>2.2</h2>
+<ul>
+<li>new design with navigation drawer</li>
+<li>Neus Design für die Liste der öffentlichen Schlüssel</li>
+<li>new public key view</li>
+<li>Fehler beim Schlüsselimport behoben</li>
+<li>key cross-certification (thanks to Ash Hughes)</li>
+<li>handle UTF-8 passwords properly (thanks to Ash Hughes)</li>
+<li>Erste Version mit neuen Sprachen (Danke an die Mitwirkenden bei Transifex)</li>
+<li>sharing of keys via QR Codes fixed and improved</li>
+<li>package signature verification for API</li>
+</ul>
+<h2>2.1.1</h2>
+<ul>
+<li>API Updates, preparation for K-9 Mail integration</li>
+</ul>
+<h2>2.1</h2>
+<ul>
+<li>Viele Fehler behoben</li>
+<li>Neue API für Entwickler</li>
+<li>PRNG Bugfix von Google</li>
+</ul>
+<h2>2.0</h2>
+<ul>
+<li>Komlett neu designd</li>
+<li>Öffentliche Schlüssel teilen via QR Code, NFC Beam</li>
+<li>Schlüssel signieren</li>
+<li>Schlüssel auf den Server hochladen</li>
+<li>Importprobleme behoben</li>
+<li>new AIDL API</li>
+</ul>
+<h2>1.0.8</h2>
+<ul>
+<li>Grundlegende Schlüsselserverunterstützung</li>
+<li>app2sd</li>
+<li>mehr Auswahlmöglichkeiten für den Passwortcache: 1, 2, 4, 8, Stunden</li>
+<li>Übersetzungen: norwegisch (Danke, Sander Danielsen), chinesisch (danke, Zhang Fredrick)</li>
+<li>Fehlerbehebungen</li>
+<li>Optimierungen</li>
+</ul>
+<h2>1.0.7</h2>
+<ul>
+<li>fixed problem with signature verification of texts with trailing newline</li>
+<li>weitere Optionen für die Time-to-live des Passphrasencaches (20, 40, 60 mins)</li>
+</ul>
+<h2>1.0.6</h2>
+<ul>
+<li>crash beim Hinzufügen eines Kontos auf Froyo repariert</li>
+<li>sichere Dateilöschung</li>
+<li>Option, um Schlüsseldatei nach dem Import zu löschen</li>
+<li>Streamverschlüsselung/-entschlüsselung (Galerie, etc.)</li>
+<li>neue Optionen (Sprache, v3-Unterschriften erzwingen)</li>
+<li>Interfaceänderungen</li>
+<li>Fehlerbehebungen</li>
+</ul>
+<h2>1.0.5</h2>
+<ul>
+<li>Deutsche und Italienische Übersetzung</li>
+<li>viel kleineres Paket, dank reduzierter BC Quellen</li>
+<li>Neues Einstellungs-GUI</li>
+<li>Lay-Out-Anpassung für die Lokalisierung</li>
+<li>Fehler bei Signatur behoben</li>
+</ul>
+<h2>1.0.4</h2>
+<ul>
+<li>fixed another crash caused by some SDK bug with query builder</li>
+</ul>
+<h2>1.0.3</h2>
+<ul>
+<li>Absturz während der Verschlüsselung/Signierung und möglicherweise Schlüsselexport behoben.</li>
+</ul>
+<h2>1.0.2</h2>
+<ul>
+<li>Filterbare Schlüsselliste</li>
+<li>smarter pre-selection of encryption keys</li>
+<li>new Intent handling for VIEW and SEND, allows files to be encrypted/decrypted out of file managers</li>
+<li>fixes and additional features (key preselection) for K-9 Mail, new beta build available</li>
+</ul>
+<h2>1.0.1</h2>
+<ul>
+<li>GMail account listing was broken in 1.0.0, fixed again</li>
+</ul>
+<h2>1.0.0</h2>
+<ul>
+<li>K-9 Mail integration, APG supporting beta build of K-9 Mail</li>
+<li>Unterstützung von mehr Filemanagern (einschließlich ASTRO)</li>
+<li>Slowenische Übersetzung</li>
+<li>Neue Datenbank, viel schneller, weniger Speicherbedarf</li>
+<li>defined Intents and content provider for other apps</li>
+<li>Fehlerbehebungen</li>
+</ul>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-de/help_nfc_beam.html b/OpenKeychain/src/main/res/raw-de/help_nfc_beam.html
new file mode 100644
index 000000000..88492731c
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-de/help_nfc_beam.html
@@ -0,0 +1,12 @@
+<html>
+<head></head>
+<body>
+<h2>How to receive keys</h2>
+<ol>
+<li>Go to your partners contacts and open the contact you want to share.</li>
+<li>Hold the two devices back to back (they have to be almost touching) and you’ll feel a vibration.</li>
+<li>After it vibrates you’ll see the content on your partners device turn into a card-like object with Star Trek warp speed-looking animation in the background.</li>
+<li>Tap the card and the content will then load on the your device.</li>
+</ol>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-de/help_start.html b/OpenKeychain/src/main/res/raw-de/help_start.html
new file mode 100644
index 000000000..7a652682e
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-de/help_start.html
@@ -0,0 +1,19 @@
+<html>
+<head></head>
+<body>
+<h2>Los gehts</h2>
+<p>First you need a personal key pair. Create one via the option menus in "Contacts" or import existing key pairs via "Import Keys". Afterwards, you can download your friends' keys or exchange them via QR Codes or NFC.</p>
+
+<p>It is recommended that you install <a href="market://details?id=org.openintents.filemanager">OI File Manager</a> for enhanced file selection and <a href="market://details?id=com.google.zxing.client.android">Barcode Scanner</a> to scan generated QR Codes. Clicking on the links will open Google Play Store or F-Droid for installation.</p>
+
+<h2>Ich habe einen Fehler in OpenKeychain gefunden!</h2>
+<p>Please report the bug using the <a href="https://github.com/openpgp-keychain/openpgp-keychain/issues">issue tracker of OpenKeychain</a>.</p>
+
+<h2>Unterstützen</h2>
+<p>If you want to help us developing OpenKeychain by contributing code <a href="https://github.com/openpgp-keychain/openpgp-keychain#contribute-code">follow our small guide on Github</a>.</p>
+
+<h2>Übersetzungen</h2>
+<p>Help translating OpenKeychain! Everybody can participate at <a href="https://www.transifex.com/projects/p/openpgp-keychain/">OpenKeychain on Transifex</a>.</p>
+
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-de/nfc_beam_share.html b/OpenKeychain/src/main/res/raw-de/nfc_beam_share.html
new file mode 100644
index 000000000..083e055c7
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-de/nfc_beam_share.html
@@ -0,0 +1,11 @@
+<html>
+<head></head>
+<body>
+<ol>
+<li>Make sure that NFC is turned on in Settings &gt; More &gt; NFC and make sure that Android Beam is also on in the same section.</li>
+<li>Hold the two devices back to back (they have to be almost touching) and you'll feel a vibration.</li>
+<li>After it vibrates you'll see the content on your device turn into a card-like object with Star Trek warp speed-looking animation in the background.</li>
+<li>Tap the card and the content will then load on the other person’s device.</li>
+</ol>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-el/help_about.html b/OpenKeychain/src/main/res/raw-el/help_about.html
new file mode 100644
index 000000000..ae7e16aae
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-el/help_about.html
@@ -0,0 +1,49 @@
+<html>
+<head></head>
+<body>
+<p><a href="http://www.openkeychain.org">http://www.openkeychain.org</a></p>
+<p><a href="http://www.openkeychain.org">OpenKeychain</a> is an OpenPGP implementation for Android.</p>
+<p>License: GPLv3+</p>
+
+<h2>Developers OpenKeychain</h2>
+<ul>
+<li>Dominik Schürmann (Lead developer)</li>
+<li>Ash Hughes (crypto patches)</li>
+<li>Brian C. Barnes</li>
+<li>Bahtiar 'kalkin' Gadimov (UI)</li>
+<li>Daniel Hammann</li>
+<li>Daniel Haß</li>
+<li>Greg Witczak</li>
+<li>Miroojin Bakshi</li>
+<li>Nikhil Peter Raj</li>
+<li>Paul Sarbinowski</li>
+<li>Sreeram Boyapati</li>
+<li>Vincent Breitmoser</li>
+</ul>
+<h2>Developers APG 1.x</h2>
+<ul>
+<li>Thialfihar (Lead developer)</li>
+<li>'Senecaso' (QRCode, sign key, upload key)</li>
+<li>Markus Doits</li>
+</ul>
+<h2>Libraries</h2>
+<ul>
+<li>
+<a href="http://developer.android.com/tools/support-library/index.html">Android Support Library v4</a> (Apache License v2)</li>
+<li>
+<a href="http://developer.android.com/tools/support-library/index.html">Android Support Library v7 'appcompat'</a> (Apache License v2)</li>
+<li>
+<a href="https://github.com/emilsjolander/StickyListHeaders">StickyListHeaders</a> (Apache License v2)</li>
+<li>
+<a href="https://github.com/Bearded-Hen/Android-Bootstrap">Android-Bootstrap</a> (MIT License)</li>
+<li>
+<a href="http://code.google.com/p/zxing/">ZXing</a> (Apache License v2)</li>
+<li>
+<a href="http://rtyley.github.com/spongycastle/">SpongyCastle</a> (MIT X11 License)</li>
+<li>
+<a href="https://github.com/dschuermann/html-textview">HtmlTextView</a> (Apache License v2)</li>
+<li>
+<a href="https://github.com/johnkil/Android-AppMsg">Android AppMsg Library</a> (Apache License v2)</li>
+</ul>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-el/help_changelog.html b/OpenKeychain/src/main/res/raw-el/help_changelog.html
new file mode 100644
index 000000000..694c7b046
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-el/help_changelog.html
@@ -0,0 +1,136 @@
+<html>
+<head></head>
+<body>
+<h2>2.5</h2>
+<ul>
+<li>fix decryption of symmetric pgp messages/files</li>
+<li>refactored edit key screen (thanks to Ash Hughes)</li>
+<li>new modern design for encrypt/decrypt screens</li>
+<li>OpenPGP API version 3 (multiple api accounts, internal fixes, key lookup)</li>
+</ul>
+<h2>2.4</h2>
+<p>Thanks to all applicants of Google Summer of Code 2014 who made this release feature rich and bug free!
+Besides several small patches, a notable number of patches are made by the following people (in alphabetical order):
+Daniel Hammann, Daniel Haß, Greg Witczak, Miroojin Bakshi, Nikhil Peter Raj, Paul Sarbinowski, Sreeram Boyapati, Vincent Breitmoser.</p>
+<ul>
+<li>new unified key list</li>
+<li>colorized key fingerprint</li>
+<li>support for keyserver ports</li>
+<li>deactivate possibility to generate weak keys</li>
+<li>much more internal work on the API</li>
+<li>certify user ids</li>
+<li>keyserver query based on machine-readable output</li>
+<li>lock navigation drawer on tablets</li>
+<li>suggestions for emails on creation of keys</li>
+<li>search in public key lists</li>
+<li>and much more improvements and fixes…</li>
+</ul>
+<h2>2.3.1</h2>
+<ul>
+<li>hotfix for crash when upgrading from old versions</li>
+</ul>
+<h2>2.3</h2>
+<ul>
+<li>remove unnecessary export of public keys when exporting secret key (thanks to Ash Hughes)</li>
+<li>fix setting expiry dates on keys (thanks to Ash Hughes)</li>
+<li>more internal fixes when editing keys (thanks to Ash Hughes)</li>
+<li>querying keyservers directly from the import screen</li>
+<li>fix layout and dialog style on Android 2.2-3.0</li>
+<li>fix crash on keys with empty user ids</li>
+<li>fix crash and empty lists when coming back from signing screen</li>
+<li>Bouncy Castle (cryptography library) updated from 1.47 to 1.50 and build from source</li>
+<li>fix upload of key from signing screen</li>
+</ul>
+<h2>2.2</h2>
+<ul>
+<li>new design with navigation drawer</li>
+<li>new public key list design</li>
+<li>new public key view</li>
+<li>bug fixes for importing of keys</li>
+<li>key cross-certification (thanks to Ash Hughes)</li>
+<li>handle UTF-8 passwords properly (thanks to Ash Hughes)</li>
+<li>first version with new languages (thanks to the contributors on Transifex)</li>
+<li>sharing of keys via QR Codes fixed and improved</li>
+<li>package signature verification for API</li>
+</ul>
+<h2>2.1.1</h2>
+<ul>
+<li>API Updates, preparation for K-9 Mail integration</li>
+</ul>
+<h2>2.1</h2>
+<ul>
+<li>lots of bug fixes</li>
+<li>new API for developers</li>
+<li>PRNG bug fix by Google</li>
+</ul>
+<h2>2.0</h2>
+<ul>
+<li>complete redesign</li>
+<li>share public keys via qr codes, nfc beam</li>
+<li>sign keys</li>
+<li>upload keys to server</li>
+<li>fixes import issues</li>
+<li>new AIDL API</li>
+</ul>
+<h2>1.0.8</h2>
+<ul>
+<li>basic keyserver support</li>
+<li>app2sd</li>
+<li>more choices for pass phrase cache: 1, 2, 4, 8, hours</li>
+<li>translations: Norwegian (thanks, Sander Danielsen), Chinese (thanks, Zhang Fredrick)</li>
+<li>bugfixes</li>
+<li>optimizations</li>
+</ul>
+<h2>1.0.7</h2>
+<ul>
+<li>fixed problem with signature verification of texts with trailing newline</li>
+<li>more options for pass phrase cache time to live (20, 40, 60 mins)</li>
+</ul>
+<h2>1.0.6</h2>
+<ul>
+<li>account adding crash on Froyo fixed</li>
+<li>secure file deletion</li>
+<li>option to delete key file after import</li>
+<li>stream encryption/decryption (gallery, etc.)</li>
+<li>new options (language, force v3 signatures)</li>
+<li>interface changes</li>
+<li>bugfixes</li>
+</ul>
+<h2>1.0.5</h2>
+<ul>
+<li>German and Italian translation</li>
+<li>much smaller package, due to reduced BC sources</li>
+<li>new preferences GUI</li>
+<li>layout adjustment for localization</li>
+<li>signature bugfix</li>
+</ul>
+<h2>1.0.4</h2>
+<ul>
+<li>fixed another crash caused by some SDK bug with query builder</li>
+</ul>
+<h2>1.0.3</h2>
+<ul>
+<li>fixed crashes during encryption/signing and possibly key export</li>
+</ul>
+<h2>1.0.2</h2>
+<ul>
+<li>filterable key lists</li>
+<li>smarter pre-selection of encryption keys</li>
+<li>new Intent handling for VIEW and SEND, allows files to be encrypted/decrypted out of file managers</li>
+<li>fixes and additional features (key preselection) for K-9 Mail, new beta build available</li>
+</ul>
+<h2>1.0.1</h2>
+<ul>
+<li>GMail account listing was broken in 1.0.0, fixed again</li>
+</ul>
+<h2>1.0.0</h2>
+<ul>
+<li>K-9 Mail integration, APG supporting beta build of K-9 Mail</li>
+<li>support of more file managers (including ASTRO)</li>
+<li>Slovenian translation</li>
+<li>new database, much faster, less memory usage</li>
+<li>defined Intents and content provider for other apps</li>
+<li>bugfixes</li>
+</ul>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-el/help_nfc_beam.html b/OpenKeychain/src/main/res/raw-el/help_nfc_beam.html
new file mode 100644
index 000000000..88492731c
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-el/help_nfc_beam.html
@@ -0,0 +1,12 @@
+<html>
+<head></head>
+<body>
+<h2>How to receive keys</h2>
+<ol>
+<li>Go to your partners contacts and open the contact you want to share.</li>
+<li>Hold the two devices back to back (they have to be almost touching) and you’ll feel a vibration.</li>
+<li>After it vibrates you’ll see the content on your partners device turn into a card-like object with Star Trek warp speed-looking animation in the background.</li>
+<li>Tap the card and the content will then load on the your device.</li>
+</ol>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-el/help_start.html b/OpenKeychain/src/main/res/raw-el/help_start.html
new file mode 100644
index 000000000..0e60c17a7
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-el/help_start.html
@@ -0,0 +1,19 @@
+<html>
+<head></head>
+<body>
+<h2>Getting started</h2>
+<p>First you need a personal key pair. Create one via the option menus in "Contacts" or import existing key pairs via "Import Keys". Afterwards, you can download your friends' keys or exchange them via QR Codes or NFC.</p>
+
+<p>It is recommended that you install <a href="market://details?id=org.openintents.filemanager">OI File Manager</a> for enhanced file selection and <a href="market://details?id=com.google.zxing.client.android">Barcode Scanner</a> to scan generated QR Codes. Clicking on the links will open Google Play Store or F-Droid for installation.</p>
+
+<h2>I found a bug in OpenKeychain!</h2>
+<p>Please report the bug using the <a href="https://github.com/openpgp-keychain/openpgp-keychain/issues">issue tracker of OpenKeychain</a>.</p>
+
+<h2>Contribute</h2>
+<p>If you want to help us developing OpenKeychain by contributing code <a href="https://github.com/openpgp-keychain/openpgp-keychain#contribute-code">follow our small guide on Github</a>.</p>
+
+<h2>Translations</h2>
+<p>Help translating OpenKeychain! Everybody can participate at <a href="https://www.transifex.com/projects/p/openpgp-keychain/">OpenKeychain on Transifex</a>.</p>
+
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-el/nfc_beam_share.html b/OpenKeychain/src/main/res/raw-el/nfc_beam_share.html
new file mode 100644
index 000000000..083e055c7
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-el/nfc_beam_share.html
@@ -0,0 +1,11 @@
+<html>
+<head></head>
+<body>
+<ol>
+<li>Make sure that NFC is turned on in Settings &gt; More &gt; NFC and make sure that Android Beam is also on in the same section.</li>
+<li>Hold the two devices back to back (they have to be almost touching) and you'll feel a vibration.</li>
+<li>After it vibrates you'll see the content on your device turn into a card-like object with Star Trek warp speed-looking animation in the background.</li>
+<li>Tap the card and the content will then load on the other person’s device.</li>
+</ol>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-es-rCO/help_about.html b/OpenKeychain/src/main/res/raw-es-rCO/help_about.html
new file mode 100644
index 000000000..ae7e16aae
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-es-rCO/help_about.html
@@ -0,0 +1,49 @@
+<html>
+<head></head>
+<body>
+<p><a href="http://www.openkeychain.org">http://www.openkeychain.org</a></p>
+<p><a href="http://www.openkeychain.org">OpenKeychain</a> is an OpenPGP implementation for Android.</p>
+<p>License: GPLv3+</p>
+
+<h2>Developers OpenKeychain</h2>
+<ul>
+<li>Dominik Schürmann (Lead developer)</li>
+<li>Ash Hughes (crypto patches)</li>
+<li>Brian C. Barnes</li>
+<li>Bahtiar 'kalkin' Gadimov (UI)</li>
+<li>Daniel Hammann</li>
+<li>Daniel Haß</li>
+<li>Greg Witczak</li>
+<li>Miroojin Bakshi</li>
+<li>Nikhil Peter Raj</li>
+<li>Paul Sarbinowski</li>
+<li>Sreeram Boyapati</li>
+<li>Vincent Breitmoser</li>
+</ul>
+<h2>Developers APG 1.x</h2>
+<ul>
+<li>Thialfihar (Lead developer)</li>
+<li>'Senecaso' (QRCode, sign key, upload key)</li>
+<li>Markus Doits</li>
+</ul>
+<h2>Libraries</h2>
+<ul>
+<li>
+<a href="http://developer.android.com/tools/support-library/index.html">Android Support Library v4</a> (Apache License v2)</li>
+<li>
+<a href="http://developer.android.com/tools/support-library/index.html">Android Support Library v7 'appcompat'</a> (Apache License v2)</li>
+<li>
+<a href="https://github.com/emilsjolander/StickyListHeaders">StickyListHeaders</a> (Apache License v2)</li>
+<li>
+<a href="https://github.com/Bearded-Hen/Android-Bootstrap">Android-Bootstrap</a> (MIT License)</li>
+<li>
+<a href="http://code.google.com/p/zxing/">ZXing</a> (Apache License v2)</li>
+<li>
+<a href="http://rtyley.github.com/spongycastle/">SpongyCastle</a> (MIT X11 License)</li>
+<li>
+<a href="https://github.com/dschuermann/html-textview">HtmlTextView</a> (Apache License v2)</li>
+<li>
+<a href="https://github.com/johnkil/Android-AppMsg">Android AppMsg Library</a> (Apache License v2)</li>
+</ul>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-es-rCO/help_changelog.html b/OpenKeychain/src/main/res/raw-es-rCO/help_changelog.html
new file mode 100644
index 000000000..694c7b046
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-es-rCO/help_changelog.html
@@ -0,0 +1,136 @@
+<html>
+<head></head>
+<body>
+<h2>2.5</h2>
+<ul>
+<li>fix decryption of symmetric pgp messages/files</li>
+<li>refactored edit key screen (thanks to Ash Hughes)</li>
+<li>new modern design for encrypt/decrypt screens</li>
+<li>OpenPGP API version 3 (multiple api accounts, internal fixes, key lookup)</li>
+</ul>
+<h2>2.4</h2>
+<p>Thanks to all applicants of Google Summer of Code 2014 who made this release feature rich and bug free!
+Besides several small patches, a notable number of patches are made by the following people (in alphabetical order):
+Daniel Hammann, Daniel Haß, Greg Witczak, Miroojin Bakshi, Nikhil Peter Raj, Paul Sarbinowski, Sreeram Boyapati, Vincent Breitmoser.</p>
+<ul>
+<li>new unified key list</li>
+<li>colorized key fingerprint</li>
+<li>support for keyserver ports</li>
+<li>deactivate possibility to generate weak keys</li>
+<li>much more internal work on the API</li>
+<li>certify user ids</li>
+<li>keyserver query based on machine-readable output</li>
+<li>lock navigation drawer on tablets</li>
+<li>suggestions for emails on creation of keys</li>
+<li>search in public key lists</li>
+<li>and much more improvements and fixes…</li>
+</ul>
+<h2>2.3.1</h2>
+<ul>
+<li>hotfix for crash when upgrading from old versions</li>
+</ul>
+<h2>2.3</h2>
+<ul>
+<li>remove unnecessary export of public keys when exporting secret key (thanks to Ash Hughes)</li>
+<li>fix setting expiry dates on keys (thanks to Ash Hughes)</li>
+<li>more internal fixes when editing keys (thanks to Ash Hughes)</li>
+<li>querying keyservers directly from the import screen</li>
+<li>fix layout and dialog style on Android 2.2-3.0</li>
+<li>fix crash on keys with empty user ids</li>
+<li>fix crash and empty lists when coming back from signing screen</li>
+<li>Bouncy Castle (cryptography library) updated from 1.47 to 1.50 and build from source</li>
+<li>fix upload of key from signing screen</li>
+</ul>
+<h2>2.2</h2>
+<ul>
+<li>new design with navigation drawer</li>
+<li>new public key list design</li>
+<li>new public key view</li>
+<li>bug fixes for importing of keys</li>
+<li>key cross-certification (thanks to Ash Hughes)</li>
+<li>handle UTF-8 passwords properly (thanks to Ash Hughes)</li>
+<li>first version with new languages (thanks to the contributors on Transifex)</li>
+<li>sharing of keys via QR Codes fixed and improved</li>
+<li>package signature verification for API</li>
+</ul>
+<h2>2.1.1</h2>
+<ul>
+<li>API Updates, preparation for K-9 Mail integration</li>
+</ul>
+<h2>2.1</h2>
+<ul>
+<li>lots of bug fixes</li>
+<li>new API for developers</li>
+<li>PRNG bug fix by Google</li>
+</ul>
+<h2>2.0</h2>
+<ul>
+<li>complete redesign</li>
+<li>share public keys via qr codes, nfc beam</li>
+<li>sign keys</li>
+<li>upload keys to server</li>
+<li>fixes import issues</li>
+<li>new AIDL API</li>
+</ul>
+<h2>1.0.8</h2>
+<ul>
+<li>basic keyserver support</li>
+<li>app2sd</li>
+<li>more choices for pass phrase cache: 1, 2, 4, 8, hours</li>
+<li>translations: Norwegian (thanks, Sander Danielsen), Chinese (thanks, Zhang Fredrick)</li>
+<li>bugfixes</li>
+<li>optimizations</li>
+</ul>
+<h2>1.0.7</h2>
+<ul>
+<li>fixed problem with signature verification of texts with trailing newline</li>
+<li>more options for pass phrase cache time to live (20, 40, 60 mins)</li>
+</ul>
+<h2>1.0.6</h2>
+<ul>
+<li>account adding crash on Froyo fixed</li>
+<li>secure file deletion</li>
+<li>option to delete key file after import</li>
+<li>stream encryption/decryption (gallery, etc.)</li>
+<li>new options (language, force v3 signatures)</li>
+<li>interface changes</li>
+<li>bugfixes</li>
+</ul>
+<h2>1.0.5</h2>
+<ul>
+<li>German and Italian translation</li>
+<li>much smaller package, due to reduced BC sources</li>
+<li>new preferences GUI</li>
+<li>layout adjustment for localization</li>
+<li>signature bugfix</li>
+</ul>
+<h2>1.0.4</h2>
+<ul>
+<li>fixed another crash caused by some SDK bug with query builder</li>
+</ul>
+<h2>1.0.3</h2>
+<ul>
+<li>fixed crashes during encryption/signing and possibly key export</li>
+</ul>
+<h2>1.0.2</h2>
+<ul>
+<li>filterable key lists</li>
+<li>smarter pre-selection of encryption keys</li>
+<li>new Intent handling for VIEW and SEND, allows files to be encrypted/decrypted out of file managers</li>
+<li>fixes and additional features (key preselection) for K-9 Mail, new beta build available</li>
+</ul>
+<h2>1.0.1</h2>
+<ul>
+<li>GMail account listing was broken in 1.0.0, fixed again</li>
+</ul>
+<h2>1.0.0</h2>
+<ul>
+<li>K-9 Mail integration, APG supporting beta build of K-9 Mail</li>
+<li>support of more file managers (including ASTRO)</li>
+<li>Slovenian translation</li>
+<li>new database, much faster, less memory usage</li>
+<li>defined Intents and content provider for other apps</li>
+<li>bugfixes</li>
+</ul>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-es-rCO/help_nfc_beam.html b/OpenKeychain/src/main/res/raw-es-rCO/help_nfc_beam.html
new file mode 100644
index 000000000..88492731c
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-es-rCO/help_nfc_beam.html
@@ -0,0 +1,12 @@
+<html>
+<head></head>
+<body>
+<h2>How to receive keys</h2>
+<ol>
+<li>Go to your partners contacts and open the contact you want to share.</li>
+<li>Hold the two devices back to back (they have to be almost touching) and you’ll feel a vibration.</li>
+<li>After it vibrates you’ll see the content on your partners device turn into a card-like object with Star Trek warp speed-looking animation in the background.</li>
+<li>Tap the card and the content will then load on the your device.</li>
+</ol>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-es-rCO/help_start.html b/OpenKeychain/src/main/res/raw-es-rCO/help_start.html
new file mode 100644
index 000000000..0e60c17a7
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-es-rCO/help_start.html
@@ -0,0 +1,19 @@
+<html>
+<head></head>
+<body>
+<h2>Getting started</h2>
+<p>First you need a personal key pair. Create one via the option menus in "Contacts" or import existing key pairs via "Import Keys". Afterwards, you can download your friends' keys or exchange them via QR Codes or NFC.</p>
+
+<p>It is recommended that you install <a href="market://details?id=org.openintents.filemanager">OI File Manager</a> for enhanced file selection and <a href="market://details?id=com.google.zxing.client.android">Barcode Scanner</a> to scan generated QR Codes. Clicking on the links will open Google Play Store or F-Droid for installation.</p>
+
+<h2>I found a bug in OpenKeychain!</h2>
+<p>Please report the bug using the <a href="https://github.com/openpgp-keychain/openpgp-keychain/issues">issue tracker of OpenKeychain</a>.</p>
+
+<h2>Contribute</h2>
+<p>If you want to help us developing OpenKeychain by contributing code <a href="https://github.com/openpgp-keychain/openpgp-keychain#contribute-code">follow our small guide on Github</a>.</p>
+
+<h2>Translations</h2>
+<p>Help translating OpenKeychain! Everybody can participate at <a href="https://www.transifex.com/projects/p/openpgp-keychain/">OpenKeychain on Transifex</a>.</p>
+
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-es-rCO/nfc_beam_share.html b/OpenKeychain/src/main/res/raw-es-rCO/nfc_beam_share.html
new file mode 100644
index 000000000..083e055c7
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-es-rCO/nfc_beam_share.html
@@ -0,0 +1,11 @@
+<html>
+<head></head>
+<body>
+<ol>
+<li>Make sure that NFC is turned on in Settings &gt; More &gt; NFC and make sure that Android Beam is also on in the same section.</li>
+<li>Hold the two devices back to back (they have to be almost touching) and you'll feel a vibration.</li>
+<li>After it vibrates you'll see the content on your device turn into a card-like object with Star Trek warp speed-looking animation in the background.</li>
+<li>Tap the card and the content will then load on the other person’s device.</li>
+</ol>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-es/help_about.html b/OpenKeychain/src/main/res/raw-es/help_about.html
new file mode 100644
index 000000000..7a4f61127
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-es/help_about.html
@@ -0,0 +1,49 @@
+<html>
+<head></head>
+<body>
+<p><a href="http://www.openkeychain.org">http://www.openkeychain.org</a></p>
+<p><a href="http://www.openkeychain.org">OpenKeychain</a> es una implementación de OpenPGP para Android.</p>
+<p>Licencia: GPLv3+</p>
+
+<h2>Desarrolladores OpenKeychain</h2>
+<ul>
+<li>Dominik Schürmann (Desarrollador principal)</li>
+<li>Ash Hughes (Parches cryptográficos)</li>
+<li>Brian C. Barnes</li>
+<li>Bahtiar 'kalkin' Gadimov (UI)</li>
+<li>Daniel Hammann</li>
+<li>Daniel Haß</li>
+<li>Greg Witczak</li>
+<li>Miroojin Bakshi</li>
+<li>Nikhil Peter Raj</li>
+<li>Paul Sarbinowski</li>
+<li>Sreeram Boyapati</li>
+<li>Vincent Breitmoser</li>
+</ul>
+<h2>Desarrolladores de APG 1.x</h2>
+<ul>
+<li>Thialfihar (Desarrollador principal)</li>
+<li>'Senecaso' (Código QR, clave de firma, carga de clave)</li>
+<li>Markus Doits</li>
+</ul>
+<h2>Librerías</h2>
+<ul>
+<li>
+<a href="http://developer.android.com/tools/support-library/index.html">Android Support Library v4</a> (Licencia Apache v2)</li>
+<li>
+<a href="http://developer.android.com/tools/support-library/index.html">Android Support Library v7 'appcompat'</a> (Licencia Apache v2)</li>
+<li>
+<a href="https://github.com/emilsjolander/StickyListHeaders">StickyListHeaders</a> (Licencia Apache v2)</li>
+<li>
+<a href="https://github.com/Bearded-Hen/Android-Bootstrap">Android-Bootstrap</a> (Licencia MIT)</li>
+<li>
+<a href="http://code.google.com/p/zxing/">ZXing</a> (Licencia Apache v2)</li>
+<li>
+<a href="http://rtyley.github.com/spongycastle/">SpongyCastle</a> (Licencia MIT X11)</li>
+<li>
+<a href="https://github.com/dschuermann/html-textview">HtmlTextView</a> (Licencia Apache v2)</li>
+<li>
+<a href="https://github.com/johnkil/Android-AppMsg">Librería Android AppMsg</a> (Licencia Apache v2)</li>
+</ul>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-es/help_changelog.html b/OpenKeychain/src/main/res/raw-es/help_changelog.html
new file mode 100644
index 000000000..4ad69fc20
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-es/help_changelog.html
@@ -0,0 +1,136 @@
+<html>
+<head></head>
+<body>
+<h2>2.5</h2>
+<ul>
+<li>corregido descifrado de mensajes/ficheros con pgp simétrico</li>
+<li>rediseñada la pantalla de edición de claves (gracias a Ash Hughes)</li>
+<li>diseño más moderno para las pantallas de cifrado/descifrado</li>
+<li>OpenPGP API versión 3 (múltiples cuentas API, reparaciones internas, comprobación de claves)</li>
+</ul>
+<h2>2.4</h2>
+<p>¡Gracias a todos los solicitantes de Google Summer of Code 2014, por hacer esta aplicación productiva y libre de errores!
+Además de varios parches pequeños, un notable número de correcciones fueron hechas por las siguientes personas (en orden alfabético):
+Daniel Hammann, Daniel Haß, Greg Witczak, Miroojin Bakshi, Nikhil Peter Raj, Paul Sarbinowski, Sreeram Boyapati, Vincent Breitmoser.</p>
+<ul>
+<li>nueva lista unificada de claves</li>
+<li>huella digital de la clave coloreada</li>
+<li>compatibilidad con puertos del servidor de claves</li>
+<li>desactivar la posibilidad de generar claves débiles</li>
+<li>mucho más trabajo en el interior de la API</li>
+<li>certificar las IDs de usuario</li>
+<li>consulta al servidor de claves basadas ​​en lecturas mecánicas</li>
+<li>cerrar navigation drawer en tabletas</li>
+<li>sugerencias para emails en la creación de claves</li>
+<li>buscar en las listas de claves públicas</li>
+<li>y muchas más mejoras y correcciones...</li>
+</ul>
+<h2>2.3.1</h2>
+<ul>
+<li>corrección del fallo cuando se actualiza desde versiones anteriores</li>
+</ul>
+<h2>2.3</h2>
+<ul>
+<li>elimina la exportación innecesaria de claves públicas cuando se exporta la clave secreta (gracias a Ash Hughes)</li>
+<li>corrige la configuración de la fecha de caducidad en las claves (gracias a Ash Hughes)</li>
+<li>más correcciones internas cuando se editan claves (gracias a Ash Hughes)</li>
+<li>consultar los servidores de claves directamente desde la ventana de importación</li>
+<li>corrige el diseño y estilo de mensajes en Android 2.2-3.0</li>
+<li>corrige error en claves con IDs de usuario vacías</li>
+<li>corrige fallo y listados vacíos cuando se regresa desde la pantalla de firma</li>
+<li>Bouncy Castle (librería criptográfica) actualizada de 1.47 a 1.50 y compilada desde la fuente</li>
+<li>corrige la carga de la clave desde la pantalla de firma</li>
+</ul>
+<h2>2.2</h2>
+<ul>
+<li>nuevo diseño con Navigation Drawer</li>
+<li>nuevo diseño de la lista de clave pública</li>
+<li>nueva vista de la clave pública</li>
+<li>correcciones en la importación de claves</li>
+<li>clave de certificación cruzada (gracias a Ash Hughes)</li>
+<li>manejo correcto de las contraseñas UTF-8 (gracias a Ash Hughes)</li>
+<li>primera versión con nuevos idiomas (gracias a los colaboradores en Transifex)</li>
+<li>compartir claves a través de códigos QR corregido y mejorado</li>
+<li>verificación por API del paquete de firma</li>
+</ul>
+<h2>2.1.1</h2>
+<ul>
+<li>Actualizaciones de la API, preparación para la integración con K-9 Mail</li>
+</ul>
+<h2>2.1</h2>
+<ul>
+<li>corrección de muchos bugs</li>
+<li>nueva API para desarrolladores</li>
+<li>corrección del bug PRNG por Google</li>
+</ul>
+<h2>2.0</h2>
+<ul>
+<li>completo rediseño</li>
+<li>compartir claves públicas a través de códigos QR, NFC, Beam</li>
+<li>claves de firma</li>
+<li>cargar claves al servidor</li>
+<li>corrige problemas importantes</li>
+<li>nueva API AIDL</li>
+</ul>
+<h2>1.0.8</h2>
+<ul>
+<li>compatibilidad básica de los servidores de claves</li>
+<li>app-a-sd</li>
+<li>más opciones para la caché de la frase de contraseña: 1, 2, 4, 8 horas</li>
+<li>traducciones: noruego (gracias, Sander Danielsen), chino (gracias, Zhang Fredrick)</li>
+<li>correcciones de errores</li>
+<li>optimizaciones</li>
+</ul>
+<h2>1.0.7</h2>
+<ul>
+<li>corregido el problema con la verificación de firma de textos que arrastran a una nueva línea</li>
+<li>más opciones para el tiempo de la caché de la frase de contraseña hasta ahora (20, 40, 60 mins)</li>
+</ul>
+<h2>1.0.6</h2>
+<ul>
+<li>corregido el problema al añadir cuentas en Froyo</li>
+<li>borrado seguro de archivo</li>
+<li>opción para borrar el archivo de clave después de importarlo</li>
+<li>flujo de cifrado/descifrado (galería, etc.)</li>
+<li>nuevas opciones (idioma, forzar firmas v3)</li>
+<li>cambios en la interfaz</li>
+<li>correcciones de errores</li>
+</ul>
+<h2>1.0.5</h2>
+<ul>
+<li>traducciones a alemán e italiano</li>
+<li>paquete de mucho menos tamaño, debido a fuentes BC reducidas</li>
+<li>nuevas preferencias en la GUI</li>
+<li>ajuste del diseño para localización</li>
+<li>corrección de error en la firma</li>
+</ul>
+<h2>1.0.4</h2>
+<ul>
+<li>corregido otro error causado por algún bug en el SDK con el constructor de consultas</li>
+</ul>
+<h2>1.0.3</h2>
+<ul>
+<li>corregidos los errores durante el cifrado/firma y probablemente en la exportación de la clave</li>
+</ul>
+<h2>1.0.2</h2>
+<ul>
+<li>listas de claves con filtro</li>
+<li>preselección de claves de cifrado más inteligente</li>
+<li>nuevo intento en el manejo para VER y ENVIAR, permite que los archivos sean cifrados/descifrados fuera de los gestores de archivos</li>
+<li>corrige y añade características (preselección de clave) para K-9 Mail, nueva compilación disponible</li>
+</ul>
+<h2>1.0.1</h2>
+<ul>
+<li>La enumeración de cuentas de GMail no funcionaba en 1.0.0, corregida de nuevo</li>
+</ul>
+<h2>1.0.0</h2>
+<ul>
+<li>integración con K-9 Mail, APG compatible con la compilación beta de K-9 Mail</li>
+<li>compatibilidad para más gestores de archivos (incluyendo ASTRO)</li>
+<li>traducción al esloveno</li>
+<li>nueva base de datos, más rápida, con menos demanda de memoria</li>
+<li>definidos los intentos y el proveedor de contenido para otras aplicaciones</li>
+<li>correcciones de errores</li>
+</ul>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-es/help_nfc_beam.html b/OpenKeychain/src/main/res/raw-es/help_nfc_beam.html
new file mode 100644
index 000000000..4a95680b5
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-es/help_nfc_beam.html
@@ -0,0 +1,12 @@
+<html>
+<head></head>
+<body>
+<h2>Cómo recibir las claves</h2>
+<ol>
+<li>Vete a los contactos de tu compañero y abre el contacto con el que quieres compartir</li>
+<li>Mantén los dos dispositivos de con ambos reversos juntos (tienen que estar casi en contacto) y notarás una vibración.</li>
+<li>Después de que vibre, verás el contenido en el dispositivo de tu compañero convertirse en una especie de ficha con una animación de Star Trek de fondo.</li>
+<li>Toca la ficha y el contenido se cargará en tu dispositivo.</li>
+</ol>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-es/help_start.html b/OpenKeychain/src/main/res/raw-es/help_start.html
new file mode 100644
index 000000000..d56399ef0
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-es/help_start.html
@@ -0,0 +1,19 @@
+<html>
+<head></head>
+<body>
+<h2>Primeros pasos</h2>
+<p>Primero necesitas un par de claves personales. Crea una a través de las opciones del menú "Contactos" o importa un par de claves ya existentes a través de "Importar claves". Después, puedes descargar las claves de tus amigos o intercambiarlas a través de códigos QR o NFC.</p>
+
+<p>Es recomendable que instales <a href="market://details?id=org.openintents.filemanager">OI File Manager</a> para una mejor selección de archivos y <a href="market://details?id=com.google.zxing.client.android">Barcode Scanner</a> para escanear los códigos QR generados. Pulsando en los enlaces se abrirá Google Play o F-Droid.</p>
+
+<h2>¡He encontrado un bug en OpenKeychain!</h2>
+<p>Por favor, informa de errores usando el <a href="https://github.com/openpgp-keychain/openpgp-keychain/issues">seguimiento de incidencias de OpenKeychain</a>.</p>
+
+<h2>Aportar</h2>
+<p>Si quieres ayudarnos con el desarrollo de OpenKeychain aportando código <a href="https://github.com/openpgp-keychain/openpgp-keychain#contribute-code">sigue nuestra pequeña guía en Github</a>.</p>
+
+<h2>Traducciones</h2>
+<p>¡Ayúdanos a traducir OpenKeychain! Todo el mundo es bienvenido en <a href="https://www.transifex.com/projects/p/openpgp-keychain/">Transifex - OpenKeychain</a>.</p>
+
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-es/nfc_beam_share.html b/OpenKeychain/src/main/res/raw-es/nfc_beam_share.html
new file mode 100644
index 000000000..b6c2a2278
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-es/nfc_beam_share.html
@@ -0,0 +1,11 @@
+<html>
+<head></head>
+<body>
+<ol>
+<li>Asegúrate de que NFC está encendido en Ajustes &gt; Más &gt; NFC, y asegúrate de que Android Beam está también activado en ese mismo apartado.</li>
+<li>Mantén los dos dispositivos con ambos reversos juntos (deben estar casi en contacto) y notarás una vibración.</li>
+<li>Después de la vibración verás el contenido de tu dispositivo convertirse en una especie de ficha con una animación de Star Trek de fondo.</li>
+<li>Pulsa la ficha y el contenido será cargado en el dispositivo de la otra persona.</li>
+</ol>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-et/help_about.html b/OpenKeychain/src/main/res/raw-et/help_about.html
new file mode 100644
index 000000000..ae7e16aae
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-et/help_about.html
@@ -0,0 +1,49 @@
+<html>
+<head></head>
+<body>
+<p><a href="http://www.openkeychain.org">http://www.openkeychain.org</a></p>
+<p><a href="http://www.openkeychain.org">OpenKeychain</a> is an OpenPGP implementation for Android.</p>
+<p>License: GPLv3+</p>
+
+<h2>Developers OpenKeychain</h2>
+<ul>
+<li>Dominik Schürmann (Lead developer)</li>
+<li>Ash Hughes (crypto patches)</li>
+<li>Brian C. Barnes</li>
+<li>Bahtiar 'kalkin' Gadimov (UI)</li>
+<li>Daniel Hammann</li>
+<li>Daniel Haß</li>
+<li>Greg Witczak</li>
+<li>Miroojin Bakshi</li>
+<li>Nikhil Peter Raj</li>
+<li>Paul Sarbinowski</li>
+<li>Sreeram Boyapati</li>
+<li>Vincent Breitmoser</li>
+</ul>
+<h2>Developers APG 1.x</h2>
+<ul>
+<li>Thialfihar (Lead developer)</li>
+<li>'Senecaso' (QRCode, sign key, upload key)</li>
+<li>Markus Doits</li>
+</ul>
+<h2>Libraries</h2>
+<ul>
+<li>
+<a href="http://developer.android.com/tools/support-library/index.html">Android Support Library v4</a> (Apache License v2)</li>
+<li>
+<a href="http://developer.android.com/tools/support-library/index.html">Android Support Library v7 'appcompat'</a> (Apache License v2)</li>
+<li>
+<a href="https://github.com/emilsjolander/StickyListHeaders">StickyListHeaders</a> (Apache License v2)</li>
+<li>
+<a href="https://github.com/Bearded-Hen/Android-Bootstrap">Android-Bootstrap</a> (MIT License)</li>
+<li>
+<a href="http://code.google.com/p/zxing/">ZXing</a> (Apache License v2)</li>
+<li>
+<a href="http://rtyley.github.com/spongycastle/">SpongyCastle</a> (MIT X11 License)</li>
+<li>
+<a href="https://github.com/dschuermann/html-textview">HtmlTextView</a> (Apache License v2)</li>
+<li>
+<a href="https://github.com/johnkil/Android-AppMsg">Android AppMsg Library</a> (Apache License v2)</li>
+</ul>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-et/help_changelog.html b/OpenKeychain/src/main/res/raw-et/help_changelog.html
new file mode 100644
index 000000000..694c7b046
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-et/help_changelog.html
@@ -0,0 +1,136 @@
+<html>
+<head></head>
+<body>
+<h2>2.5</h2>
+<ul>
+<li>fix decryption of symmetric pgp messages/files</li>
+<li>refactored edit key screen (thanks to Ash Hughes)</li>
+<li>new modern design for encrypt/decrypt screens</li>
+<li>OpenPGP API version 3 (multiple api accounts, internal fixes, key lookup)</li>
+</ul>
+<h2>2.4</h2>
+<p>Thanks to all applicants of Google Summer of Code 2014 who made this release feature rich and bug free!
+Besides several small patches, a notable number of patches are made by the following people (in alphabetical order):
+Daniel Hammann, Daniel Haß, Greg Witczak, Miroojin Bakshi, Nikhil Peter Raj, Paul Sarbinowski, Sreeram Boyapati, Vincent Breitmoser.</p>
+<ul>
+<li>new unified key list</li>
+<li>colorized key fingerprint</li>
+<li>support for keyserver ports</li>
+<li>deactivate possibility to generate weak keys</li>
+<li>much more internal work on the API</li>
+<li>certify user ids</li>
+<li>keyserver query based on machine-readable output</li>
+<li>lock navigation drawer on tablets</li>
+<li>suggestions for emails on creation of keys</li>
+<li>search in public key lists</li>
+<li>and much more improvements and fixes…</li>
+</ul>
+<h2>2.3.1</h2>
+<ul>
+<li>hotfix for crash when upgrading from old versions</li>
+</ul>
+<h2>2.3</h2>
+<ul>
+<li>remove unnecessary export of public keys when exporting secret key (thanks to Ash Hughes)</li>
+<li>fix setting expiry dates on keys (thanks to Ash Hughes)</li>
+<li>more internal fixes when editing keys (thanks to Ash Hughes)</li>
+<li>querying keyservers directly from the import screen</li>
+<li>fix layout and dialog style on Android 2.2-3.0</li>
+<li>fix crash on keys with empty user ids</li>
+<li>fix crash and empty lists when coming back from signing screen</li>
+<li>Bouncy Castle (cryptography library) updated from 1.47 to 1.50 and build from source</li>
+<li>fix upload of key from signing screen</li>
+</ul>
+<h2>2.2</h2>
+<ul>
+<li>new design with navigation drawer</li>
+<li>new public key list design</li>
+<li>new public key view</li>
+<li>bug fixes for importing of keys</li>
+<li>key cross-certification (thanks to Ash Hughes)</li>
+<li>handle UTF-8 passwords properly (thanks to Ash Hughes)</li>
+<li>first version with new languages (thanks to the contributors on Transifex)</li>
+<li>sharing of keys via QR Codes fixed and improved</li>
+<li>package signature verification for API</li>
+</ul>
+<h2>2.1.1</h2>
+<ul>
+<li>API Updates, preparation for K-9 Mail integration</li>
+</ul>
+<h2>2.1</h2>
+<ul>
+<li>lots of bug fixes</li>
+<li>new API for developers</li>
+<li>PRNG bug fix by Google</li>
+</ul>
+<h2>2.0</h2>
+<ul>
+<li>complete redesign</li>
+<li>share public keys via qr codes, nfc beam</li>
+<li>sign keys</li>
+<li>upload keys to server</li>
+<li>fixes import issues</li>
+<li>new AIDL API</li>
+</ul>
+<h2>1.0.8</h2>
+<ul>
+<li>basic keyserver support</li>
+<li>app2sd</li>
+<li>more choices for pass phrase cache: 1, 2, 4, 8, hours</li>
+<li>translations: Norwegian (thanks, Sander Danielsen), Chinese (thanks, Zhang Fredrick)</li>
+<li>bugfixes</li>
+<li>optimizations</li>
+</ul>
+<h2>1.0.7</h2>
+<ul>
+<li>fixed problem with signature verification of texts with trailing newline</li>
+<li>more options for pass phrase cache time to live (20, 40, 60 mins)</li>
+</ul>
+<h2>1.0.6</h2>
+<ul>
+<li>account adding crash on Froyo fixed</li>
+<li>secure file deletion</li>
+<li>option to delete key file after import</li>
+<li>stream encryption/decryption (gallery, etc.)</li>
+<li>new options (language, force v3 signatures)</li>
+<li>interface changes</li>
+<li>bugfixes</li>
+</ul>
+<h2>1.0.5</h2>
+<ul>
+<li>German and Italian translation</li>
+<li>much smaller package, due to reduced BC sources</li>
+<li>new preferences GUI</li>
+<li>layout adjustment for localization</li>
+<li>signature bugfix</li>
+</ul>
+<h2>1.0.4</h2>
+<ul>
+<li>fixed another crash caused by some SDK bug with query builder</li>
+</ul>
+<h2>1.0.3</h2>
+<ul>
+<li>fixed crashes during encryption/signing and possibly key export</li>
+</ul>
+<h2>1.0.2</h2>
+<ul>
+<li>filterable key lists</li>
+<li>smarter pre-selection of encryption keys</li>
+<li>new Intent handling for VIEW and SEND, allows files to be encrypted/decrypted out of file managers</li>
+<li>fixes and additional features (key preselection) for K-9 Mail, new beta build available</li>
+</ul>
+<h2>1.0.1</h2>
+<ul>
+<li>GMail account listing was broken in 1.0.0, fixed again</li>
+</ul>
+<h2>1.0.0</h2>
+<ul>
+<li>K-9 Mail integration, APG supporting beta build of K-9 Mail</li>
+<li>support of more file managers (including ASTRO)</li>
+<li>Slovenian translation</li>
+<li>new database, much faster, less memory usage</li>
+<li>defined Intents and content provider for other apps</li>
+<li>bugfixes</li>
+</ul>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-et/help_nfc_beam.html b/OpenKeychain/src/main/res/raw-et/help_nfc_beam.html
new file mode 100644
index 000000000..88492731c
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-et/help_nfc_beam.html
@@ -0,0 +1,12 @@
+<html>
+<head></head>
+<body>
+<h2>How to receive keys</h2>
+<ol>
+<li>Go to your partners contacts and open the contact you want to share.</li>
+<li>Hold the two devices back to back (they have to be almost touching) and you’ll feel a vibration.</li>
+<li>After it vibrates you’ll see the content on your partners device turn into a card-like object with Star Trek warp speed-looking animation in the background.</li>
+<li>Tap the card and the content will then load on the your device.</li>
+</ol>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-et/help_start.html b/OpenKeychain/src/main/res/raw-et/help_start.html
new file mode 100644
index 000000000..0e60c17a7
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-et/help_start.html
@@ -0,0 +1,19 @@
+<html>
+<head></head>
+<body>
+<h2>Getting started</h2>
+<p>First you need a personal key pair. Create one via the option menus in "Contacts" or import existing key pairs via "Import Keys". Afterwards, you can download your friends' keys or exchange them via QR Codes or NFC.</p>
+
+<p>It is recommended that you install <a href="market://details?id=org.openintents.filemanager">OI File Manager</a> for enhanced file selection and <a href="market://details?id=com.google.zxing.client.android">Barcode Scanner</a> to scan generated QR Codes. Clicking on the links will open Google Play Store or F-Droid for installation.</p>
+
+<h2>I found a bug in OpenKeychain!</h2>
+<p>Please report the bug using the <a href="https://github.com/openpgp-keychain/openpgp-keychain/issues">issue tracker of OpenKeychain</a>.</p>
+
+<h2>Contribute</h2>
+<p>If you want to help us developing OpenKeychain by contributing code <a href="https://github.com/openpgp-keychain/openpgp-keychain#contribute-code">follow our small guide on Github</a>.</p>
+
+<h2>Translations</h2>
+<p>Help translating OpenKeychain! Everybody can participate at <a href="https://www.transifex.com/projects/p/openpgp-keychain/">OpenKeychain on Transifex</a>.</p>
+
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-et/nfc_beam_share.html b/OpenKeychain/src/main/res/raw-et/nfc_beam_share.html
new file mode 100644
index 000000000..083e055c7
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-et/nfc_beam_share.html
@@ -0,0 +1,11 @@
+<html>
+<head></head>
+<body>
+<ol>
+<li>Make sure that NFC is turned on in Settings &gt; More &gt; NFC and make sure that Android Beam is also on in the same section.</li>
+<li>Hold the two devices back to back (they have to be almost touching) and you'll feel a vibration.</li>
+<li>After it vibrates you'll see the content on your device turn into a card-like object with Star Trek warp speed-looking animation in the background.</li>
+<li>Tap the card and the content will then load on the other person’s device.</li>
+</ol>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-fa-rIR/help_about.html b/OpenKeychain/src/main/res/raw-fa-rIR/help_about.html
new file mode 100644
index 000000000..ae7e16aae
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-fa-rIR/help_about.html
@@ -0,0 +1,49 @@
+<html>
+<head></head>
+<body>
+<p><a href="http://www.openkeychain.org">http://www.openkeychain.org</a></p>
+<p><a href="http://www.openkeychain.org">OpenKeychain</a> is an OpenPGP implementation for Android.</p>
+<p>License: GPLv3+</p>
+
+<h2>Developers OpenKeychain</h2>
+<ul>
+<li>Dominik Schürmann (Lead developer)</li>
+<li>Ash Hughes (crypto patches)</li>
+<li>Brian C. Barnes</li>
+<li>Bahtiar 'kalkin' Gadimov (UI)</li>
+<li>Daniel Hammann</li>
+<li>Daniel Haß</li>
+<li>Greg Witczak</li>
+<li>Miroojin Bakshi</li>
+<li>Nikhil Peter Raj</li>
+<li>Paul Sarbinowski</li>
+<li>Sreeram Boyapati</li>
+<li>Vincent Breitmoser</li>
+</ul>
+<h2>Developers APG 1.x</h2>
+<ul>
+<li>Thialfihar (Lead developer)</li>
+<li>'Senecaso' (QRCode, sign key, upload key)</li>
+<li>Markus Doits</li>
+</ul>
+<h2>Libraries</h2>
+<ul>
+<li>
+<a href="http://developer.android.com/tools/support-library/index.html">Android Support Library v4</a> (Apache License v2)</li>
+<li>
+<a href="http://developer.android.com/tools/support-library/index.html">Android Support Library v7 'appcompat'</a> (Apache License v2)</li>
+<li>
+<a href="https://github.com/emilsjolander/StickyListHeaders">StickyListHeaders</a> (Apache License v2)</li>
+<li>
+<a href="https://github.com/Bearded-Hen/Android-Bootstrap">Android-Bootstrap</a> (MIT License)</li>
+<li>
+<a href="http://code.google.com/p/zxing/">ZXing</a> (Apache License v2)</li>
+<li>
+<a href="http://rtyley.github.com/spongycastle/">SpongyCastle</a> (MIT X11 License)</li>
+<li>
+<a href="https://github.com/dschuermann/html-textview">HtmlTextView</a> (Apache License v2)</li>
+<li>
+<a href="https://github.com/johnkil/Android-AppMsg">Android AppMsg Library</a> (Apache License v2)</li>
+</ul>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-fa-rIR/help_changelog.html b/OpenKeychain/src/main/res/raw-fa-rIR/help_changelog.html
new file mode 100644
index 000000000..694c7b046
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-fa-rIR/help_changelog.html
@@ -0,0 +1,136 @@
+<html>
+<head></head>
+<body>
+<h2>2.5</h2>
+<ul>
+<li>fix decryption of symmetric pgp messages/files</li>
+<li>refactored edit key screen (thanks to Ash Hughes)</li>
+<li>new modern design for encrypt/decrypt screens</li>
+<li>OpenPGP API version 3 (multiple api accounts, internal fixes, key lookup)</li>
+</ul>
+<h2>2.4</h2>
+<p>Thanks to all applicants of Google Summer of Code 2014 who made this release feature rich and bug free!
+Besides several small patches, a notable number of patches are made by the following people (in alphabetical order):
+Daniel Hammann, Daniel Haß, Greg Witczak, Miroojin Bakshi, Nikhil Peter Raj, Paul Sarbinowski, Sreeram Boyapati, Vincent Breitmoser.</p>
+<ul>
+<li>new unified key list</li>
+<li>colorized key fingerprint</li>
+<li>support for keyserver ports</li>
+<li>deactivate possibility to generate weak keys</li>
+<li>much more internal work on the API</li>
+<li>certify user ids</li>
+<li>keyserver query based on machine-readable output</li>
+<li>lock navigation drawer on tablets</li>
+<li>suggestions for emails on creation of keys</li>
+<li>search in public key lists</li>
+<li>and much more improvements and fixes…</li>
+</ul>
+<h2>2.3.1</h2>
+<ul>
+<li>hotfix for crash when upgrading from old versions</li>
+</ul>
+<h2>2.3</h2>
+<ul>
+<li>remove unnecessary export of public keys when exporting secret key (thanks to Ash Hughes)</li>
+<li>fix setting expiry dates on keys (thanks to Ash Hughes)</li>
+<li>more internal fixes when editing keys (thanks to Ash Hughes)</li>
+<li>querying keyservers directly from the import screen</li>
+<li>fix layout and dialog style on Android 2.2-3.0</li>
+<li>fix crash on keys with empty user ids</li>
+<li>fix crash and empty lists when coming back from signing screen</li>
+<li>Bouncy Castle (cryptography library) updated from 1.47 to 1.50 and build from source</li>
+<li>fix upload of key from signing screen</li>
+</ul>
+<h2>2.2</h2>
+<ul>
+<li>new design with navigation drawer</li>
+<li>new public key list design</li>
+<li>new public key view</li>
+<li>bug fixes for importing of keys</li>
+<li>key cross-certification (thanks to Ash Hughes)</li>
+<li>handle UTF-8 passwords properly (thanks to Ash Hughes)</li>
+<li>first version with new languages (thanks to the contributors on Transifex)</li>
+<li>sharing of keys via QR Codes fixed and improved</li>
+<li>package signature verification for API</li>
+</ul>
+<h2>2.1.1</h2>
+<ul>
+<li>API Updates, preparation for K-9 Mail integration</li>
+</ul>
+<h2>2.1</h2>
+<ul>
+<li>lots of bug fixes</li>
+<li>new API for developers</li>
+<li>PRNG bug fix by Google</li>
+</ul>
+<h2>2.0</h2>
+<ul>
+<li>complete redesign</li>
+<li>share public keys via qr codes, nfc beam</li>
+<li>sign keys</li>
+<li>upload keys to server</li>
+<li>fixes import issues</li>
+<li>new AIDL API</li>
+</ul>
+<h2>1.0.8</h2>
+<ul>
+<li>basic keyserver support</li>
+<li>app2sd</li>
+<li>more choices for pass phrase cache: 1, 2, 4, 8, hours</li>
+<li>translations: Norwegian (thanks, Sander Danielsen), Chinese (thanks, Zhang Fredrick)</li>
+<li>bugfixes</li>
+<li>optimizations</li>
+</ul>
+<h2>1.0.7</h2>
+<ul>
+<li>fixed problem with signature verification of texts with trailing newline</li>
+<li>more options for pass phrase cache time to live (20, 40, 60 mins)</li>
+</ul>
+<h2>1.0.6</h2>
+<ul>
+<li>account adding crash on Froyo fixed</li>
+<li>secure file deletion</li>
+<li>option to delete key file after import</li>
+<li>stream encryption/decryption (gallery, etc.)</li>
+<li>new options (language, force v3 signatures)</li>
+<li>interface changes</li>
+<li>bugfixes</li>
+</ul>
+<h2>1.0.5</h2>
+<ul>
+<li>German and Italian translation</li>
+<li>much smaller package, due to reduced BC sources</li>
+<li>new preferences GUI</li>
+<li>layout adjustment for localization</li>
+<li>signature bugfix</li>
+</ul>
+<h2>1.0.4</h2>
+<ul>
+<li>fixed another crash caused by some SDK bug with query builder</li>
+</ul>
+<h2>1.0.3</h2>
+<ul>
+<li>fixed crashes during encryption/signing and possibly key export</li>
+</ul>
+<h2>1.0.2</h2>
+<ul>
+<li>filterable key lists</li>
+<li>smarter pre-selection of encryption keys</li>
+<li>new Intent handling for VIEW and SEND, allows files to be encrypted/decrypted out of file managers</li>
+<li>fixes and additional features (key preselection) for K-9 Mail, new beta build available</li>
+</ul>
+<h2>1.0.1</h2>
+<ul>
+<li>GMail account listing was broken in 1.0.0, fixed again</li>
+</ul>
+<h2>1.0.0</h2>
+<ul>
+<li>K-9 Mail integration, APG supporting beta build of K-9 Mail</li>
+<li>support of more file managers (including ASTRO)</li>
+<li>Slovenian translation</li>
+<li>new database, much faster, less memory usage</li>
+<li>defined Intents and content provider for other apps</li>
+<li>bugfixes</li>
+</ul>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-fa-rIR/help_nfc_beam.html b/OpenKeychain/src/main/res/raw-fa-rIR/help_nfc_beam.html
new file mode 100644
index 000000000..88492731c
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-fa-rIR/help_nfc_beam.html
@@ -0,0 +1,12 @@
+<html>
+<head></head>
+<body>
+<h2>How to receive keys</h2>
+<ol>
+<li>Go to your partners contacts and open the contact you want to share.</li>
+<li>Hold the two devices back to back (they have to be almost touching) and you’ll feel a vibration.</li>
+<li>After it vibrates you’ll see the content on your partners device turn into a card-like object with Star Trek warp speed-looking animation in the background.</li>
+<li>Tap the card and the content will then load on the your device.</li>
+</ol>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-fa-rIR/help_start.html b/OpenKeychain/src/main/res/raw-fa-rIR/help_start.html
new file mode 100644
index 000000000..93a305796
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-fa-rIR/help_start.html
@@ -0,0 +1,19 @@
+<html>
+<head></head>
+<body>
+<h2>شروع کار</h2>
+<p>First you need a personal key pair. Create one via the option menus in "Contacts" or import existing key pairs via "Import Keys". Afterwards, you can download your friends' keys or exchange them via QR Codes or NFC.</p>
+
+<p>It is recommended that you install <a href="market://details?id=org.openintents.filemanager">OI File Manager</a> for enhanced file selection and <a href="market://details?id=com.google.zxing.client.android">Barcode Scanner</a> to scan generated QR Codes. Clicking on the links will open Google Play Store or F-Droid for installation.</p>
+
+<h2>I found a bug in OpenKeychain!</h2>
+<p>Please report the bug using the <a href="https://github.com/openpgp-keychain/openpgp-keychain/issues">issue tracker of OpenKeychain</a>.</p>
+
+<h2>هم بخشی کردن</h2>
+<p>If you want to help us developing OpenKeychain by contributing code <a href="https://github.com/openpgp-keychain/openpgp-keychain#contribute-code">follow our small guide on Github</a>.</p>
+
+<h2>ترجمه ها</h2>
+<p>Help translating OpenKeychain! Everybody can participate at <a href="https://www.transifex.com/projects/p/openpgp-keychain/">OpenKeychain on Transifex</a>.</p>
+
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-fa-rIR/nfc_beam_share.html b/OpenKeychain/src/main/res/raw-fa-rIR/nfc_beam_share.html
new file mode 100644
index 000000000..083e055c7
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-fa-rIR/nfc_beam_share.html
@@ -0,0 +1,11 @@
+<html>
+<head></head>
+<body>
+<ol>
+<li>Make sure that NFC is turned on in Settings &gt; More &gt; NFC and make sure that Android Beam is also on in the same section.</li>
+<li>Hold the two devices back to back (they have to be almost touching) and you'll feel a vibration.</li>
+<li>After it vibrates you'll see the content on your device turn into a card-like object with Star Trek warp speed-looking animation in the background.</li>
+<li>Tap the card and the content will then load on the other person’s device.</li>
+</ol>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-fr/help_about.html b/OpenKeychain/src/main/res/raw-fr/help_about.html
new file mode 100644
index 000000000..00370c77e
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-fr/help_about.html
@@ -0,0 +1,49 @@
+<html>
+<head></head>
+<body>
+<p><a href="http://www.openkeychain.org">http://www.openkeychain.org</a></p>
+<p><a href="http://www.openkeychain.org">OpenKeychain</a> est une implémentation d'OpenPGP pour Android.</p>
+<p>Licence : GPLv3+</p>
+
+<h2>Les développeurs d'OpenKeychain</h2>
+<ul>
+<li>Dominik Schürmann (développeur principal)</li>
+<li>Ash Hughes (correctif crypto)</li>
+<li>Brian C. Barnes</li>
+<li>Bahtiar « kalkin » Gadimov (interface utilisateur)</li>
+<li>Daniel Hammann</li>
+<li>Daniel Haß</li>
+<li>Greg Witczak</li>
+<li>Miroojin Bakshi</li>
+<li>Nikhil Peter Raj</li>
+<li>Paul Sarbinowski</li>
+<li>Sreeram Boyapati</li>
+<li>Vincent Breitmoser</li>
+</ul>
+<h2>Les développeurs d'APG 1.x</h2>
+<ul>
+<li>Thialfihar (développeur principal)</li>
+<li>« Senecaso » (Code QR, signer/téléverser la clef)</li>
+<li>Markus Doits</li>
+</ul>
+<h2>Bibliothèques</h2>
+<ul>
+<li>
+<a href="http://developer.android.com/tools/support-library/index.html">Bibliothèque de soutien Android v4</a> (Licence Apache v2)</li>
+<li>
+<a href="http://developer.android.com/tools/support-library/index.html">Bibliothèque de soutien Android v7 « appcompat »</a> (Licence Apache v2)</li>
+<li>
+<a href="https://github.com/emilsjolander/StickyListHeaders">StickyListHeaders</a> (Licence Apache v2)</li>
+<li>
+<a href="https://github.com/Bearded-Hen/Android-Bootstrap">Android-Bootstrap</a> ( Licence MIT)</li>
+<li>
+<a href="http://code.google.com/p/zxing/">ZXing</a> (Licence Apache v2)</li>
+<li>
+<a href="http://rtyley.github.com/spongycastle/">SpongyCastle</a> (Licence MIT X11)</li>
+<li>
+<a href="https://github.com/dschuermann/html-textview">HtmlTextView</a> (Licence Apache v2)</li>
+<li>
+<a href="https://github.com/johnkil/Android-AppMsg">Bibliothèque Android AppMsg</a> (Licence Apache v2)</li>
+</ul>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-fr/help_changelog.html b/OpenKeychain/src/main/res/raw-fr/help_changelog.html
new file mode 100644
index 000000000..7a22eafbb
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-fr/help_changelog.html
@@ -0,0 +1,136 @@
+<html>
+<head></head>
+<body>
+<h2>2.5</h2>
+<ul>
+<li>corrige le déchiffrement des messages/fichiers pgp symétriques</li>
+<li>écran réusiné de modification des clefs (merci à Ash Hughes)</li>
+<li>nouveau style moderne des écrans de chiffrement/déchiffrement</li>
+<li>API OpenPGP version 3 (comptes multiples d'api, correctifs internes, recherche de clefs)</li>
+</ul>
+<h2>2.4</h2>
+<p>Merci à tous les participants de « Google Summer of Code 2014 » qui ont rendu cette version riche en fonctions et sans bogue !
+À part plusieurs petits correctifs, un nombre notable de correctifs ont été apportés par les personnes suivantes (par ordre alphabétique) :
+Daniel Hammann, Daniel Haß, Greg Witczak, Miroojin Bakshi, Nikhil Peter Raj, Paul Sarbinowski, Sreeram Boyapati, Vincent Breitmoser.</p>
+<ul>
+<li>Nouvelle liste de clefs unifiée</li>
+<li>empreinte de clef colorée</li>
+<li>prise en charge des ports du serveur de clefs</li>
+<li>désactiver la possibilité de générer des clefs faibles</li>
+<li>bien plus de travail interne sur l'API</li>
+<li>certifier les ID des utilisateurs</li>
+<li>requête du serveur de clef basée sur une sortie lisible par la machine</li>
+<li>verrouiller le tiroir de navigation sur les tablettes</li>
+<li>suggestions de courriels à la création des clefs</li>
+<li>recherche dans les listes de clefs publiques</li>
+<li>et bien plus d'améliorations et de correctifs...</li>
+</ul>
+<h2>2.3.1</h2>
+<ul>
+<li>correctif de plantage lors de la mise à niveau des anciennes versions</li>
+</ul>
+<h2>2.3</h2>
+<ul>
+<li>supprimer l'exportation non nécessaire des clefs publiques lors de l'exportation d'une clef secrète</li>
+<li>correctif de définition de la date date de péremption des clefs (merci à Ash Hughes)</li>
+<li>autres correctifs internes affectant la modifications des clefs (merci à Ash hughes)</li>
+<li>interrogation des serveurs de clefs directement depuis l'écran d'importation</li>
+<li>correctif de mise en page et du style des fenêtres de dialogue sur Android 2.2-3.0</li>
+<li>corrige un plantage pour les clefs avec des ID utilisateur vides</li>
+<li>corrige un plantage et des listes vides en revenant de l'écran de signature</li>
+<li>Bouncy Castle (bibliothèque cryptographique) mise à jour de 1.47 à 1.50 et compilée depuis la source</li>
+<li>correction du téléversement d'une clef depuis l'écran de signature</li>
+</ul>
+<h2>2.2</h2>
+<ul>
+<li>nouvelle conception avec tiroir de navigation</li>
+<li>nouveau style de liste des clefs publics</li>
+<li>nouvel affichage des clefs publics</li>
+<li>correctif de bogues d'importation de clefs</li>
+<li>certification croisée des clefs (merci à Ash Hughes)</li>
+<li>bonne gestion des mots de passe UTF-8 (merci à Ash Hughes)</li>
+<li>première version avec de nouvelles langues (merci aux contributeurs sur Transifex)</li>
+<li>correctif et amélioration du partage des clefs par codes QR</li>
+<li>vérification de la signature des paquets pour l'API</li>
+</ul>
+<h2>2.1.1</h2>
+<ul>
+<li>mise à jour de l'API, préparation à l'intégration à K-9 Mail</li>
+</ul>
+<h2>2.1</h2>
+<ul>
+<li>beaucoup de bogues corrigés</li>
+<li>nouvelle API pour les développeurs</li>
+<li>Correctif du blogue PRNG par Google</li>
+</ul>
+<h2>2.0</h2>
+<ul>
+<li>conception complètement repensée</li>
+<li>partage de clefs publiques par codes QR, faisceau NFC</li>
+<li>signer les clefs</li>
+<li>téléverser les clefs vers le serveur</li>
+<li>corrige les problèmes d'importation</li>
+<li>nouvelle API AIDL</li>
+</ul>
+<h2>1.0.8</h2>
+<ul>
+<li>prise en charge de base du serveur de clef</li>
+<li>app2sd</li>
+<li>plus de choix pour le cache de phrase de passe : 1, 2, 4, 8 heures</li>
+<li>traductions : norvégien (merci Sander Danielsen), chinois (merci Zhang Fredrick)</li>
+<li>correctifs de bogues</li>
+<li>optimisations</li>
+</ul>
+<h2>1.0.7</h2>
+<ul>
+<li>problème corrigé avec la vérification de la signature des textes se terminant par un retour à la ligne</li>
+<li>plus de choix pour la durée de vie de la phrase de passe : (20, 40, 60 min)</li>
+</ul>
+<h2>1.0.6</h2>
+<ul>
+<li>correction de l'ajout de compte sur Froyo</li>
+<li>suppression sécurisée de fichiers</li>
+<li>option de suppression du fichier de clef après l'importation</li>
+<li>chiffrement/déchiffrement de flux (galerie, etc...)</li>
+<li>nouvelles options (langue, forcer les signatures v3)</li>
+<li>changements dans l'interface</li>
+<li>correctifs de bogues</li>
+</ul>
+<h2>1.0.5</h2>
+<ul>
+<li>Traduction allemande et italienne</li>
+<li>paquet beaucoup plus petit grâce à des sources BC réduites</li>
+<li>nouvelle interface utilisateur pour les paramètres</li>
+<li>ajustement de la mise en page pour les localisations</li>
+<li>correctif d'un bogue de signature</li>
+</ul>
+<h2>1.0.4</h2>
+<ul>
+<li>correction d'un autre plantage causé par quelque bogue SDK avec le constructeur de requêtes</li>
+</ul>
+<h2>1.0.3</h2>
+<ul>
+<li>corrections de plantages durant le chiffrement/la signature et aussi peut-être l'exportation de clef</li>
+</ul>
+<h2>1.0.2</h2>
+<ul>
+<li>listes de clefs filtrables</li>
+<li>présélection plus intelligente des clefs de chiffrement</li>
+<li>nouvelle gestion des intentions pour VIEW et SEND, permet le chiffrement/déchiffrement des fichiers depuis les gestionnaires de fichiers</li>
+<li>correctifs et fonctions additionnelles (présélection des clefs) pour K-9-Mail, nouvelle version bêta disponible.</li>
+</ul>
+<h2>1.0.1</h2>
+<ul>
+<li>Le listage des comptes Gmail ne fonctionnait pas dans 1.0.0, corrigé de nouveau</li>
+</ul>
+<h2>1.0.0</h2>
+<ul>
+<li>Intégration à K-9 Mail, APG prenant en charge la version bêta de K-9 Mail</li>
+<li>prise en charge de plus de gestionnaires de fichiers (incluant ASTRO)</li>
+<li>Traduction slovène</li>
+<li>nouvelle base de données, bien plus rapide, utilisation de la mémoire moindre</li>
+<li>intentions définies et fournisseur de contenu pour d'autres applis</li>
+<li>correctifs de bogues</li>
+</ul>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-fr/help_nfc_beam.html b/OpenKeychain/src/main/res/raw-fr/help_nfc_beam.html
new file mode 100644
index 000000000..673e5b224
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-fr/help_nfc_beam.html
@@ -0,0 +1,12 @@
+<html>
+<head></head>
+<body>
+<h2>Comment recevoir des clefs</h2>
+<ol>
+<li>Allez aux contacts de votre partenaire et ouvrez le contact que vous voulez partager.</li>
+<li>Tenir les deux appareils dos à dos (se touchant presque) et une vibration sera ressentie.</li>
+<li>Après la vibration, le contenu de l'appareil de votre partenaire deviendra un objet en forme de carte avec une animation à la Star Trek en arrière-plan.</li>
+<li>Toquer la carte et le contenu se chargera alors sur votre appareil.</li>
+</ol>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-fr/help_start.html b/OpenKeychain/src/main/res/raw-fr/help_start.html
new file mode 100644
index 000000000..ddaac44b1
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-fr/help_start.html
@@ -0,0 +1,19 @@
+<html>
+<head></head>
+<body>
+<h2>Commencer</h2>
+<p>Il vous faut d'abord une paire de clefs personnelles. Créez-en une avec le menu des options dans « Contacts » ou importez des paires de clefs existantes avec « Importer des clefs ». Ensuite vous pouvez télécharger les clefs de vos amis, ou les échanger par codes QR ou NFC.</p>
+
+<p>Il vous est recommendé d'installer le <a href="market://details?id=org.openintents.filemanager">gestionnaire de fichiers OI</a> pour sa fonction améliorée de séléction des fichiers et le <a href="market://details?id=com.google.zxing.client.android">lecteur de codes à barres</a> pour balayer les codes QR générés. Cliquer sur les liens ouvrira Google Play Store ou F-Droid pour l'installation.</p>
+
+<h2>J'ai trouvé un bogue dans OpenKeychain !</h2>
+<p>Veuillez rapporter le bogue en utilisant le <a href="https://github.com/openpgp-keychain/openpgp-keychain/issues">gestionnaire de bogue d'OpenKeychain</a>.</p>
+
+<h2>Contribuer</h2>
+<p>Si vous voulez nous aider à développer OpenKeychain en y contribuant par du code <a href="https://github.com/openpgp-keychain/openpgp-keychain#contribute-code">veuillez suivre notre petit guide sur Github</a>.</p>
+
+<h2>Traductions</h2>
+<p>Aidez-nous à traduire le OpenKeychain ! Tout le monde peut y participer sur la <a href="https://www.transifex.com/projects/p/openpgp-keychain/">page d'OpenKeychain sur Transifex</a>.</p>
+
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-fr/nfc_beam_share.html b/OpenKeychain/src/main/res/raw-fr/nfc_beam_share.html
new file mode 100644
index 000000000..b63c9ac84
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-fr/nfc_beam_share.html
@@ -0,0 +1,11 @@
+<html>
+<head></head>
+<body>
+<ol>
+<li>Assurez-vous que la NFC est activée dans Paramètres &gt; Paramètres supplémentaires &gt; NFC, ainsi que Android Beam. </li>
+<li>Tenir les deux appareils dos à dos (se touchant presque) et une vibration sera ressentie.</li>
+<li>Après la vibration, le contenu de votre appareil deviendra un objet en forme de carte avec une animation à la Star Trek en arrière-plan.</li>
+<li>Toquer la carte et le contenu se chargera alors sur votre appareil.</li>
+</ol>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-it-rIT/help_about.html b/OpenKeychain/src/main/res/raw-it-rIT/help_about.html
new file mode 100644
index 000000000..8644d3fc6
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-it-rIT/help_about.html
@@ -0,0 +1,49 @@
+<html>
+<head></head>
+<body>
+<p><a href="http://www.openkeychain.org">http://www.openkeychain.org</a></p>
+<p><a href="http://www.openkeychain.org">OpenKeychain</a> un implementazione OpenPGP per Android.</p>
+<p>Licenza: GPLv3+</p>
+
+<h2>Sviluppatori OpenKeychain</h2>
+<ul>
+<li>Dominik Schürmann (Capo Sviluppatore)</li>
+<li>Ash Hughes (Patch crittografia)</li>
+<li>Brian C. Barnes</li>
+<li>Bahtiar 'kalkin' Gadimov (Interfaccia Utente)</li>
+<li>Daniel Hammann</li>
+<li>Daniel Haß</li>
+<li>Greg Witczak</li>
+<li>Miroojin Bakshi</li>
+<li>Nikhil Peter Raj</li>
+<li>Paul Sarbinowski</li>
+<li>Sreeram Boyapati</li>
+<li>Vincent Breitmoser</li>
+</ul>
+<h2>Sviluppatori APG 1.x</h2>
+<ul>
+<li>Thialfihar (Capo Sviluppatore)</li>
+<li>'Senecaso' (QRCode, firma chiavi, caricamento chiavi)</li>
+<li>Markus Doits</li>
+</ul>
+<h2>Librerie</h2>
+<ul>
+<li>
+<a href="http://developer.android.com/tools/support-library/index.html">Android Support Library v4</a> (Licenza Apache v2)</li>
+<li>
+<a href="http://developer.android.com/tools/support-library/index.html">Android Support Library v7 'appcompat'</a> (Licenza Apache v2)</li>
+<li>
+<a href="https://github.com/emilsjolander/StickyListHeaders">StickyListHeaders</a> (Licenza Apache v2)</li>
+<li>
+<a href="https://github.com/Bearded-Hen/Android-Bootstrap">Android-Bootstrap</a> (Licenza MIT)</li>
+<li>
+<a href="http://code.google.com/p/zxing/">ZXing</a> (Licenza Apache v2)</li>
+<li>
+<a href="http://rtyley.github.com/spongycastle/">SpongyCastle</a> (Licenza MIT X11)</li>
+<li>
+<a href="https://github.com/dschuermann/html-textview">HtmlTextView</a> (Licenza Apache v2)</li>
+<li>
+<a href="https://github.com/johnkil/Android-AppMsg">Android AppMsg Library</a> (Licenza Apache v2)</li>
+</ul>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-it-rIT/help_changelog.html b/OpenKeychain/src/main/res/raw-it-rIT/help_changelog.html
new file mode 100644
index 000000000..46604991c
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-it-rIT/help_changelog.html
@@ -0,0 +1,136 @@
+<html>
+<head></head>
+<body>
+<h2>2.5</h2>
+<ul>
+<li>Corretta la decodifica di messaggi PGP / file simmetrici</li>
+<li>Refactoring della schermata di modifica chiave (grazie a Ash Hughes)</li>
+<li>nuovo design moderno per le schermate di codifica / decodifica</li>
+<li>OpenPGP API versione 3 (api account multipli, correzioni interne, ricerca chiavi)</li>
+</ul>
+<h2>2.4</h2>
+<p>Grazie a tutti i partecipanti di Google Summer of Code 2014 che hanno reso questo rilascio ricco di caratteristiche e privo di bug!
+Oltre a numerose piccole correzioni, un notevole numero di patch sono state fatte dalle seguenti persone (in ordine alfabetico):
+Daniel Hammann, Daniel Haß, Greg Witczak, Miroojin Bakshi, Nikhil Peter Raj, Paolo Sarbinowski, Sreeram Boyapati, Vincent Breitmoser. </p>
+<ul>
+<li>nuova lista chiave unificata</li>
+<li>impronta chiave colorata</li>
+<li>supporto per porte</li>
+<li>disattiva la possibilità di generare chiavi deboli</li>
+<li>molto più lavoro interno sulle API</li>
+<li>certificazione ID utente</li>
+<li>interrogazione keyserver basate su output leggibile a livello macchina</li>
+<li>blocco del menu di navigazione sui tablet</li>
+<li>suggerimenti per e-mail sulla creazione di chiavi</li>
+<li>ricerca nelle liste di chiavi pubbliche</li>
+<li>e molti altri miglioramenti e correzioni ...</li>
+</ul>
+<h2>2.3.1</h2>
+<ul>
+<li>correzione del crash quando si aggiorna da versioni precedenti</li>
+</ul>
+<h2>2.3</h2>
+<ul>
+<li>rimossa esportazione non necessaria delle chiavi pubbliche quando si esportano le chiavi private (grazie a Ash Hughes)</li>
+<li>corretto impostazione data di scadenza delle chiavi (grazie a Ash Hughes)</li>
+<li>altre correzioni interne quando si modificano le chiavi (grazie a Ash Hughes)</li>
+<li>interrogazione server delle chiavi direttamente dalla schermata di importazione</li>
+<li>corretto layout e dialog style su Android 2.2-3.0</li>
+<li>corretto crash su chiavi con id utente vuoto</li>
+<li>corretto crash e liste vuote quando si torna dalla schermata di firma</li>
+<li>Bouncy Castle (libreria crittografica) aggiornata da 1.47 a 1.50 e compilata da sorgente</li>
+<li>corretto upload delle chiavi dalla schermata di firma</li>
+</ul>
+<h2>2.2</h2>
+<ul>
+<li>nuovo design con drawer di navigazione</li>
+<li>nuovo design per la lista chiavi pubbliche</li>
+<li>nuova visuale chiavi pubbliche</li>
+<li>correzione bug per importazione chiavi</li>
+<li>Chiave certificazione incrociata (grazie a Ash Hughes)</li>
+<li>password UTF-8 gestite correttamente (grazie a Ash Hughes)</li>
+<li>Prima versione con nuove lingue (grazie ai contributori su Transifex)</li>
+<li>condivisione di chiavi via Codici QR corretta e migliorata</li>
+<li>Verifica firma pacchetto per API</li>
+</ul>
+<h2>2.1.1</h2>
+<ul>
+<li>Aggiornamenti API, preparazione per integrazione con K-9 Mail</li>
+</ul>
+<h2>2.1</h2>
+<ul>
+<li>molte correzioni di bug</li>
+<li>nuove API per sviluppatori</li>
+<li>PRNG bug fix by Google</li>
+</ul>
+<h2>2.0</h2>
+<ul>
+<li>completo restyle</li>
+<li>condivisione chiavi pubbliche via codici qr, nfc beam</li>
+<li>firma chiavi</li>
+<li>Carica chiavi sul server</li>
+<li>corrette caratteristiche di importazione</li>
+<li>nuova AIDL API</li>
+</ul>
+<h2>1.0.8</h2>
+<ul>
+<li>supporto base per server delle chiavi</li>
+<li>app2sd</li>
+<li>Aggiunte opzioni per la cache della frase di accesso: 1, 2, 4, 8 ore</li>
+<li>Traduzioni: Norvegese (grazie, Sander Danielsen), Cinese (grazie, Zhang Fredrick)</li>
+<li>correzione bug</li>
+<li>ottimizzazioni</li>
+</ul>
+<h2>1.0.7</h2>
+<ul>
+<li>corretto problema con la verifica firma di testi con capo finale</li>
+<li>maggiori opzioni per il tempo di mantenimento della cache della frase di accesso (20, 40, 60 minuti)</li>
+</ul>
+<h2>1.0.6</h2>
+<ul>
+<li>crash della aggiunta degli account risolto su Froyo</li>
+<li>Cancellazione sicura dei file</li>
+<li>opzione per cancellare file delle chiavi dopo l'importazione</li>
+<li>flusso codifica/decodifica (galleria, ecc.)</li>
+<li>nuove opzioni (lingua, forza firme v3)</li>
+<li>cambiamenti interfaccia</li>
+<li>correzione bug</li>
+</ul>
+<h2>1.0.5</h2>
+<ul>
+<li>Traduzione Italiana e Tedesca</li>
+<li>dimensioni pacchetto ridotte, a causa della riduzione dei sorgenti BC</li>
+<li>Nuove preferenze GUI</li>
+<li>Regolazione layout per la localizzazione</li>
+<li>correzione bug firma</li>
+</ul>
+<h2>1.0.4</h2>
+<ul>
+<li>corretto altro crash causato da alcuni bug SDK con query builder</li>
+</ul>
+<h2>1.0.3</h2>
+<ul>
+<li>corretto crash durante codifica/firma e possibilita' di esportare chiave</li>
+</ul>
+<h2>1.0.2</h2>
+<ul>
+<li>liste chiavi filtrabili</li>
+<li>preselezione di chiavi di codifica intelligente</li>
+<li>nuovo gestore intent per VIEW e SEND, permette la codifica/decodifica file all'infuori di file manager</li>
+<li>caratteristiche corrette e aggiunte (preselezione chiavi) per K-9 Mail. nuova build beta disponibile</li>
+</ul>
+<h2>1.0.1</h2>
+<ul>
+<li>elencazione account GMail corrotta in 1.0.0, corretta nuovamente</li>
+</ul>
+<h2>1.0.0</h2>
+<ul>
+<li>integrazione K-9 Mail, APG supporto beta build di K-9 Mail</li>
+<li>supporto per altri file manager (incluso ASTRO)</li>
+<li>traduzione Slovena</li>
+<li>Nuovo database, piu' veloce, utilizzo memoria ridotto</li>
+<li>Definiti Intent e ContentProvider per le altre app</li>
+<li>correzione bug</li>
+</ul>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-it-rIT/help_nfc_beam.html b/OpenKeychain/src/main/res/raw-it-rIT/help_nfc_beam.html
new file mode 100644
index 000000000..6ff56194e
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-it-rIT/help_nfc_beam.html
@@ -0,0 +1,12 @@
+<html>
+<head></head>
+<body>
+<h2>Come ricevere le chiavi</h2>
+<ol>
+<li>Vai ai contatti dei tuoi partner e apri il contatto che si desidera condividere.</li>
+<li>Mantieni i due dispositivi vicini (devono quasi toccarsi) e sentirai una vibrazione.</li>
+<li>Dopo che ha vibrato, vedrai il contenuto del tuo dispositivo diventare come una scheda e nello sfondo apparirà una animazione come la propulsione a curvatura di Star Trek.</li>
+<li>Tocca la scheda e il contenuto dopo si trasferira' nel dispositivo dell'altra persona.</li>
+</ol>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-it-rIT/help_start.html b/OpenKeychain/src/main/res/raw-it-rIT/help_start.html
new file mode 100644
index 000000000..0fd24178c
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-it-rIT/help_start.html
@@ -0,0 +1,19 @@
+<html>
+<head></head>
+<body>
+<h2>Per iniziare</h2>
+<p>In primo luogo è necessario una coppia di chiavi personale. Creane una tramite l'opzione nel menu "Contatti" o importando coppie di chiavi esistenti tramite "Importa Chiavi". Successivamente, è possibile scaricare le chiavi dei vostri amici o scambiarle con i codici QR o NFC.</p>
+
+<p>Si raccomanda di installare <a href="market://details?id=org.openintents.filemanager">OI File Manager</a> per una migliore selezione dei file e <a href="market://details?id=com.google.zxing.client.android">Barcode Scanner</a> per scansionare i codici QR. I collegamenti verranno aperti in Google Play Store o F-Droid per l'installazione.</p>
+
+<h2>Ho trovato un bug in OpenKeychain!</h2>
+<p>Per favore riporta i bug usando il <a href="https://github.com/openpgp-keychain/openpgp-keychain/issues">issue tracker di OpenKeychain</a>.</p>
+
+<h2>Contribuire</h2>
+<p>Se vuoi aiutarci nello sviluppo di OpenKeychain contribuendo al codice <a href="https://github.com/openpgp-keychain/openpgp-keychain#contribute-code">segui la nostra breve guida su Github</a>.</p>
+
+<h2>Traduzioni</h2>
+<p>Aiutaci a tradurre OpenKeychain! Tutti possono collaborare a <a href="https://www.transifex.com/projects/p/openpgp-keychain/">OpenKeychain su Transifex</a>.</p>
+
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-it-rIT/nfc_beam_share.html b/OpenKeychain/src/main/res/raw-it-rIT/nfc_beam_share.html
new file mode 100644
index 000000000..e75877efe
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-it-rIT/nfc_beam_share.html
@@ -0,0 +1,11 @@
+<html>
+<head></head>
+<body>
+<ol>
+<li>Assicurati che NFC sia acceso in Impostazioni &gt; Altro &gt; NFC a assicurati che Android Beam sia pure su On nella stessa sezione.</li>
+<li>Mantieni i due dispositivi vicini (devono quasi toccarsi) e sentirai una vibrazione.</li>
+<li>Dopo che ha vibrato, vedrai il contenuto del tuo dispositivo diventare come una scheda e nello sfondo apparirà una animazione come la propulsione a curvatura di Star Trek.</li>
+<li>Tocca la scheda e il contenuto dopo si trasferira' nel dispositivo dell'altra persona.</li>
+</ol>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-ja/help_about.html b/OpenKeychain/src/main/res/raw-ja/help_about.html
new file mode 100644
index 000000000..e60add867
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-ja/help_about.html
@@ -0,0 +1,49 @@
+<html>
+<head></head>
+<body>
+<p><a href="http://www.openkeychain.org">http://www.openkeychain.org</a></p>
+<p><a href="http://www.openkeychain.org">OpenKeychain</a> は Android における OpenPGP 実装です。</p>
+<p>ライセンス: GPLv3以降</p>
+
+<h2>OpenKeychain開発者</h2>
+<ul>
+<li>Dominik Schürmann (主任開発者)</li>
+<li>Ash Hughes (暗号関係パッチ提供)</li>
+<li>Brian C. Barnes</li>
+<li>Bahtiar 'kalkin' Gadimov (UI)</li>
+<li>Daniel Hammann</li>
+<li>Daniel Haß</li>
+<li>Greg Witczak</li>
+<li>Miroojin Bakshi</li>
+<li>Nikhil Peter Raj</li>
+<li>Paul Sarbinowski</li>
+<li>Sreeram Boyapati</li>
+<li>Vincent Breitmoser</li>
+</ul>
+<h2>APG 1.xの開発者達</h2>
+<ul>
+<li>Thialfihar (主任開発者)</li>
+<li>'Senecaso' (QRコード, 鍵署名, 鍵アップロード関係)</li>
+<li>Markus Doits</li>
+</ul>
+<h2>ライブラリ</h2>
+<ul>
+<li>
+<a href="http://developer.android.com/tools/support-library/index.html">Android Support Library v4</a> (Apache License v2)</li>
+<li>
+<a href="http://developer.android.com/tools/support-library/index.html">Android Support Library v7 'appcompat'</a> (Apache License v2)</li>
+<li>
+<a href="https://github.com/emilsjolander/StickyListHeaders">StickyListHeaders</a> (Apache License v2)</li>
+<li>
+<a href="https://github.com/Bearded-Hen/Android-Bootstrap">Android-Bootstrap</a> (MIT License)</li>
+<li>
+<a href="http://code.google.com/p/zxing/">ZXing</a> (Apache License v2)</li>
+<li>
+<a href="http://rtyley.github.com/spongycastle/">SpongyCastle</a> (MIT X11 License)</li>
+<li>
+<a href="https://github.com/dschuermann/html-textview">HtmlTextView</a> (Apache License v2)</li>
+<li>
+<a href="https://github.com/johnkil/Android-AppMsg">Android AppMsg Library</a> (Apache License v2)</li>
+</ul>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-ja/help_changelog.html b/OpenKeychain/src/main/res/raw-ja/help_changelog.html
new file mode 100644
index 000000000..6a8aba073
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-ja/help_changelog.html
@@ -0,0 +1,136 @@
+<html>
+<head></head>
+<body>
+<h2>2.5</h2>
+<ul>
+<li>対称暗号化PGPメッセージ/ファイルの復号化を修正</li>
+<li>鍵編集画面のリファクタ (thanks to Ash Hughes)</li>
+<li>暗号化/復号化画面を新しいモダンなデザインに</li>
+<li>OpenPGP API バージョン 3 (複数APIアカウント, 内部修正,鍵検索)</li>
+</ul>
+<h2>2.4</h2>
+<p>このリリースにおいて適用したリッチでバグのない機能を作ってくれたGoogle Summer of Code 2014の参加者たちに感謝を!
+また、以下の人達(アルファベット順)の作ってくれたいくつもののさなパッチや相当数のパッチにも:
+Daniel Hammann, Daniel Haß, Greg Witczak, Miroojin Bakshi, Nikhil Peter Raj, Paul Sarbinowski, Sreeram Boyapati, Vincent Breitmoser.</p>
+<ul>
+<li>新しい統合キーリスト</li>
+<li>鍵指紋のカラー化</li>
+<li>鍵サーバのポート設定のサポート</li>
+<li>弱い鍵の生成が可能だったのを無効化</li>
+<li>さらなるAPIでの内部動作</li>
+<li>ユーザーIDの検証</li>
+<li>鍵サーバへの要求をマシンリーダブル出力を基盤にした</li>
+<li>タブレットでのナビゲーションドロワーのロック</li>
+<li>鍵の生成についてメールでのサジェスト</li>
+<li>公開鍵リスト内での検索</li>
+<li>そしてさらなる改善と修正...</li>
+</ul>
+<h2>2.3.1</h2>
+<ul>
+<li>古いバージョンからのアップデートでクラッシュすることに対するホットフィックス</li>
+</ul>
+<h2>2.3</h2>
+<ul>
+<li>秘密鍵のエクスポート時における必要でない公開鍵のエクスポートの削除 (thanks to Ash Hughes)</li>
+<li>鍵の期限日時設定の修正 (thanks to Ash Hughes)</li>
+<li>鍵編集時のさらなる内部修正 (thanks to Ash Hughes)</li>
+<li>インポート画面から直接鍵サーバへ要求するようにした</li>
+<li>Android 2.2から3.0でのレイアウトとダイアログスタイルの修正</li>
+<li>空ユーザIDの鍵でのクラッシュ修正</li>
+<li>署名画面から戻ってきたときにリストが空だとクラッシュするのを修正</li>
+<li>Bouncy Castle (cryptography library) を1.47 から 1.50アップデートおよびソースからのビルド</li>
+<li>署名画面からの鍵のアップロード修正</li>
+</ul>
+<h2>2.2</h2>
+<ul>
+<li>ナビゲーションドロワーの新しいデザイン</li>
+<li>新しい公開鍵リストデザイン</li>
+<li>新しい公開鍵ビュー</li>
+<li>鍵のインポート時のバグ修正</li>
+<li>鍵のクロス証明 (thanks to Ash Hughes)</li>
+<li>適切な UTF-8 パスワード処理 (thanks to Ash Hughes)</li>
+<li>新しい言語での最初のバージョン (thanks to the contributors on Transifex)</li>
+<li>QRコードによる鍵共有の修正と改善</li>
+<li>APIでのパッケージ署名検証</li>
+</ul>
+<h2>2.1.1</h2>
+<ul>
+<li>APIアップデート、K-9 Mail統合準備</li>
+</ul>
+<h2>2.1</h2>
+<ul>
+<li>たくさんのバグ修正</li>
+<li>開発者向け新API</li>
+<li>Googleによる擬似乱数生成器バグの修正</li>
+</ul>
+<h2>2.0</h2>
+<ul>
+<li>再デザイン完了</li>
+<li>QRコード、NFCビームでの公開鍵共有</li>
+<li>鍵への署名</li>
+<li>鍵サーバへアップロード</li>
+<li>インポート問題修正</li>
+<li>新しいAIDL API</li>
+</ul>
+<h2>1.0.8</h2>
+<ul>
+<li>鍵サーバの基本サポート</li>
+<li>App2SD</li>
+<li>パスフレーズのキャッシュ時間について1,2,4,8時間の選択肢追加</li>
+<li>翻訳: ノルウェー語 (thanks, Sander Danielsen), 中国語 (thanks, Zhang Fredrick) 追加</li>
+<li>バグ修正</li>
+<li>最適化</li>
+</ul>
+<h2>1.0.7</h2>
+<ul>
+<li>改行を含まないテキストの署名検証問題の修正</li>
+<li>パスフレーズのキャッシュ時間 (20,40,60分) のオプション追加</li>
+</ul>
+<h2>1.0.6</h2>
+<ul>
+<li>Froyo でのアカウント追加時クラッシュの修正</li>
+<li>セキュアファイル削除</li>
+<li>鍵ファイルインポート後の削除オプション</li>
+<li>ストリーム暗号化/復号化 (ギャラリーなど)</li>
+<li>新しいオプション (言語、強制V3署名)</li>
+<li>インタフェース変更</li>
+<li>バグ修正</li>
+</ul>
+<h2>1.0.5</h2>
+<ul>
+<li>ドイツ語およびイタリア語翻訳追加</li>
+<li>BCソースが重複してしまっていたことによる、より小さいパッケージ化</li>
+<li>新しい設定GUI</li>
+<li>ローカライズでのレイアウトを適合化</li>
+<li>署名バグ修正</li>
+</ul>
+<h2>1.0.4</h2>
+<ul>
+<li>クエリービルダーによるSDKのいくつかのバグによるクラッシュの修正</li>
+</ul>
+<h2>1.0.3</h2>
+<ul>
+<li>鍵エクスポートできる時と暗号化/署名中のクラッシュ修正</li>
+</ul>
+<h2>1.0.2</h2>
+<ul>
+<li>鍵リストのフィルタ可能化</li>
+<li>暗号化鍵の事前選択のよりスマートな実装</li>
+<li>VIEWおよびSENDについて新しいインテントのハンドリング、ファイルマネージャ外のファイルを暗号化/復号化するのを受け付けるようになる。</li>
+<li>K-9 Mailにおける修正と追加機能 (鍵事前選択)、新しいベータビルド提供</li>
+</ul>
+<h2>1.0.1</h2>
+<ul>
+<li>1.0.0で壊れたGmailアカウントリストアップを再度修正</li>
+</ul>
+<h2>1.0.0</h2>
+<ul>
+<li>K-9 Mail 統合、K-9 MailでのAPG サポートのベータビルド</li>
+<li>(ASTROを含む)ファイルマネージャのさらなるサポート</li>
+<li>スロベニア語翻訳追加</li>
+<li>より早くてメモリ使用量の少ない新しいデータベース</li>
+<li>他のアプリでのインテントおよびコンテンツプロバイダの定義</li>
+<li>バグ修正</li>
+</ul>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-ja/help_nfc_beam.html b/OpenKeychain/src/main/res/raw-ja/help_nfc_beam.html
new file mode 100644
index 000000000..c19280fd1
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-ja/help_nfc_beam.html
@@ -0,0 +1,12 @@
+<html>
+<head></head>
+<body>
+<h2>鍵の受信方法</h2>
+<ol>
+<li>パートナーの連絡先に行き、共有したい連絡先を開きます。</li>
+<li>2つのデバイスを背中合せ(ほとんどすべてのタッチ方法)にしてバイブを感じるまで保持しておいてください。</li>
+<li>バイブの後、相手のデバイスでスタートレック風のバックグラウンドアニメーションしているカード風のコンテンツを見ると思います。</li>
+<li>カードをタップしコンテンツをあなたのデバイスにロードします。</li>
+</ol>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-ja/help_start.html b/OpenKeychain/src/main/res/raw-ja/help_start.html
new file mode 100644
index 000000000..04ad31352
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-ja/help_start.html
@@ -0,0 +1,19 @@
+<html>
+<head></head>
+<body>
+<h2>入門</h2>
+<p>最初にあなたの個人用鍵ペアが必要になります。オプションメニューの"連絡先"で生成するか、"鍵のインポート"から既存の鍵ペアをインポートします。その後、あなたの友人の鍵をダウンロード、もしくはQRコードやNFCで交換します。</p>
+
+<p>ファイルの選択を拡張するには<a href="market://details?id=org.openintents.filemanager">OI File Manager</a>、<a href="market://details?id=com.google.zxing.client.android">Barcode Scanner</a>を生成したQRコードのスキャンのため、それぞれのインストールを必要とします。 リンクをクリックして、Google Play Store上かF-Droidからインストールしてください。</p>
+
+<h2>OpenKeychainでバグを見付けた!</h2>
+<p><a href="https://github.com/openpgp-keychain/openpgp-keychain/issues">OpenKeychainのIssueトラッカー</a>を使ってバグレポートを送ってください。</p>
+
+<h2>寄贈</h2>
+<p>もし、あなたが OpenKeychain の開発を助け、コードを寄贈するというなら、<a href="https://github.com/openpgp-keychain/openpgp-keychain#contribute-code">Githubのミニガイドを確認</a>して下さい。</p>
+
+<h2>翻訳</h2>
+<p>OpenKeychainの翻訳を助けてください! <a href="https://www.transifex.com/projects/p/openpgp-keychain/">TransifexのOpenKeychainプロジェクト</a>に誰でも参加できます。</p>
+
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-ja/nfc_beam_share.html b/OpenKeychain/src/main/res/raw-ja/nfc_beam_share.html
new file mode 100644
index 000000000..422423a5d
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-ja/nfc_beam_share.html
@@ -0,0 +1,11 @@
+<html>
+<head></head>
+<body>
+<ol>
+<li>設定 &gt; その他 &gt; NFC からNFCを有効にしてください、そしてAndroid Beamもまた選択してください。</li>
+<li>2つのデバイスを背中合せ(ほとんどすべてのタッチ方法)にしてバイブを感じるまで保持しておいてください。</li>
+<li>バイブの後、相手のデバイスでスタートレック風のバックグラウンドアニメーションしているカード風のコンテンツを見ると思います。</li>
+<li>カードをタップしコンテンツを他のデバイスに読み込ませてください。</li>
+</ol>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-nl-rNL/help_about.html b/OpenKeychain/src/main/res/raw-nl-rNL/help_about.html
new file mode 100644
index 000000000..ae7e16aae
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-nl-rNL/help_about.html
@@ -0,0 +1,49 @@
+<html>
+<head></head>
+<body>
+<p><a href="http://www.openkeychain.org">http://www.openkeychain.org</a></p>
+<p><a href="http://www.openkeychain.org">OpenKeychain</a> is an OpenPGP implementation for Android.</p>
+<p>License: GPLv3+</p>
+
+<h2>Developers OpenKeychain</h2>
+<ul>
+<li>Dominik Schürmann (Lead developer)</li>
+<li>Ash Hughes (crypto patches)</li>
+<li>Brian C. Barnes</li>
+<li>Bahtiar 'kalkin' Gadimov (UI)</li>
+<li>Daniel Hammann</li>
+<li>Daniel Haß</li>
+<li>Greg Witczak</li>
+<li>Miroojin Bakshi</li>
+<li>Nikhil Peter Raj</li>
+<li>Paul Sarbinowski</li>
+<li>Sreeram Boyapati</li>
+<li>Vincent Breitmoser</li>
+</ul>
+<h2>Developers APG 1.x</h2>
+<ul>
+<li>Thialfihar (Lead developer)</li>
+<li>'Senecaso' (QRCode, sign key, upload key)</li>
+<li>Markus Doits</li>
+</ul>
+<h2>Libraries</h2>
+<ul>
+<li>
+<a href="http://developer.android.com/tools/support-library/index.html">Android Support Library v4</a> (Apache License v2)</li>
+<li>
+<a href="http://developer.android.com/tools/support-library/index.html">Android Support Library v7 'appcompat'</a> (Apache License v2)</li>
+<li>
+<a href="https://github.com/emilsjolander/StickyListHeaders">StickyListHeaders</a> (Apache License v2)</li>
+<li>
+<a href="https://github.com/Bearded-Hen/Android-Bootstrap">Android-Bootstrap</a> (MIT License)</li>
+<li>
+<a href="http://code.google.com/p/zxing/">ZXing</a> (Apache License v2)</li>
+<li>
+<a href="http://rtyley.github.com/spongycastle/">SpongyCastle</a> (MIT X11 License)</li>
+<li>
+<a href="https://github.com/dschuermann/html-textview">HtmlTextView</a> (Apache License v2)</li>
+<li>
+<a href="https://github.com/johnkil/Android-AppMsg">Android AppMsg Library</a> (Apache License v2)</li>
+</ul>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-nl-rNL/help_changelog.html b/OpenKeychain/src/main/res/raw-nl-rNL/help_changelog.html
new file mode 100644
index 000000000..694c7b046
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-nl-rNL/help_changelog.html
@@ -0,0 +1,136 @@
+<html>
+<head></head>
+<body>
+<h2>2.5</h2>
+<ul>
+<li>fix decryption of symmetric pgp messages/files</li>
+<li>refactored edit key screen (thanks to Ash Hughes)</li>
+<li>new modern design for encrypt/decrypt screens</li>
+<li>OpenPGP API version 3 (multiple api accounts, internal fixes, key lookup)</li>
+</ul>
+<h2>2.4</h2>
+<p>Thanks to all applicants of Google Summer of Code 2014 who made this release feature rich and bug free!
+Besides several small patches, a notable number of patches are made by the following people (in alphabetical order):
+Daniel Hammann, Daniel Haß, Greg Witczak, Miroojin Bakshi, Nikhil Peter Raj, Paul Sarbinowski, Sreeram Boyapati, Vincent Breitmoser.</p>
+<ul>
+<li>new unified key list</li>
+<li>colorized key fingerprint</li>
+<li>support for keyserver ports</li>
+<li>deactivate possibility to generate weak keys</li>
+<li>much more internal work on the API</li>
+<li>certify user ids</li>
+<li>keyserver query based on machine-readable output</li>
+<li>lock navigation drawer on tablets</li>
+<li>suggestions for emails on creation of keys</li>
+<li>search in public key lists</li>
+<li>and much more improvements and fixes…</li>
+</ul>
+<h2>2.3.1</h2>
+<ul>
+<li>hotfix for crash when upgrading from old versions</li>
+</ul>
+<h2>2.3</h2>
+<ul>
+<li>remove unnecessary export of public keys when exporting secret key (thanks to Ash Hughes)</li>
+<li>fix setting expiry dates on keys (thanks to Ash Hughes)</li>
+<li>more internal fixes when editing keys (thanks to Ash Hughes)</li>
+<li>querying keyservers directly from the import screen</li>
+<li>fix layout and dialog style on Android 2.2-3.0</li>
+<li>fix crash on keys with empty user ids</li>
+<li>fix crash and empty lists when coming back from signing screen</li>
+<li>Bouncy Castle (cryptography library) updated from 1.47 to 1.50 and build from source</li>
+<li>fix upload of key from signing screen</li>
+</ul>
+<h2>2.2</h2>
+<ul>
+<li>new design with navigation drawer</li>
+<li>new public key list design</li>
+<li>new public key view</li>
+<li>bug fixes for importing of keys</li>
+<li>key cross-certification (thanks to Ash Hughes)</li>
+<li>handle UTF-8 passwords properly (thanks to Ash Hughes)</li>
+<li>first version with new languages (thanks to the contributors on Transifex)</li>
+<li>sharing of keys via QR Codes fixed and improved</li>
+<li>package signature verification for API</li>
+</ul>
+<h2>2.1.1</h2>
+<ul>
+<li>API Updates, preparation for K-9 Mail integration</li>
+</ul>
+<h2>2.1</h2>
+<ul>
+<li>lots of bug fixes</li>
+<li>new API for developers</li>
+<li>PRNG bug fix by Google</li>
+</ul>
+<h2>2.0</h2>
+<ul>
+<li>complete redesign</li>
+<li>share public keys via qr codes, nfc beam</li>
+<li>sign keys</li>
+<li>upload keys to server</li>
+<li>fixes import issues</li>
+<li>new AIDL API</li>
+</ul>
+<h2>1.0.8</h2>
+<ul>
+<li>basic keyserver support</li>
+<li>app2sd</li>
+<li>more choices for pass phrase cache: 1, 2, 4, 8, hours</li>
+<li>translations: Norwegian (thanks, Sander Danielsen), Chinese (thanks, Zhang Fredrick)</li>
+<li>bugfixes</li>
+<li>optimizations</li>
+</ul>
+<h2>1.0.7</h2>
+<ul>
+<li>fixed problem with signature verification of texts with trailing newline</li>
+<li>more options for pass phrase cache time to live (20, 40, 60 mins)</li>
+</ul>
+<h2>1.0.6</h2>
+<ul>
+<li>account adding crash on Froyo fixed</li>
+<li>secure file deletion</li>
+<li>option to delete key file after import</li>
+<li>stream encryption/decryption (gallery, etc.)</li>
+<li>new options (language, force v3 signatures)</li>
+<li>interface changes</li>
+<li>bugfixes</li>
+</ul>
+<h2>1.0.5</h2>
+<ul>
+<li>German and Italian translation</li>
+<li>much smaller package, due to reduced BC sources</li>
+<li>new preferences GUI</li>
+<li>layout adjustment for localization</li>
+<li>signature bugfix</li>
+</ul>
+<h2>1.0.4</h2>
+<ul>
+<li>fixed another crash caused by some SDK bug with query builder</li>
+</ul>
+<h2>1.0.3</h2>
+<ul>
+<li>fixed crashes during encryption/signing and possibly key export</li>
+</ul>
+<h2>1.0.2</h2>
+<ul>
+<li>filterable key lists</li>
+<li>smarter pre-selection of encryption keys</li>
+<li>new Intent handling for VIEW and SEND, allows files to be encrypted/decrypted out of file managers</li>
+<li>fixes and additional features (key preselection) for K-9 Mail, new beta build available</li>
+</ul>
+<h2>1.0.1</h2>
+<ul>
+<li>GMail account listing was broken in 1.0.0, fixed again</li>
+</ul>
+<h2>1.0.0</h2>
+<ul>
+<li>K-9 Mail integration, APG supporting beta build of K-9 Mail</li>
+<li>support of more file managers (including ASTRO)</li>
+<li>Slovenian translation</li>
+<li>new database, much faster, less memory usage</li>
+<li>defined Intents and content provider for other apps</li>
+<li>bugfixes</li>
+</ul>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-nl-rNL/help_nfc_beam.html b/OpenKeychain/src/main/res/raw-nl-rNL/help_nfc_beam.html
new file mode 100644
index 000000000..88492731c
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-nl-rNL/help_nfc_beam.html
@@ -0,0 +1,12 @@
+<html>
+<head></head>
+<body>
+<h2>How to receive keys</h2>
+<ol>
+<li>Go to your partners contacts and open the contact you want to share.</li>
+<li>Hold the two devices back to back (they have to be almost touching) and you’ll feel a vibration.</li>
+<li>After it vibrates you’ll see the content on your partners device turn into a card-like object with Star Trek warp speed-looking animation in the background.</li>
+<li>Tap the card and the content will then load on the your device.</li>
+</ol>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-nl-rNL/help_start.html b/OpenKeychain/src/main/res/raw-nl-rNL/help_start.html
new file mode 100644
index 000000000..0e60c17a7
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-nl-rNL/help_start.html
@@ -0,0 +1,19 @@
+<html>
+<head></head>
+<body>
+<h2>Getting started</h2>
+<p>First you need a personal key pair. Create one via the option menus in "Contacts" or import existing key pairs via "Import Keys". Afterwards, you can download your friends' keys or exchange them via QR Codes or NFC.</p>
+
+<p>It is recommended that you install <a href="market://details?id=org.openintents.filemanager">OI File Manager</a> for enhanced file selection and <a href="market://details?id=com.google.zxing.client.android">Barcode Scanner</a> to scan generated QR Codes. Clicking on the links will open Google Play Store or F-Droid for installation.</p>
+
+<h2>I found a bug in OpenKeychain!</h2>
+<p>Please report the bug using the <a href="https://github.com/openpgp-keychain/openpgp-keychain/issues">issue tracker of OpenKeychain</a>.</p>
+
+<h2>Contribute</h2>
+<p>If you want to help us developing OpenKeychain by contributing code <a href="https://github.com/openpgp-keychain/openpgp-keychain#contribute-code">follow our small guide on Github</a>.</p>
+
+<h2>Translations</h2>
+<p>Help translating OpenKeychain! Everybody can participate at <a href="https://www.transifex.com/projects/p/openpgp-keychain/">OpenKeychain on Transifex</a>.</p>
+
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-nl-rNL/nfc_beam_share.html b/OpenKeychain/src/main/res/raw-nl-rNL/nfc_beam_share.html
new file mode 100644
index 000000000..083e055c7
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-nl-rNL/nfc_beam_share.html
@@ -0,0 +1,11 @@
+<html>
+<head></head>
+<body>
+<ol>
+<li>Make sure that NFC is turned on in Settings &gt; More &gt; NFC and make sure that Android Beam is also on in the same section.</li>
+<li>Hold the two devices back to back (they have to be almost touching) and you'll feel a vibration.</li>
+<li>After it vibrates you'll see the content on your device turn into a card-like object with Star Trek warp speed-looking animation in the background.</li>
+<li>Tap the card and the content will then load on the other person’s device.</li>
+</ol>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-pl/help_about.html b/OpenKeychain/src/main/res/raw-pl/help_about.html
new file mode 100644
index 000000000..a033c084a
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-pl/help_about.html
@@ -0,0 +1,49 @@
+<html>
+<head></head>
+<body>
+<p><a href="http://www.openkeychain.org">http://www.openkeychain.org</a></p>
+<p><a href="http://www.openkeychain.org">OpenKeychain</a> to implementacja OpenPGP na platformę Android.</p>
+<p>Licencja: GPLv3+</p>
+
+<h2>Deweloperzy OpenKeychain</h2>
+<ul>
+<li>Dominik Schürmann (Wiodący developer)</li>
+<li>Ash Hughes (łatki crypto)</li>
+<li>Brian C. Barnes</li>
+<li>Bahtiar 'kalkin' Gadimov (Interfejs Użytkownika)</li>
+<li>Daniel Hammann</li>
+<li>Daniel Haß</li>
+<li>Greg Witczak</li>
+<li>Miroojin Bakshi</li>
+<li>Nikhil Peter Raj</li>
+<li>Paul Sarbinowski</li>
+<li>Sreeram Boyapati</li>
+<li>Vincent Breitmoser</li>
+</ul>
+<h2>Deweloperzy APG 1.x</h2>
+<ul>
+<li>Thialfihar (Wiodący deweloper)</li>
+<li>'Senecaso' (kody QR, podpisy kluczy, wysyłanie kluczy)</li>
+<li>Markus Doits</li>
+</ul>
+<h2>Biblioteki</h2>
+<ul>
+<li>
+<a href="http://developer.android.com/tools/support-library/index.html">Android Support Library v4</a> (Licencja Apache v2)</li>
+<li>
+<a href="http://developer.android.com/tools/support-library/index.html">Android Support Library v7 'appcompat'</a> (Licencja Apache v2)</li>
+<li>
+<a href="https://github.com/emilsjolander/StickyListHeaders">StickyListHeaders</a> (Licencja Apache v2)</li>
+<li>
+<a href="https://github.com/Bearded-Hen/Android-Bootstrap">Android-Bootstrap</a> (Licencja MIT)</li>
+<li>
+<a href="http://code.google.com/p/zxing/">ZXing</a> (Licencja Apache v2)</li>
+<li>
+<a href="http://rtyley.github.com/spongycastle/">SpongyCastle</a> (Licencja MIT X11)</li>
+<li>
+<a href="https://github.com/dschuermann/html-textview">HtmlTextView</a> (Licencja Apache v2)</li>
+<li>
+<a href="https://github.com/johnkil/Android-AppMsg">Android AppMsg Library</a> (Licencja Apache v2)</li>
+</ul>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-pl/help_changelog.html b/OpenKeychain/src/main/res/raw-pl/help_changelog.html
new file mode 100644
index 000000000..33207f8db
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-pl/help_changelog.html
@@ -0,0 +1,136 @@
+<html>
+<head></head>
+<body>
+<h2>2.5</h2>
+<ul>
+<li>naprawiono deszyfrowanie symetrycznych wiadomości/plików PGP</li>
+<li>przerobiono ekran edytowania klucza (podziękowania dla Ash Hughes)</li>
+<li>nowy nowoczesny design dla ekranów szyfrowania/deszyfrowania</li>
+<li>OpenPGP API wersja 3 (wiele kont API, wewnętrzne poprawki, wyszukiwanie kluczy)</li>
+</ul>
+<h2>2.4</h2>
+<p>Podziękowania dla wszystkich kandydatów do Google Summer of Code 2014 którzy uczynili to wydanie bogatym w nowe funkcje i pozbawione błedów!
+Poza kilkoma małymi poprawkami, znaczna ilość aktualizacji została wykonana przez poniższe osoby (w kolejności alfabetycznej):
+Daniel Hammann, Daniel Haß, Greg Witczak, Miroojin Bakshi, Nikhil Peter Raj, Paul Sarbinowski, Sreeram Boyapati, Vincent Breitmoser.</p>
+<ul>
+<li>nowa ujednolicona lista kluczy</li>
+<li>pokolowane odciski klucza</li>
+<li>obsługa portów w serwerach kluczy</li>
+<li>zablokowana możliwość generowania słabych kluczy</li>
+<li>wiele wewnętrznych prac nad API</li>
+<li>podpisywanie identyfikatorów użytkowników</li>
+<li>zapytania do serwera kluczy wykorzystują wydajniejszą komunikację maszynową</li>
+<li>zablokowany panel nawigacyjny na tabletach</li>
+<li>podpowiedzi do adresu email przy tworzeniu kluczy</li>
+<li>wyszukiwanie w liście publicznych kluczy</li>
+<li>i wiele innych usprawnień i poprawek...</li>
+</ul>
+<h2>2.3.1</h2>
+<ul>
+<li>szybka poprawka awarii aplikacji przy aktualizacji ze starszej wersji</li>
+</ul>
+<h2>2.3</h2>
+<ul>
+<li>usunięto zbędne eksportowanie kluczy publicznych przy eksportowaniu kluczy prywatnych (podziękowania dla Ash Hughes)</li>
+<li>naprawiono błąd z ustawianiem daty wygaśnięcia kluczy (podziękowania dla Ash Hugens)</li>
+<li>więcej wewnętrznych poprawek przy edytowaniu kluczy (podziękowania dla Ash Hughes)</li>
+<li>wysyłanie zapytań do serwera kluczy bezpośrednio z ekranu importu</li>
+<li>poprawiony wygląd interfejsu i okienek na Androidzie 2.2-3.0</li>
+<li>naprawiono awarię programu dla kluczy z pustym identyfikatorem użytkownika</li>
+<li>naprawiono awarię aplikacji przy powrocie z ekranu podpisywania</li>
+<li>Bouncy Castle (biblioteka kryptograficzna) zaktualizowana z wersji 1.47 do 1.50 i kompilowana ze źródeł</li>
+<li>naprawiony błąd przy wysyłaniu klucza z ekranu podpisywania</li>
+</ul>
+<h2>2.2</h2>
+<ul>
+<li>nowy wygląd z panelem nawigacji</li>
+<li>nowy wygląd listy kluczy publicznych</li>
+<li>nowy widok klucza publicznego</li>
+<li>naprawiono błędy związane z importowaniem kluczy</li>
+<li>krzyżowa certyfikacja kluczy (podziękowania dla Ash Hughes)</li>
+<li>hasła zapisane w UTF-8 są teraz prawidłowo obsługiwane (podziękowania dla Ash Hughes)</li>
+<li>pierwsza wersja z nowymi językami (podziękowania dla tłumaczy-wolontariuszy z Transifex)</li>
+<li>udostępnianie kluczy przez kody QR zostało poprawione i ulepszone</li>
+<li>weryfikacja podpisu paczki dla API</li>
+</ul>
+<h2>2.1.1</h2>
+<ul>
+<li>aktualizacje API, przygotowanie do integracji z K-9 Mail</li>
+</ul>
+<h2>2.1</h2>
+<ul>
+<li>wiele poprawek błędów</li>
+<li>nowe API dla programistów</li>
+<li>Naprawiono błąd generatora liczb losowych (PRNG), Google.</li>
+</ul>
+<h2>2.0</h2>
+<ul>
+<li>kompletna przebudowa</li>
+<li>udostępnianie kluczy publicznych przez kody QR oraz NFC</li>
+<li>możliwość podpisywania kluczem</li>
+<li>wysyłanie kluczy na serwer</li>
+<li>naprawiono problemy związane z importowaniem</li>
+<li>nowy AIDL API</li>
+</ul>
+<h2>1.0.8</h2>
+<ul>
+<li>podstawowa obsługa serwerów kluczy</li>
+<li>app2sd</li>
+<li>dodano więcej przedziałów czasowych zapamiętywania hasła: 1, 2, 4, 8 godzin</li>
+<li>tłumaczenia: norweski (podziękowania dla Sander Danielsen), chiński (podziękowania dla Zhang Fredrick)</li>
+<li>naprawione błędy</li>
+<li>usprawnienia</li>
+</ul>
+<h2>1.0.7</h2>
+<ul>
+<li>naprawiono problem z weryfikowaniem podpisu tekstów kończących się znakiem nowej linii</li>
+<li>dodano więcej przedziałów czasowych zapamiętywania hasła (20, 40, 60 minut)</li>
+</ul>
+<h2>1.0.6</h2>
+<ul>
+<li>naprawiono błąd powodujący awarię aplikacji przy dodawaniu nowego konta na Androidzie 2.2 Froyo</li>
+<li>dodano bezpieczne usuwanie plików</li>
+<li>Dodano możliwość usuwania plików kluczy po zaimportowaniu</li>
+<li>możliwość strumieniowego szyfrowania/deszyfrowania (galeria i inne)</li>
+<li>nowe opcje (języki, wymuszanie podpisów v3)</li>
+<li>zmiany w interfejsie</li>
+<li>naprawione błędy</li>
+</ul>
+<h2>1.0.5</h2>
+<ul>
+<li>tłumaczenie na niemiecki i włoski</li>
+<li>znaczne zmniejszenie rozmiaru paczki, z powodu zredukowania źródeł BC</li>
+<li>nowy interfejs graficzny Właściwości</li>
+<li>usprawnienia wyglądu dla lokalizacji</li>
+<li>naprawa błędu z podpisami</li>
+</ul>
+<h2>1.0.4</h2>
+<ul>
+<li>naprawiono kolejny błąd powodujący awarię aplikacji, spowodowany przez jakąś usterkę w SDK przy budowaniu zapytań</li>
+</ul>
+<h2>1.0.3</h2>
+<ul>
+<li>naprawiono błąd w trakcie szyfrowania/podpisywania i prawdopodobnie eksportowania klucza</li>
+</ul>
+<h2>1.0.2</h2>
+<ul>
+<li>dodano możliwość filtrowania listy kluczy</li>
+<li>sprytniejsze automatyczne wybieranie kluczy szyfrujących</li>
+<li>dodano nowy sposób obsługi intencji "wyświetl" i "wyślij", umożliwia szyfrowanie/deszyfrowanie plików wprost z menadżera plików.</li>
+<li>poprawki i dodatkowe funkcje (podpowiedź wyboru klucza) dla K-9 Mail, nowe wydanie beta dostępne</li>
+</ul>
+<h2>1.0.1</h2>
+<ul>
+<li>wyświetlanie kont w GMailu było zepsute w 1.0.0, naprawiono je ponownie</li>
+</ul>
+<h2>1.0.0</h2>
+<ul>
+<li>integracja z K-9 Mail, APG obsługuje wersję beta K-9 Mail</li>
+<li>dodano wsparcie dla większej liczby menadżerów plików (włącznie z ASTRO)</li>
+<li>tłumaczenie na słoweński</li>
+<li>Wykorzystanie nowej bazy danych, która jest znacznie szybsza i mniej pamięciożerna</li>
+<li>zdefiniowano intecję i dostawców treści dla pozostałych aplikacji</li>
+<li>naprawione błędy</li>
+</ul>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-pl/help_nfc_beam.html b/OpenKeychain/src/main/res/raw-pl/help_nfc_beam.html
new file mode 100644
index 000000000..53db5e80c
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-pl/help_nfc_beam.html
@@ -0,0 +1,12 @@
+<html>
+<head></head>
+<body>
+<h2>Jak odbierać klucze</h2>
+<ol>
+<li>Wejdź do listy kontaktów Twojego partnera i otwórz kontakt, który chcesz przesłać.</li>
+<li>Przytrzymaj oba urządzenia plecami do siebie (powinny się niemal dotykać) i poczujesz wibrację.</li>
+<li>Po zakończeniu wibracji zobaczysz, że zawartość urządzenia partnera zamienia się w obiekt zbliżony do wizytówki, z animacją rodem ze Star Treka w tle.</li>
+<li>Dotknij wizytówkę, a jej zawartość zostanie wysłana na Twoje urządzenie.</li>
+</ol>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-pl/help_start.html b/OpenKeychain/src/main/res/raw-pl/help_start.html
new file mode 100644
index 000000000..e88a1ad6d
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-pl/help_start.html
@@ -0,0 +1,19 @@
+<html>
+<head></head>
+<body>
+<h2>Pierwsze kroki</h2>
+<p>Po pierwsze potrzebujesz swoją osobistą parę kluczy. Stwórz ją, korzystając z odpowiedniej opcji w sekcji "Kontakty" albo zainportuj istniejącą parę korzystając z sekcji "Inportuj klucze". Następnie możesz porać klucze Twoich znajomych lub wymieniać się z nimi za pośrednictwem kodów QR lub technologii NFC.</p>
+
+<p>Zalecana jest instalacja menadżera plików <a href="market://details?id=org.openintents.filemanager">OI File Manager</a> w celu zapewnienia wygodniejszego wyboru plików oraz programu <a href="market://details?id=com.google.zxing.client.android">Barcode Scanner</a>, który jest w stanie skanować wygenerowane kody QR. Kliknięcie na powyższe linki przekieruje Cię do sklepu Google Play / F-Droid.</p>
+
+<h2>Znalazłem błąd w OpenKeychain!</h2>
+<p>Zgłoś błąd korzystając z <a href="https://github.com/openpgp-keychain/openpgp-keychain/issues">systemu śledzenia błędów OpenKeychain</a>.</p>
+
+<h2>Wkład</h2>
+<p>Jeżeli chcesz pomóc nam rozwijać OpenKeychain jako programista, <a href="https://github.com/openpgp-keychain/openpgp-keychain#contribute-code">zapoznaj się z naszym małym poradnikiem na Githubie</a>.</p>
+
+<h2>Tłumaczenia</h2>
+<p>Pomóż przetłumaczyć OpenKeychain! Każdy może wziąć udział przez stronę <a href="https://www.transifex.com/projects/p/openpgp-keychain/">OpenKeychain w serwisie Transifex</a>.</p>
+
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-pl/nfc_beam_share.html b/OpenKeychain/src/main/res/raw-pl/nfc_beam_share.html
new file mode 100644
index 000000000..f17e44079
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-pl/nfc_beam_share.html
@@ -0,0 +1,11 @@
+<html>
+<head></head>
+<body>
+<ol>
+<li>Upewnij się, że NFC (Near Field Communication, pol.: komunikacja bliskiego zasięgu) jest włączone. W tym celu wejdź w Ustawienia &gt; Inne &gt; NFC. Upewnij się również, że włączona jest funkcja Android Beam (znajduje się w tym samym miejscu).</li>
+<li>Przytrzymaj oba urządzenia plecami do siebie (powinny się niemal dotykać) i poczujesz wibrację.</li>
+<li>Po zakończeniu wibracji zobaczysz, że zawartość urządzenia partnera zamienia się w obiekt zbliżony do wizytówki, z animacją rodem ze Star Treka w tle.</li>
+<li>Dotknij wizytówkę, a jej zawartość zostanie wysłana na urządzenie drugiej osoby.</li>
+</ol>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-pt-rBR/help_about.html b/OpenKeychain/src/main/res/raw-pt-rBR/help_about.html
new file mode 100644
index 000000000..ae7e16aae
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-pt-rBR/help_about.html
@@ -0,0 +1,49 @@
+<html>
+<head></head>
+<body>
+<p><a href="http://www.openkeychain.org">http://www.openkeychain.org</a></p>
+<p><a href="http://www.openkeychain.org">OpenKeychain</a> is an OpenPGP implementation for Android.</p>
+<p>License: GPLv3+</p>
+
+<h2>Developers OpenKeychain</h2>
+<ul>
+<li>Dominik Schürmann (Lead developer)</li>
+<li>Ash Hughes (crypto patches)</li>
+<li>Brian C. Barnes</li>
+<li>Bahtiar 'kalkin' Gadimov (UI)</li>
+<li>Daniel Hammann</li>
+<li>Daniel Haß</li>
+<li>Greg Witczak</li>
+<li>Miroojin Bakshi</li>
+<li>Nikhil Peter Raj</li>
+<li>Paul Sarbinowski</li>
+<li>Sreeram Boyapati</li>
+<li>Vincent Breitmoser</li>
+</ul>
+<h2>Developers APG 1.x</h2>
+<ul>
+<li>Thialfihar (Lead developer)</li>
+<li>'Senecaso' (QRCode, sign key, upload key)</li>
+<li>Markus Doits</li>
+</ul>
+<h2>Libraries</h2>
+<ul>
+<li>
+<a href="http://developer.android.com/tools/support-library/index.html">Android Support Library v4</a> (Apache License v2)</li>
+<li>
+<a href="http://developer.android.com/tools/support-library/index.html">Android Support Library v7 'appcompat'</a> (Apache License v2)</li>
+<li>
+<a href="https://github.com/emilsjolander/StickyListHeaders">StickyListHeaders</a> (Apache License v2)</li>
+<li>
+<a href="https://github.com/Bearded-Hen/Android-Bootstrap">Android-Bootstrap</a> (MIT License)</li>
+<li>
+<a href="http://code.google.com/p/zxing/">ZXing</a> (Apache License v2)</li>
+<li>
+<a href="http://rtyley.github.com/spongycastle/">SpongyCastle</a> (MIT X11 License)</li>
+<li>
+<a href="https://github.com/dschuermann/html-textview">HtmlTextView</a> (Apache License v2)</li>
+<li>
+<a href="https://github.com/johnkil/Android-AppMsg">Android AppMsg Library</a> (Apache License v2)</li>
+</ul>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-pt-rBR/help_changelog.html b/OpenKeychain/src/main/res/raw-pt-rBR/help_changelog.html
new file mode 100644
index 000000000..694c7b046
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-pt-rBR/help_changelog.html
@@ -0,0 +1,136 @@
+<html>
+<head></head>
+<body>
+<h2>2.5</h2>
+<ul>
+<li>fix decryption of symmetric pgp messages/files</li>
+<li>refactored edit key screen (thanks to Ash Hughes)</li>
+<li>new modern design for encrypt/decrypt screens</li>
+<li>OpenPGP API version 3 (multiple api accounts, internal fixes, key lookup)</li>
+</ul>
+<h2>2.4</h2>
+<p>Thanks to all applicants of Google Summer of Code 2014 who made this release feature rich and bug free!
+Besides several small patches, a notable number of patches are made by the following people (in alphabetical order):
+Daniel Hammann, Daniel Haß, Greg Witczak, Miroojin Bakshi, Nikhil Peter Raj, Paul Sarbinowski, Sreeram Boyapati, Vincent Breitmoser.</p>
+<ul>
+<li>new unified key list</li>
+<li>colorized key fingerprint</li>
+<li>support for keyserver ports</li>
+<li>deactivate possibility to generate weak keys</li>
+<li>much more internal work on the API</li>
+<li>certify user ids</li>
+<li>keyserver query based on machine-readable output</li>
+<li>lock navigation drawer on tablets</li>
+<li>suggestions for emails on creation of keys</li>
+<li>search in public key lists</li>
+<li>and much more improvements and fixes…</li>
+</ul>
+<h2>2.3.1</h2>
+<ul>
+<li>hotfix for crash when upgrading from old versions</li>
+</ul>
+<h2>2.3</h2>
+<ul>
+<li>remove unnecessary export of public keys when exporting secret key (thanks to Ash Hughes)</li>
+<li>fix setting expiry dates on keys (thanks to Ash Hughes)</li>
+<li>more internal fixes when editing keys (thanks to Ash Hughes)</li>
+<li>querying keyservers directly from the import screen</li>
+<li>fix layout and dialog style on Android 2.2-3.0</li>
+<li>fix crash on keys with empty user ids</li>
+<li>fix crash and empty lists when coming back from signing screen</li>
+<li>Bouncy Castle (cryptography library) updated from 1.47 to 1.50 and build from source</li>
+<li>fix upload of key from signing screen</li>
+</ul>
+<h2>2.2</h2>
+<ul>
+<li>new design with navigation drawer</li>
+<li>new public key list design</li>
+<li>new public key view</li>
+<li>bug fixes for importing of keys</li>
+<li>key cross-certification (thanks to Ash Hughes)</li>
+<li>handle UTF-8 passwords properly (thanks to Ash Hughes)</li>
+<li>first version with new languages (thanks to the contributors on Transifex)</li>
+<li>sharing of keys via QR Codes fixed and improved</li>
+<li>package signature verification for API</li>
+</ul>
+<h2>2.1.1</h2>
+<ul>
+<li>API Updates, preparation for K-9 Mail integration</li>
+</ul>
+<h2>2.1</h2>
+<ul>
+<li>lots of bug fixes</li>
+<li>new API for developers</li>
+<li>PRNG bug fix by Google</li>
+</ul>
+<h2>2.0</h2>
+<ul>
+<li>complete redesign</li>
+<li>share public keys via qr codes, nfc beam</li>
+<li>sign keys</li>
+<li>upload keys to server</li>
+<li>fixes import issues</li>
+<li>new AIDL API</li>
+</ul>
+<h2>1.0.8</h2>
+<ul>
+<li>basic keyserver support</li>
+<li>app2sd</li>
+<li>more choices for pass phrase cache: 1, 2, 4, 8, hours</li>
+<li>translations: Norwegian (thanks, Sander Danielsen), Chinese (thanks, Zhang Fredrick)</li>
+<li>bugfixes</li>
+<li>optimizations</li>
+</ul>
+<h2>1.0.7</h2>
+<ul>
+<li>fixed problem with signature verification of texts with trailing newline</li>
+<li>more options for pass phrase cache time to live (20, 40, 60 mins)</li>
+</ul>
+<h2>1.0.6</h2>
+<ul>
+<li>account adding crash on Froyo fixed</li>
+<li>secure file deletion</li>
+<li>option to delete key file after import</li>
+<li>stream encryption/decryption (gallery, etc.)</li>
+<li>new options (language, force v3 signatures)</li>
+<li>interface changes</li>
+<li>bugfixes</li>
+</ul>
+<h2>1.0.5</h2>
+<ul>
+<li>German and Italian translation</li>
+<li>much smaller package, due to reduced BC sources</li>
+<li>new preferences GUI</li>
+<li>layout adjustment for localization</li>
+<li>signature bugfix</li>
+</ul>
+<h2>1.0.4</h2>
+<ul>
+<li>fixed another crash caused by some SDK bug with query builder</li>
+</ul>
+<h2>1.0.3</h2>
+<ul>
+<li>fixed crashes during encryption/signing and possibly key export</li>
+</ul>
+<h2>1.0.2</h2>
+<ul>
+<li>filterable key lists</li>
+<li>smarter pre-selection of encryption keys</li>
+<li>new Intent handling for VIEW and SEND, allows files to be encrypted/decrypted out of file managers</li>
+<li>fixes and additional features (key preselection) for K-9 Mail, new beta build available</li>
+</ul>
+<h2>1.0.1</h2>
+<ul>
+<li>GMail account listing was broken in 1.0.0, fixed again</li>
+</ul>
+<h2>1.0.0</h2>
+<ul>
+<li>K-9 Mail integration, APG supporting beta build of K-9 Mail</li>
+<li>support of more file managers (including ASTRO)</li>
+<li>Slovenian translation</li>
+<li>new database, much faster, less memory usage</li>
+<li>defined Intents and content provider for other apps</li>
+<li>bugfixes</li>
+</ul>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-pt-rBR/help_nfc_beam.html b/OpenKeychain/src/main/res/raw-pt-rBR/help_nfc_beam.html
new file mode 100644
index 000000000..88492731c
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-pt-rBR/help_nfc_beam.html
@@ -0,0 +1,12 @@
+<html>
+<head></head>
+<body>
+<h2>How to receive keys</h2>
+<ol>
+<li>Go to your partners contacts and open the contact you want to share.</li>
+<li>Hold the two devices back to back (they have to be almost touching) and you’ll feel a vibration.</li>
+<li>After it vibrates you’ll see the content on your partners device turn into a card-like object with Star Trek warp speed-looking animation in the background.</li>
+<li>Tap the card and the content will then load on the your device.</li>
+</ol>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-pt-rBR/help_start.html b/OpenKeychain/src/main/res/raw-pt-rBR/help_start.html
new file mode 100644
index 000000000..0e60c17a7
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-pt-rBR/help_start.html
@@ -0,0 +1,19 @@
+<html>
+<head></head>
+<body>
+<h2>Getting started</h2>
+<p>First you need a personal key pair. Create one via the option menus in "Contacts" or import existing key pairs via "Import Keys". Afterwards, you can download your friends' keys or exchange them via QR Codes or NFC.</p>
+
+<p>It is recommended that you install <a href="market://details?id=org.openintents.filemanager">OI File Manager</a> for enhanced file selection and <a href="market://details?id=com.google.zxing.client.android">Barcode Scanner</a> to scan generated QR Codes. Clicking on the links will open Google Play Store or F-Droid for installation.</p>
+
+<h2>I found a bug in OpenKeychain!</h2>
+<p>Please report the bug using the <a href="https://github.com/openpgp-keychain/openpgp-keychain/issues">issue tracker of OpenKeychain</a>.</p>
+
+<h2>Contribute</h2>
+<p>If you want to help us developing OpenKeychain by contributing code <a href="https://github.com/openpgp-keychain/openpgp-keychain#contribute-code">follow our small guide on Github</a>.</p>
+
+<h2>Translations</h2>
+<p>Help translating OpenKeychain! Everybody can participate at <a href="https://www.transifex.com/projects/p/openpgp-keychain/">OpenKeychain on Transifex</a>.</p>
+
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-pt-rBR/nfc_beam_share.html b/OpenKeychain/src/main/res/raw-pt-rBR/nfc_beam_share.html
new file mode 100644
index 000000000..083e055c7
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-pt-rBR/nfc_beam_share.html
@@ -0,0 +1,11 @@
+<html>
+<head></head>
+<body>
+<ol>
+<li>Make sure that NFC is turned on in Settings &gt; More &gt; NFC and make sure that Android Beam is also on in the same section.</li>
+<li>Hold the two devices back to back (they have to be almost touching) and you'll feel a vibration.</li>
+<li>After it vibrates you'll see the content on your device turn into a card-like object with Star Trek warp speed-looking animation in the background.</li>
+<li>Tap the card and the content will then load on the other person’s device.</li>
+</ol>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-ru/help_about.html b/OpenKeychain/src/main/res/raw-ru/help_about.html
new file mode 100644
index 000000000..29cc1af83
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-ru/help_about.html
@@ -0,0 +1,49 @@
+<html>
+<head></head>
+<body>
+<p><a href="http://www.openkeychain.org">http://www.openkeychain.org</a></p>
+<p><a href="http://www.openkeychain.org">OpenKeychain</a> - реализация OpenPGP для Android.</p>
+<p>Лицензия: GPLv3+</p>
+
+<h2>Разработчики OpenKeychain</h2>
+<ul>
+<li>Dominik Schürmann (главный разработчик)</li>
+<li>Ash Hughes (патчи криптографии)</li>
+<li>Brian C. Barnes</li>
+<li>Bahtiar 'kalkin' Gadimov (UI)</li>
+<li>Daniel Hammann</li>
+<li>Daniel Haß</li>
+<li>Greg Witczak</li>
+<li>Miroojin Bakshi</li>
+<li>Nikhil Peter Raj</li>
+<li>Paul Sarbinowski</li>
+<li>Sreeram Boyapati</li>
+<li>Vincent Breitmoser</li>
+</ul>
+<h2>Разработчики APG 1.x</h2>
+<ul>
+<li>Thialfihar (главный разработчик)</li>
+<li>'Senecaso' (QR коды, подписание и загрузка ключей)</li>
+<li>Markus Doits</li>
+</ul>
+<h2>Компоненты</h2>
+<ul>
+<li>
+<a href="http://developer.android.com/tools/support-library/index.html">Android Support Library v4</a> (Apache License v2)</li>
+<li>
+<a href="http://developer.android.com/tools/support-library/index.html">Android Support Library v7 'appcompat'</a> (Apache License v2)</li>
+<li>
+<a href="https://github.com/emilsjolander/StickyListHeaders">StickyListHeaders</a> (Apache License v2)</li>
+<li>
+<a href="https://github.com/Bearded-Hen/Android-Bootstrap">Android-Bootstrap</a> (MIT License)</li>
+<li>
+<a href="http://code.google.com/p/zxing/">ZXing</a> (Apache License v2)</li>
+<li>
+<a href="http://rtyley.github.com/spongycastle/">SpongyCastle</a> (MIT X11 License)</li>
+<li>
+<a href="https://github.com/dschuermann/html-textview">HtmlTextView</a> (Apache License v2)</li>
+<li>
+<a href="https://github.com/johnkil/Android-AppMsg">Библиотека Android AppMsg</a> (Apache License v2)</li>
+</ul>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-ru/help_changelog.html b/OpenKeychain/src/main/res/raw-ru/help_changelog.html
new file mode 100644
index 000000000..8b1580c3a
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-ru/help_changelog.html
@@ -0,0 +1,136 @@
+<html>
+<head></head>
+<body>
+<h2>2.5</h2>
+<ul>
+<li>исправлено симметричное шифрование сообщений/файлов</li>
+<li>переработано окно изменения ключа (благодаря Ash Hughes)</li>
+<li>новый дизайн для окон шифрования/расшифровки</li>
+<li>OpenPGP API версии 3 (множественные аккаунты, внутренние исправления, поиск ключей)</li>
+</ul>
+<h2>2.4</h2>
+<p>Спасибо всем участникам Google Summer of Code 2014, которые помогли сделать этот выпуск, добавив функции и исправив ошибки!
+Из общего числа патчей, особенный вклад внесли следующие люди (в алфавитном порядке):
+Daniel Hammann, Daniel Haß, Greg Witczak, Miroojin Bakshi, Nikhil Peter Raj, Paul Sarbinowski, Sreeram Boyapati, Vincent Breitmoser.</p>
+<ul>
+<li>новый объединенный список ключей</li>
+<li>цветовая индикация отпечатков ключей</li>
+<li>поддержка портов серверов ключей</li>
+<li>отключена возможность создавать слабые ключи</li>
+<li>ещё больше улучшений работы API</li>
+<li>сертификация пользовательских данных</li>
+<li>keyserver query based on machine-readable output</li>
+<li>фиксация панели на планшетах</li>
+<li>подсказки email при создании ключей</li>
+<li>поиск в списках публичных ключей</li>
+<li>и множество других исправлений и улучшений...</li>
+</ul>
+<h2>2.3.1</h2>
+<ul>
+<li>исправление ошибки при обновлении со старых версий</li>
+</ul>
+<h2>2.3</h2>
+<ul>
+<li>удален не требующийся экспорт публичного ключа при экспорте секретного ключа (спасибо, Ash Hughes)</li>
+<li>исправлена ошибка срока годности ключей (спасибо, Ash Hughes)</li>
+<li>исправления ошибок при изменении ключей (спасибо, Ash Hughes)</li>
+<li>запрос ключа с сервера прямо из окна импорта ключей</li>
+<li>исправление внешнего вида для Android 2.2-3.0</li>
+<li>исправлено падение когда ключ не содержал имя пользователя</li>
+<li>исправлено падение и пустой список при возвращении из окна подписания</li>
+<li>криптографическая библиотека Bouncy Castle обновлена до версии 1.50</li>
+<li>исправлена загрузка ключа из окна подписания</li>
+</ul>
+<h2>2.2</h2>
+<ul>
+<li>новый дизайн с боковой панелью</li>
+<li>новый дизайн списка ключей</li>
+<li>новый вид просмотра ключа</li>
+<li>исправление ошибок импорта ключей</li>
+<li>кросс-сертификация ключей (спасибо, Ash Hughes)</li>
+<li>правильная обработка паролей в UTF-8 (спасибо, Ash Hughes)</li>
+<li>первая версия с новыми языками (спасибо переводчикам с Transifex)</li>
+<li>исправление и улучшение передачи ключей через QR коды</li>
+<li>проверка подписей пакетов для API</li>
+</ul>
+<h2>2.1.1</h2>
+<ul>
+<li>обновление API, подготовка к интеграции с K-9 Mail</li>
+</ul>
+<h2>2.1</h2>
+<ul>
+<li>множество исправлений ошибок</li>
+<li>новый API для разработчиков</li>
+<li>исправление ошибки генератора случайных чисел</li>
+</ul>
+<h2>2.0</h2>
+<ul>
+<li>переработка дизайна</li>
+<li>передача ключей через QR коды и NFC</li>
+<li>подписание ключей</li>
+<li>загрузка ключей на сервер</li>
+<li>исправление проблем импорта</li>
+<li>новый AIDL API</li>
+</ul>
+<h2>1.0.8</h2>
+<ul>
+<li>поддержка серверов ключей</li>
+<li>App2SD</li>
+<li>больше вариантов сохранения кэша пароля: 1, 2, 4, 8 часов</li>
+<li>переводы: норвежский (спасибо, Sander Danielsen), китайский (спасибо, Zhang Fredrick)</li>
+<li>исправления ошибок</li>
+<li>оптимизация</li>
+</ul>
+<h2>1.0.7</h2>
+<ul>
+<li>исправление ошибки при проверке подписи текста с переводом строки</li>
+<li>больше вариантов сохранения кэша пароля: 20, 40, 60 минут</li>
+</ul>
+<h2>1.0.6</h2>
+<ul>
+<li>исправление ошибки создания записи на Froyo</li>
+<li>безопасное удаление файлов</li>
+<li>удаление файла ключа после импорта</li>
+<li>передача шифрования (галерея и т.д.)</li>
+<li>новые возможности (язык, v3 подписи)</li>
+<li>изменения интерфейса</li>
+<li>исправления ошибок</li>
+</ul>
+<h2>1.0.5</h2>
+<ul>
+<li>новые языки: немецкий, итальянский</li>
+<li>уменьшение размера программы</li>
+<li>новый интерфейс настроек</li>
+<li>изменение вида для локализации</li>
+<li>исправление ошибки подписи</li>
+</ul>
+<h2>1.0.4</h2>
+<ul>
+<li>исправление еще одной ошибки, возникающей в SDK</li>
+</ul>
+<h2>1.0.3</h2>
+<ul>
+<li>исправление ошибок при шифровании/подписании и экспорте ключей</li>
+</ul>
+<h2>1.0.2</h2>
+<ul>
+<li>фильтр списка ключей</li>
+<li>улучшение выбора ключей шифрования</li>
+<li>добавлена возможность шифровать файлы прямо из файлового менеджера</li>
+<li>исправления ошибок и новые возможности для интеграции с K-9 Mail</li>
+</ul>
+<h2>1.0.1</h2>
+<ul>
+<li>исправление выбора учетной записи GMail, сломанного в 1.0.0</li>
+</ul>
+<h2>1.0.0</h2>
+<ul>
+<li>интеграция с K-9 Mail, APG поддерживает beta-версию K-9 Mail</li>
+<li>поддержка сторонних файловых менеджеров (в т.ч. ASTRO)</li>
+<li>Словенский перевод</li>
+<li>новая база данных, еще быстрее и компактнее</li>
+<li>добавлены обработчики для взаимодействия с другими приложениями</li>
+<li>исправления ошибок</li>
+</ul>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-ru/help_nfc_beam.html b/OpenKeychain/src/main/res/raw-ru/help_nfc_beam.html
new file mode 100644
index 000000000..fcf55cdaf
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-ru/help_nfc_beam.html
@@ -0,0 +1,12 @@
+<html>
+<head></head>
+<body>
+<h2>Как обменяться ключами</h2>
+<ol>
+<li>Нажмите и удерживайте ключ, который вы хотите передать.</li>
+<li>Поднесите оба устройства вплотную обратными сторонами (до полного касания). Вы почувствуете небольшую вибрацию.</li>
+<li>Как только устройства завибрируют, на экране появится карточка с передаваемым содержимым.</li>
+<li>Нажмите на карточку, что бы передать данные (ключи) с одного устройства на другое.</li>
+</ol>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-ru/help_start.html b/OpenKeychain/src/main/res/raw-ru/help_start.html
new file mode 100644
index 000000000..78db598ec
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-ru/help_start.html
@@ -0,0 +1,19 @@
+<html>
+<head></head>
+<body>
+<h2>Приступая</h2>
+<p>Для начала вам понадобится своя пара ключей. Создайте её через меню раздела "Контакты" или импортируйте ранее созданный секретный ключ через меню "Импорт ключей". После этого вы сможете скачать ключи ваших друзей или обменяться ключами посредством QR кодов или NFC.</p>
+
+<p>Рекомендуется установить <a href="market://details?id=org.openintents.filemanager">OI File Manager</a> для удобного выбора файлов и <a href="market://details?id=com.google.zxing.client.android">Barcode Scanner</a> для распознавания QR кодов. Перейдите по ссылкам на соответствующие страницы Google Play или F-Droid для дальнейшей установки.</p>
+
+<h2>Я нашел ошибку в OpenKeychain!</h2>
+<p>Пожалуйста, сообщайте о возникших проблемах и найденных ошибках в разделе <a href="https://github.com/openpgp-keychain/openpgp-keychain/issues">Решение проблем OpenKeychain</a>.</p>
+
+<h2>Вклад в развитие</h2>
+<p>Если Вы хотите помочь в разработке OpenKeychain, обратитесь к <a href="https://github.com/openpgp-keychain/openpgp-keychain#contribute-code">инструкции на Github</a>.</p>
+
+<h2>Перевод</h2>
+<p>Помогите переводить OpenKeychain! Каждый может принять участие в переводе <a href="https://www.transifex.com/projects/p/openpgp-keychain/">OpenKeychain на Transifex</a>.</p>
+
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-ru/nfc_beam_share.html b/OpenKeychain/src/main/res/raw-ru/nfc_beam_share.html
new file mode 100644
index 000000000..584353da4
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-ru/nfc_beam_share.html
@@ -0,0 +1,11 @@
+<html>
+<head></head>
+<body>
+<ol>
+<li>Убедитесь, что NFC включен в настройках телефона: Настройки &gt; Дополнительно &gt; NFC</li>
+<li>Поднесите оба устройства вплотную обратными сторонами (до полного касания). Вы почувствуете небольшую вибрацию.</li>
+<li>Как только устройства завибрируют, на экране появится карточка с передаваемым содержимым.</li>
+<li>Нажмите на карточку, что бы передать данные (ключи) с одного устройства на другое.</li>
+</ol>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-sl-rSI/help_about.html b/OpenKeychain/src/main/res/raw-sl-rSI/help_about.html
new file mode 100644
index 000000000..ae7e16aae
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-sl-rSI/help_about.html
@@ -0,0 +1,49 @@
+<html>
+<head></head>
+<body>
+<p><a href="http://www.openkeychain.org">http://www.openkeychain.org</a></p>
+<p><a href="http://www.openkeychain.org">OpenKeychain</a> is an OpenPGP implementation for Android.</p>
+<p>License: GPLv3+</p>
+
+<h2>Developers OpenKeychain</h2>
+<ul>
+<li>Dominik Schürmann (Lead developer)</li>
+<li>Ash Hughes (crypto patches)</li>
+<li>Brian C. Barnes</li>
+<li>Bahtiar 'kalkin' Gadimov (UI)</li>
+<li>Daniel Hammann</li>
+<li>Daniel Haß</li>
+<li>Greg Witczak</li>
+<li>Miroojin Bakshi</li>
+<li>Nikhil Peter Raj</li>
+<li>Paul Sarbinowski</li>
+<li>Sreeram Boyapati</li>
+<li>Vincent Breitmoser</li>
+</ul>
+<h2>Developers APG 1.x</h2>
+<ul>
+<li>Thialfihar (Lead developer)</li>
+<li>'Senecaso' (QRCode, sign key, upload key)</li>
+<li>Markus Doits</li>
+</ul>
+<h2>Libraries</h2>
+<ul>
+<li>
+<a href="http://developer.android.com/tools/support-library/index.html">Android Support Library v4</a> (Apache License v2)</li>
+<li>
+<a href="http://developer.android.com/tools/support-library/index.html">Android Support Library v7 'appcompat'</a> (Apache License v2)</li>
+<li>
+<a href="https://github.com/emilsjolander/StickyListHeaders">StickyListHeaders</a> (Apache License v2)</li>
+<li>
+<a href="https://github.com/Bearded-Hen/Android-Bootstrap">Android-Bootstrap</a> (MIT License)</li>
+<li>
+<a href="http://code.google.com/p/zxing/">ZXing</a> (Apache License v2)</li>
+<li>
+<a href="http://rtyley.github.com/spongycastle/">SpongyCastle</a> (MIT X11 License)</li>
+<li>
+<a href="https://github.com/dschuermann/html-textview">HtmlTextView</a> (Apache License v2)</li>
+<li>
+<a href="https://github.com/johnkil/Android-AppMsg">Android AppMsg Library</a> (Apache License v2)</li>
+</ul>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-sl-rSI/help_changelog.html b/OpenKeychain/src/main/res/raw-sl-rSI/help_changelog.html
new file mode 100644
index 000000000..694c7b046
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-sl-rSI/help_changelog.html
@@ -0,0 +1,136 @@
+<html>
+<head></head>
+<body>
+<h2>2.5</h2>
+<ul>
+<li>fix decryption of symmetric pgp messages/files</li>
+<li>refactored edit key screen (thanks to Ash Hughes)</li>
+<li>new modern design for encrypt/decrypt screens</li>
+<li>OpenPGP API version 3 (multiple api accounts, internal fixes, key lookup)</li>
+</ul>
+<h2>2.4</h2>
+<p>Thanks to all applicants of Google Summer of Code 2014 who made this release feature rich and bug free!
+Besides several small patches, a notable number of patches are made by the following people (in alphabetical order):
+Daniel Hammann, Daniel Haß, Greg Witczak, Miroojin Bakshi, Nikhil Peter Raj, Paul Sarbinowski, Sreeram Boyapati, Vincent Breitmoser.</p>
+<ul>
+<li>new unified key list</li>
+<li>colorized key fingerprint</li>
+<li>support for keyserver ports</li>
+<li>deactivate possibility to generate weak keys</li>
+<li>much more internal work on the API</li>
+<li>certify user ids</li>
+<li>keyserver query based on machine-readable output</li>
+<li>lock navigation drawer on tablets</li>
+<li>suggestions for emails on creation of keys</li>
+<li>search in public key lists</li>
+<li>and much more improvements and fixes…</li>
+</ul>
+<h2>2.3.1</h2>
+<ul>
+<li>hotfix for crash when upgrading from old versions</li>
+</ul>
+<h2>2.3</h2>
+<ul>
+<li>remove unnecessary export of public keys when exporting secret key (thanks to Ash Hughes)</li>
+<li>fix setting expiry dates on keys (thanks to Ash Hughes)</li>
+<li>more internal fixes when editing keys (thanks to Ash Hughes)</li>
+<li>querying keyservers directly from the import screen</li>
+<li>fix layout and dialog style on Android 2.2-3.0</li>
+<li>fix crash on keys with empty user ids</li>
+<li>fix crash and empty lists when coming back from signing screen</li>
+<li>Bouncy Castle (cryptography library) updated from 1.47 to 1.50 and build from source</li>
+<li>fix upload of key from signing screen</li>
+</ul>
+<h2>2.2</h2>
+<ul>
+<li>new design with navigation drawer</li>
+<li>new public key list design</li>
+<li>new public key view</li>
+<li>bug fixes for importing of keys</li>
+<li>key cross-certification (thanks to Ash Hughes)</li>
+<li>handle UTF-8 passwords properly (thanks to Ash Hughes)</li>
+<li>first version with new languages (thanks to the contributors on Transifex)</li>
+<li>sharing of keys via QR Codes fixed and improved</li>
+<li>package signature verification for API</li>
+</ul>
+<h2>2.1.1</h2>
+<ul>
+<li>API Updates, preparation for K-9 Mail integration</li>
+</ul>
+<h2>2.1</h2>
+<ul>
+<li>lots of bug fixes</li>
+<li>new API for developers</li>
+<li>PRNG bug fix by Google</li>
+</ul>
+<h2>2.0</h2>
+<ul>
+<li>complete redesign</li>
+<li>share public keys via qr codes, nfc beam</li>
+<li>sign keys</li>
+<li>upload keys to server</li>
+<li>fixes import issues</li>
+<li>new AIDL API</li>
+</ul>
+<h2>1.0.8</h2>
+<ul>
+<li>basic keyserver support</li>
+<li>app2sd</li>
+<li>more choices for pass phrase cache: 1, 2, 4, 8, hours</li>
+<li>translations: Norwegian (thanks, Sander Danielsen), Chinese (thanks, Zhang Fredrick)</li>
+<li>bugfixes</li>
+<li>optimizations</li>
+</ul>
+<h2>1.0.7</h2>
+<ul>
+<li>fixed problem with signature verification of texts with trailing newline</li>
+<li>more options for pass phrase cache time to live (20, 40, 60 mins)</li>
+</ul>
+<h2>1.0.6</h2>
+<ul>
+<li>account adding crash on Froyo fixed</li>
+<li>secure file deletion</li>
+<li>option to delete key file after import</li>
+<li>stream encryption/decryption (gallery, etc.)</li>
+<li>new options (language, force v3 signatures)</li>
+<li>interface changes</li>
+<li>bugfixes</li>
+</ul>
+<h2>1.0.5</h2>
+<ul>
+<li>German and Italian translation</li>
+<li>much smaller package, due to reduced BC sources</li>
+<li>new preferences GUI</li>
+<li>layout adjustment for localization</li>
+<li>signature bugfix</li>
+</ul>
+<h2>1.0.4</h2>
+<ul>
+<li>fixed another crash caused by some SDK bug with query builder</li>
+</ul>
+<h2>1.0.3</h2>
+<ul>
+<li>fixed crashes during encryption/signing and possibly key export</li>
+</ul>
+<h2>1.0.2</h2>
+<ul>
+<li>filterable key lists</li>
+<li>smarter pre-selection of encryption keys</li>
+<li>new Intent handling for VIEW and SEND, allows files to be encrypted/decrypted out of file managers</li>
+<li>fixes and additional features (key preselection) for K-9 Mail, new beta build available</li>
+</ul>
+<h2>1.0.1</h2>
+<ul>
+<li>GMail account listing was broken in 1.0.0, fixed again</li>
+</ul>
+<h2>1.0.0</h2>
+<ul>
+<li>K-9 Mail integration, APG supporting beta build of K-9 Mail</li>
+<li>support of more file managers (including ASTRO)</li>
+<li>Slovenian translation</li>
+<li>new database, much faster, less memory usage</li>
+<li>defined Intents and content provider for other apps</li>
+<li>bugfixes</li>
+</ul>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-sl-rSI/help_nfc_beam.html b/OpenKeychain/src/main/res/raw-sl-rSI/help_nfc_beam.html
new file mode 100644
index 000000000..88492731c
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-sl-rSI/help_nfc_beam.html
@@ -0,0 +1,12 @@
+<html>
+<head></head>
+<body>
+<h2>How to receive keys</h2>
+<ol>
+<li>Go to your partners contacts and open the contact you want to share.</li>
+<li>Hold the two devices back to back (they have to be almost touching) and you’ll feel a vibration.</li>
+<li>After it vibrates you’ll see the content on your partners device turn into a card-like object with Star Trek warp speed-looking animation in the background.</li>
+<li>Tap the card and the content will then load on the your device.</li>
+</ol>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-sl-rSI/help_start.html b/OpenKeychain/src/main/res/raw-sl-rSI/help_start.html
new file mode 100644
index 000000000..0e60c17a7
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-sl-rSI/help_start.html
@@ -0,0 +1,19 @@
+<html>
+<head></head>
+<body>
+<h2>Getting started</h2>
+<p>First you need a personal key pair. Create one via the option menus in "Contacts" or import existing key pairs via "Import Keys". Afterwards, you can download your friends' keys or exchange them via QR Codes or NFC.</p>
+
+<p>It is recommended that you install <a href="market://details?id=org.openintents.filemanager">OI File Manager</a> for enhanced file selection and <a href="market://details?id=com.google.zxing.client.android">Barcode Scanner</a> to scan generated QR Codes. Clicking on the links will open Google Play Store or F-Droid for installation.</p>
+
+<h2>I found a bug in OpenKeychain!</h2>
+<p>Please report the bug using the <a href="https://github.com/openpgp-keychain/openpgp-keychain/issues">issue tracker of OpenKeychain</a>.</p>
+
+<h2>Contribute</h2>
+<p>If you want to help us developing OpenKeychain by contributing code <a href="https://github.com/openpgp-keychain/openpgp-keychain#contribute-code">follow our small guide on Github</a>.</p>
+
+<h2>Translations</h2>
+<p>Help translating OpenKeychain! Everybody can participate at <a href="https://www.transifex.com/projects/p/openpgp-keychain/">OpenKeychain on Transifex</a>.</p>
+
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-sl-rSI/nfc_beam_share.html b/OpenKeychain/src/main/res/raw-sl-rSI/nfc_beam_share.html
new file mode 100644
index 000000000..083e055c7
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-sl-rSI/nfc_beam_share.html
@@ -0,0 +1,11 @@
+<html>
+<head></head>
+<body>
+<ol>
+<li>Make sure that NFC is turned on in Settings &gt; More &gt; NFC and make sure that Android Beam is also on in the same section.</li>
+<li>Hold the two devices back to back (they have to be almost touching) and you'll feel a vibration.</li>
+<li>After it vibrates you'll see the content on your device turn into a card-like object with Star Trek warp speed-looking animation in the background.</li>
+<li>Tap the card and the content will then load on the other person’s device.</li>
+</ol>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-tr/help_about.html b/OpenKeychain/src/main/res/raw-tr/help_about.html
new file mode 100644
index 000000000..7d2c24f9c
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-tr/help_about.html
@@ -0,0 +1,49 @@
+<html>
+<head></head>
+<body>
+<p><a href="http://www.openkeychain.org">http://www.openkeychain.org</a></p>
+<p><a href="http://www.openkeychain.org">OpenKeychain</a> is an OpenPGP implementation for Android.</p>
+<p>Lisans: GPLv3+</p>
+
+<h2>Developers OpenKeychain</h2>
+<ul>
+<li>Dominik Schürmann (Baş geliştirici)</li>
+<li>Ash Hughes (kripto yamaları)</li>
+<li>Brian C. Barnes</li>
+<li>Bahtiar 'kalkin' Gadimov (Arayüz)</li>
+<li>Daniel Hammann</li>
+<li>Daniel Haß</li>
+<li>Greg Witczak</li>
+<li>Miroojin Bakshi</li>
+<li>Nikhil Peter Raj</li>
+<li>Paul Sarbinowski</li>
+<li>Sreeram Boyapati</li>
+<li>Vincent Breitmoser</li>
+</ul>
+<h2>Geliştiriciler APG 1.x</h2>
+<ul>
+<li>Thialfihar (Lead developer)</li>
+<li>'Senecaso' (QR Kodu, anahtar imzalama, anahtar yükleme)</li>
+<li>Markus Doits</li>
+</ul>
+<h2>Kütüphaneler</h2>
+<ul>
+<li>
+<a href="http://developer.android.com/tools/support-library/index.html">Android Support Library v4</a> (Apache License v2)</li>
+<li>
+<a href="http://developer.android.com/tools/support-library/index.html">Android Support Library v7 'appcompat'</a> (Apache License v2)</li>
+<li>
+<a href="https://github.com/emilsjolander/StickyListHeaders">StickyListHeaders</a> (Apache License v2)</li>
+<li>
+<a href="https://github.com/Bearded-Hen/Android-Bootstrap">Android-Bootstrap</a> (MIT License)</li>
+<li>
+<a href="http://code.google.com/p/zxing/">ZXing</a> (Apache License v2)</li>
+<li>
+<a href="http://rtyley.github.com/spongycastle/">SpongyCastle</a> (MIT X11 License)</li>
+<li>
+<a href="https://github.com/dschuermann/html-textview">HtmlTextView</a> (Apache License v2)</li>
+<li>
+<a href="https://github.com/johnkil/Android-AppMsg">Android AppMsg Library</a> (Apache License v2)</li>
+</ul>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-tr/help_changelog.html b/OpenKeychain/src/main/res/raw-tr/help_changelog.html
new file mode 100644
index 000000000..694c7b046
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-tr/help_changelog.html
@@ -0,0 +1,136 @@
+<html>
+<head></head>
+<body>
+<h2>2.5</h2>
+<ul>
+<li>fix decryption of symmetric pgp messages/files</li>
+<li>refactored edit key screen (thanks to Ash Hughes)</li>
+<li>new modern design for encrypt/decrypt screens</li>
+<li>OpenPGP API version 3 (multiple api accounts, internal fixes, key lookup)</li>
+</ul>
+<h2>2.4</h2>
+<p>Thanks to all applicants of Google Summer of Code 2014 who made this release feature rich and bug free!
+Besides several small patches, a notable number of patches are made by the following people (in alphabetical order):
+Daniel Hammann, Daniel Haß, Greg Witczak, Miroojin Bakshi, Nikhil Peter Raj, Paul Sarbinowski, Sreeram Boyapati, Vincent Breitmoser.</p>
+<ul>
+<li>new unified key list</li>
+<li>colorized key fingerprint</li>
+<li>support for keyserver ports</li>
+<li>deactivate possibility to generate weak keys</li>
+<li>much more internal work on the API</li>
+<li>certify user ids</li>
+<li>keyserver query based on machine-readable output</li>
+<li>lock navigation drawer on tablets</li>
+<li>suggestions for emails on creation of keys</li>
+<li>search in public key lists</li>
+<li>and much more improvements and fixes…</li>
+</ul>
+<h2>2.3.1</h2>
+<ul>
+<li>hotfix for crash when upgrading from old versions</li>
+</ul>
+<h2>2.3</h2>
+<ul>
+<li>remove unnecessary export of public keys when exporting secret key (thanks to Ash Hughes)</li>
+<li>fix setting expiry dates on keys (thanks to Ash Hughes)</li>
+<li>more internal fixes when editing keys (thanks to Ash Hughes)</li>
+<li>querying keyservers directly from the import screen</li>
+<li>fix layout and dialog style on Android 2.2-3.0</li>
+<li>fix crash on keys with empty user ids</li>
+<li>fix crash and empty lists when coming back from signing screen</li>
+<li>Bouncy Castle (cryptography library) updated from 1.47 to 1.50 and build from source</li>
+<li>fix upload of key from signing screen</li>
+</ul>
+<h2>2.2</h2>
+<ul>
+<li>new design with navigation drawer</li>
+<li>new public key list design</li>
+<li>new public key view</li>
+<li>bug fixes for importing of keys</li>
+<li>key cross-certification (thanks to Ash Hughes)</li>
+<li>handle UTF-8 passwords properly (thanks to Ash Hughes)</li>
+<li>first version with new languages (thanks to the contributors on Transifex)</li>
+<li>sharing of keys via QR Codes fixed and improved</li>
+<li>package signature verification for API</li>
+</ul>
+<h2>2.1.1</h2>
+<ul>
+<li>API Updates, preparation for K-9 Mail integration</li>
+</ul>
+<h2>2.1</h2>
+<ul>
+<li>lots of bug fixes</li>
+<li>new API for developers</li>
+<li>PRNG bug fix by Google</li>
+</ul>
+<h2>2.0</h2>
+<ul>
+<li>complete redesign</li>
+<li>share public keys via qr codes, nfc beam</li>
+<li>sign keys</li>
+<li>upload keys to server</li>
+<li>fixes import issues</li>
+<li>new AIDL API</li>
+</ul>
+<h2>1.0.8</h2>
+<ul>
+<li>basic keyserver support</li>
+<li>app2sd</li>
+<li>more choices for pass phrase cache: 1, 2, 4, 8, hours</li>
+<li>translations: Norwegian (thanks, Sander Danielsen), Chinese (thanks, Zhang Fredrick)</li>
+<li>bugfixes</li>
+<li>optimizations</li>
+</ul>
+<h2>1.0.7</h2>
+<ul>
+<li>fixed problem with signature verification of texts with trailing newline</li>
+<li>more options for pass phrase cache time to live (20, 40, 60 mins)</li>
+</ul>
+<h2>1.0.6</h2>
+<ul>
+<li>account adding crash on Froyo fixed</li>
+<li>secure file deletion</li>
+<li>option to delete key file after import</li>
+<li>stream encryption/decryption (gallery, etc.)</li>
+<li>new options (language, force v3 signatures)</li>
+<li>interface changes</li>
+<li>bugfixes</li>
+</ul>
+<h2>1.0.5</h2>
+<ul>
+<li>German and Italian translation</li>
+<li>much smaller package, due to reduced BC sources</li>
+<li>new preferences GUI</li>
+<li>layout adjustment for localization</li>
+<li>signature bugfix</li>
+</ul>
+<h2>1.0.4</h2>
+<ul>
+<li>fixed another crash caused by some SDK bug with query builder</li>
+</ul>
+<h2>1.0.3</h2>
+<ul>
+<li>fixed crashes during encryption/signing and possibly key export</li>
+</ul>
+<h2>1.0.2</h2>
+<ul>
+<li>filterable key lists</li>
+<li>smarter pre-selection of encryption keys</li>
+<li>new Intent handling for VIEW and SEND, allows files to be encrypted/decrypted out of file managers</li>
+<li>fixes and additional features (key preselection) for K-9 Mail, new beta build available</li>
+</ul>
+<h2>1.0.1</h2>
+<ul>
+<li>GMail account listing was broken in 1.0.0, fixed again</li>
+</ul>
+<h2>1.0.0</h2>
+<ul>
+<li>K-9 Mail integration, APG supporting beta build of K-9 Mail</li>
+<li>support of more file managers (including ASTRO)</li>
+<li>Slovenian translation</li>
+<li>new database, much faster, less memory usage</li>
+<li>defined Intents and content provider for other apps</li>
+<li>bugfixes</li>
+</ul>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-tr/help_nfc_beam.html b/OpenKeychain/src/main/res/raw-tr/help_nfc_beam.html
new file mode 100644
index 000000000..88492731c
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-tr/help_nfc_beam.html
@@ -0,0 +1,12 @@
+<html>
+<head></head>
+<body>
+<h2>How to receive keys</h2>
+<ol>
+<li>Go to your partners contacts and open the contact you want to share.</li>
+<li>Hold the two devices back to back (they have to be almost touching) and you’ll feel a vibration.</li>
+<li>After it vibrates you’ll see the content on your partners device turn into a card-like object with Star Trek warp speed-looking animation in the background.</li>
+<li>Tap the card and the content will then load on the your device.</li>
+</ol>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-tr/help_start.html b/OpenKeychain/src/main/res/raw-tr/help_start.html
new file mode 100644
index 000000000..0e60c17a7
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-tr/help_start.html
@@ -0,0 +1,19 @@
+<html>
+<head></head>
+<body>
+<h2>Getting started</h2>
+<p>First you need a personal key pair. Create one via the option menus in "Contacts" or import existing key pairs via "Import Keys". Afterwards, you can download your friends' keys or exchange them via QR Codes or NFC.</p>
+
+<p>It is recommended that you install <a href="market://details?id=org.openintents.filemanager">OI File Manager</a> for enhanced file selection and <a href="market://details?id=com.google.zxing.client.android">Barcode Scanner</a> to scan generated QR Codes. Clicking on the links will open Google Play Store or F-Droid for installation.</p>
+
+<h2>I found a bug in OpenKeychain!</h2>
+<p>Please report the bug using the <a href="https://github.com/openpgp-keychain/openpgp-keychain/issues">issue tracker of OpenKeychain</a>.</p>
+
+<h2>Contribute</h2>
+<p>If you want to help us developing OpenKeychain by contributing code <a href="https://github.com/openpgp-keychain/openpgp-keychain#contribute-code">follow our small guide on Github</a>.</p>
+
+<h2>Translations</h2>
+<p>Help translating OpenKeychain! Everybody can participate at <a href="https://www.transifex.com/projects/p/openpgp-keychain/">OpenKeychain on Transifex</a>.</p>
+
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-tr/nfc_beam_share.html b/OpenKeychain/src/main/res/raw-tr/nfc_beam_share.html
new file mode 100644
index 000000000..083e055c7
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-tr/nfc_beam_share.html
@@ -0,0 +1,11 @@
+<html>
+<head></head>
+<body>
+<ol>
+<li>Make sure that NFC is turned on in Settings &gt; More &gt; NFC and make sure that Android Beam is also on in the same section.</li>
+<li>Hold the two devices back to back (they have to be almost touching) and you'll feel a vibration.</li>
+<li>After it vibrates you'll see the content on your device turn into a card-like object with Star Trek warp speed-looking animation in the background.</li>
+<li>Tap the card and the content will then load on the other person’s device.</li>
+</ol>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-uk/help_about.html b/OpenKeychain/src/main/res/raw-uk/help_about.html
new file mode 100644
index 000000000..b51b80617
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-uk/help_about.html
@@ -0,0 +1,49 @@
+<html>
+<head></head>
+<body>
+<p><a href="http://www.openkeychain.org">http://www.openkeychain.org</a></p>
+<p><a href="http://www.openkeychain.org">OpenKeychain</a> імплементація OpenPGP для Андроїду.</p>
+<p>Ліцензія: GPLv3+</p>
+
+<h2>Розробники OpenPGP Keychain</h2>
+<ul>
+<li>Домінік Шурман (основний розробник)</li>
+<li>Аш Гюдж (латки шифрування)</li>
+<li>Браян С. Барнс</li>
+<li>Бахтіяр 'kalkin' Ґадімов (інтерфейс)</li>
+<li>Даніель Гаман</li>
+<li>Даніель Габ</li>
+<li>Ґреґ Вітчак</li>
+<li>Міроджін Бакші</li>
+<li>Ніхіл Петер Радж</li>
+<li>Пауль Сарбіновський</li>
+<li>Срірам Вояпаті</li>
+<li>Вінсент Брейтмозер</li>
+</ul>
+<h2>Розробники APG 1.x</h2>
+<ul>
+<li>Thialfihar (основний розробник)</li>
+<li>'Senecaso' (штрих-код, підпис і завантаження ключів)</li>
+<li>Маркус Дойтс</li>
+</ul>
+<h2>Бібліотеки</h2>
+<ul>
+<li>
+<a href="http://developer.android.com/tools/support-library/index.html">Бібліотека підтримки Android в.4</a> (Ліцензія Apache в. 2)</li>
+<li>
+<a href="http://developer.android.com/tools/support-library/index.html">Бібліотека підтримки Android в.7 'appcompat'</a> (Ліцензія Apache в.2)</li>
+<li>
+<a href="https://github.com/emilsjolander/StickyListHeaders">StickyListHeaders</a> (ліцензія Apache в. 2)</li>
+<li>
+<a href="https://github.com/Bearded-Hen/Android-Bootstrap">Android-Bootstrap</a> (ліцензія МІТ)</li>
+<li>
+<a href="http://code.google.com/p/zxing/">ZXing</a> (ліцензія Apache в.2)</li>
+<li>
+<a href="http://rtyley.github.com/spongycastle/">SpongyCastle</a> (ліцензія MIT X11)</li>
+<li>
+<a href="https://github.com/dschuermann/html-textview">HtmlTextView</a> (ліцензія Apache в.2)</li>
+<li>
+<a href="https://github.com/johnkil/Android-AppMsg">Бібліотека Android AppMsg Library</a> (Ліцензія Apache в. 2)</li>
+</ul>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-uk/help_changelog.html b/OpenKeychain/src/main/res/raw-uk/help_changelog.html
new file mode 100644
index 000000000..f3dd5d5b6
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-uk/help_changelog.html
@@ -0,0 +1,136 @@
+<html>
+<head></head>
+<body>
+<h2>2.5</h2>
+<ul>
+<li>виправлено опис симетричних повідомлень/файлів pgp</li>
+<li>перероблено екран редагування ключа (завдяки Ash Hughes)</li>
+<li>новий сучасний дизайн для екранів шифрування/розшифрування</li>
+<li>OpenPGP API версія 3 (підтримка кількох профілів, внутрішні зміни, пошук ключа)</li>
+</ul>
+<h2>2.4</h2>
+<p>Дякуємо усім заявникам Google Summer of Code 2014, які зробили цю версію багатшу на функції та вільну від помилок!
+Крім окремих незначних латок, значне число латок зробили наступні люди (у алфавітному порядку):
+Даніель Гаман, Даніель Габ, Ґреґ Вітчак, Міроджін Бакші, Ніхіл Петер Радж, Пауль Сарбіновський, Срірам Бояпаті, Вінсент Брейтмосер.</p>
+<ul>
+<li>новий єдиний перелік ключів</li>
+<li>кольоровий відбиток ключа</li>
+<li>підтримка для портів сервера ключів</li>
+<li>деактивувати можливість генерувати слабкі ключі</li>
+<li>набагато більше внутрішньої роботи на API</li>
+<li>сертифікувати ідентифікатори користувача</li>
+<li>запит сервера ключів на основі машиночитабельного виводу</li>
+<li>блокувати панель навігації на планшетах</li>
+<li>пропозиції для листів при створенні ключів</li>
+<li>пошук у списках відкритих ключів</li>
+<li>і багато інших покращень та виправлень…</li>
+</ul>
+<h2>2.3.1</h2>
+<ul>
+<li>свіже виправлення збою при оновленні із старих версій</li>
+</ul>
+<h2>2.3</h2>
+<ul>
+<li>видалений непотрібний експорт публічного ключа при експорті секретного ключа (завдяки Ash Hughes)</li>
+<li>виправлено налаштування дат дії ключів (завдяки Ash Hughes)</li>
+<li>більше внутрішніх виправлень при редагуванні ключів (завдяки Ash Hughes)</li>
+<li>сервери запитаного ключа безпосередньо з екрану імпорту</li>
+<li>виправлено стиль розмітки і діалогу у Андроїд 2.2-3.0</li>
+<li>виправлено збої, коли ключ мав порожній ідентифікатор користувача</li>
+<li>виправлено збої та порожні списки при поверненні з екрану реєстрації</li>
+<li>Bouncy Castle (криптографічна бібліотека) оновлена з версії 1.47 до 1.50 та зібрана з коду</li>
+<li>виправлено завантаження ключа з вікна реєстрації</li>
+</ul>
+<h2>2.2</h2>
+<ul>
+<li>новий дизайн з бічною панеллю</li>
+<li>новий дизайн списку ключів</li>
+<li>новий вид перегляду ключа</li>
+<li>виправлення помилок імпорту ключів</li>
+<li>Крос-сертифікація ключів (завдяки Ash Hughes)</li>
+<li>правильна обробка паролів в UTF-8 (завдяки Ash Hughes)</li>
+<li>перша версія з новими мовами (завдяки перекладачам на Transifex)</li>
+<li>виправлення і поліпшення передачі ключів через QR коди</li>
+<li>перевірка підписів пакетів для API</li>
+</ul>
+<h2>2.1.1</h2>
+<ul>
+<li>Оновлення API, підготовка до інтеграції з K-9 Mail</li>
+</ul>
+<h2>2.1</h2>
+<ul>
+<li>безліч виправлень помилок</li>
+<li>новий API для розробників</li>
+<li>Виправлення вади генератора випадкових чисел від Google</li>
+</ul>
+<h2>2.0</h2>
+<ul>
+<li>переробка дизайну</li>
+<li>передача ключів через QR-коди і NFC</li>
+<li>підписання ключів</li>
+<li>завантаження на сервер ключів</li>
+<li>виправлення проблем імпорту</li>
+<li>новий AIDL API</li>
+</ul>
+<h2>1.0.8</h2>
+<ul>
+<li>підтримка сервера основних ключів</li>
+<li>app2sd</li>
+<li>більше варіантів збереження кешу пароля: 1, 2, 4, 8 годин</li>
+<li>переклади: норвезькою (завдяки Сандер Даніельсен), китайською (завдяки Чжан Фредріку)</li>
+<li>виправлення вад</li>
+<li>оптимізації</li>
+</ul>
+<h2>1.0.7</h2>
+<ul>
+<li>виправлення помилки при перевірці підпису тексту з переведенням рядка</li>
+<li>більше варіантів збереження кешу пароля (20, 40, 60 хвилин)</li>
+</ul>
+<h2>1.0.6</h2>
+<ul>
+<li>виправлення помилки створення запису на Froyo</li>
+<li>вилучення безпечного файлу</li>
+<li>вилучення файлу ключа після імпорту</li>
+<li>передача шифрування (галерея і т. д.)</li>
+<li>нові можливості (мова, примусові v3 підписи)</li>
+<li>зміни інтерфейсу</li>
+<li>виправлення вад</li>
+</ul>
+<h2>1.0.5</h2>
+<ul>
+<li>Німецький та італійський переклад</li>
+<li>істотно менший пакунок програми завдяки зменшенню джерел</li>
+<li>нові налаштунки інтерфейсу</li>
+<li>зміна розмітки для локалізації</li>
+<li>виправлення помилки підпису</li>
+</ul>
+<h2>1.0.4</h2>
+<ul>
+<li>виправлення ще однієї помилки, що виникає в SDK</li>
+</ul>
+<h2>1.0.3</h2>
+<ul>
+<li>виправлення помилок при шифруванні/підписанні та експорті ключів</li>
+</ul>
+<h2>1.0.2</h2>
+<ul>
+<li>фільтр списку ключів</li>
+<li>поліпшення вибору ключів шифрування</li>
+<li>додана можливість шифрувати файли прямо з файлового менеджера</li>
+<li>виправлення помилок і нові можливості (попередній вибір ключа) для інтеграції з K-9 Mail, нова бета-збірка доступна</li>
+</ul>
+<h2>1.0.1</h2>
+<ul>
+<li>виправлення вибору облікового запису GMail, зламаного в 1.0.0</li>
+</ul>
+<h2>1.0.0</h2>
+<ul>
+<li>інтеграція з K-9 Mail, APG підтримує бета-збірку K-9 Mail</li>
+<li>підтримка сторонніх файлових менеджерів (в т.ч. ASTRO)</li>
+<li>Словенський переклад</li>
+<li>нова база даних, швидша робота, менше використання пам'яті</li>
+<li>додано обробники для взаємодії з іншими програмами</li>
+<li>виправлення вад</li>
+</ul>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-uk/help_nfc_beam.html b/OpenKeychain/src/main/res/raw-uk/help_nfc_beam.html
new file mode 100644
index 000000000..dc34048d3
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-uk/help_nfc_beam.html
@@ -0,0 +1,12 @@
+<html>
+<head></head>
+<body>
+<h2>Як обмінятися ключами</h2>
+<ol>
+<li>Перейдіть до контактних даних ваших партнерів і відкрийте контакт, який ви хочете надіслати.</li>
+<li>Піднесіть обидва пристрої впритул зворотними сторонами (до повного торкання). Ви відчуєте невелику вібрацію.</li>
+<li>Після вібрації пристроїв на екрані з'явиться картка з передаваним вмістом.</li>
+<li>Натисніть на картку, що б передати дані з одного пристрою на інший.</li>
+</ol>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-uk/help_start.html b/OpenKeychain/src/main/res/raw-uk/help_start.html
new file mode 100644
index 000000000..45a3edb6a
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-uk/help_start.html
@@ -0,0 +1,19 @@
+<html>
+<head></head>
+<body>
+<h2>Приступаючи до роботи</h2>
+<p>Спершу вам потрібна персональна в'язка ключів. Створіть одну через меню параметрів у "Контактах" або імпортуйте наявні в'язки ключів через "Імпорт ключів". Після цього ви зможете завантажувати ключі ваших друзів чи обміняти їх через штрих-коди або NFC.</p>
+
+<p>Рекомендуємо вам встановити <a href="market://details?id=org.openintents.filemanager">OI File Manager</a> для поліпшеного виділення файлів та <a href="market://details?id=com.google.zxing.client.android">Barcode Scanner</a> для сканування згенерованих штрих-кодів. Натискання посилань відкриє Google Play або F-Droid для встановлення.</p>
+
+<h2>Я знайшов помилку в OpenPGP Keychain!</h2>
+<p>Будь ласка, повідомте про ваду за допомогою <a href="https://github.com/openpgp-keychain/openpgp-keychain/issues">відстежувача проблем OpenPGP Keychain</a>.</p>
+
+<h2>Внесок</h2>
+<p>Якщо ви хочете допомогти нам у розробці OpenPGP Keychain через редагування коду програми <a href="https://github.com/openpgp-keychain/openpgp-keychain#contribute-code">підпишіться на наш невеличкий посібник у Github</a>.</p>
+
+<h2>Переклади</h2>
+<p>Допоможіть перекласти OpenPGP Keychain! Кожний може взяти участь на <a href="https://www.transifex.com/projects/p/openpgp-keychain/">OpenPGP Keychain на Transifex</a>.</p>
+
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-uk/nfc_beam_share.html b/OpenKeychain/src/main/res/raw-uk/nfc_beam_share.html
new file mode 100644
index 000000000..4adff525f
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-uk/nfc_beam_share.html
@@ -0,0 +1,11 @@
+<html>
+<head></head>
+<body>
+<ol>
+<li>Переконайтеся, що NFC увімкнений в налаштуваннях телефону: Налаштування &gt; Додатково &gt; NFC</li>
+<li>Піднесіть обидва пристрої впритул зворотними сторонами (до повного торкання). Ви відчуєте невелику вібрацію.</li>
+<li>Як тільки пристрої завібрують, на екрані з'явиться картка з переданими вмістом.</li>
+<li>Натисніть на картку, що б передати дані з одного пристрою на інший.</li>
+</ol>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-zh-rTW/help_about.html b/OpenKeychain/src/main/res/raw-zh-rTW/help_about.html
new file mode 100644
index 000000000..ae7e16aae
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-zh-rTW/help_about.html
@@ -0,0 +1,49 @@
+<html>
+<head></head>
+<body>
+<p><a href="http://www.openkeychain.org">http://www.openkeychain.org</a></p>
+<p><a href="http://www.openkeychain.org">OpenKeychain</a> is an OpenPGP implementation for Android.</p>
+<p>License: GPLv3+</p>
+
+<h2>Developers OpenKeychain</h2>
+<ul>
+<li>Dominik Schürmann (Lead developer)</li>
+<li>Ash Hughes (crypto patches)</li>
+<li>Brian C. Barnes</li>
+<li>Bahtiar 'kalkin' Gadimov (UI)</li>
+<li>Daniel Hammann</li>
+<li>Daniel Haß</li>
+<li>Greg Witczak</li>
+<li>Miroojin Bakshi</li>
+<li>Nikhil Peter Raj</li>
+<li>Paul Sarbinowski</li>
+<li>Sreeram Boyapati</li>
+<li>Vincent Breitmoser</li>
+</ul>
+<h2>Developers APG 1.x</h2>
+<ul>
+<li>Thialfihar (Lead developer)</li>
+<li>'Senecaso' (QRCode, sign key, upload key)</li>
+<li>Markus Doits</li>
+</ul>
+<h2>Libraries</h2>
+<ul>
+<li>
+<a href="http://developer.android.com/tools/support-library/index.html">Android Support Library v4</a> (Apache License v2)</li>
+<li>
+<a href="http://developer.android.com/tools/support-library/index.html">Android Support Library v7 'appcompat'</a> (Apache License v2)</li>
+<li>
+<a href="https://github.com/emilsjolander/StickyListHeaders">StickyListHeaders</a> (Apache License v2)</li>
+<li>
+<a href="https://github.com/Bearded-Hen/Android-Bootstrap">Android-Bootstrap</a> (MIT License)</li>
+<li>
+<a href="http://code.google.com/p/zxing/">ZXing</a> (Apache License v2)</li>
+<li>
+<a href="http://rtyley.github.com/spongycastle/">SpongyCastle</a> (MIT X11 License)</li>
+<li>
+<a href="https://github.com/dschuermann/html-textview">HtmlTextView</a> (Apache License v2)</li>
+<li>
+<a href="https://github.com/johnkil/Android-AppMsg">Android AppMsg Library</a> (Apache License v2)</li>
+</ul>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-zh-rTW/help_changelog.html b/OpenKeychain/src/main/res/raw-zh-rTW/help_changelog.html
new file mode 100644
index 000000000..694c7b046
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-zh-rTW/help_changelog.html
@@ -0,0 +1,136 @@
+<html>
+<head></head>
+<body>
+<h2>2.5</h2>
+<ul>
+<li>fix decryption of symmetric pgp messages/files</li>
+<li>refactored edit key screen (thanks to Ash Hughes)</li>
+<li>new modern design for encrypt/decrypt screens</li>
+<li>OpenPGP API version 3 (multiple api accounts, internal fixes, key lookup)</li>
+</ul>
+<h2>2.4</h2>
+<p>Thanks to all applicants of Google Summer of Code 2014 who made this release feature rich and bug free!
+Besides several small patches, a notable number of patches are made by the following people (in alphabetical order):
+Daniel Hammann, Daniel Haß, Greg Witczak, Miroojin Bakshi, Nikhil Peter Raj, Paul Sarbinowski, Sreeram Boyapati, Vincent Breitmoser.</p>
+<ul>
+<li>new unified key list</li>
+<li>colorized key fingerprint</li>
+<li>support for keyserver ports</li>
+<li>deactivate possibility to generate weak keys</li>
+<li>much more internal work on the API</li>
+<li>certify user ids</li>
+<li>keyserver query based on machine-readable output</li>
+<li>lock navigation drawer on tablets</li>
+<li>suggestions for emails on creation of keys</li>
+<li>search in public key lists</li>
+<li>and much more improvements and fixes…</li>
+</ul>
+<h2>2.3.1</h2>
+<ul>
+<li>hotfix for crash when upgrading from old versions</li>
+</ul>
+<h2>2.3</h2>
+<ul>
+<li>remove unnecessary export of public keys when exporting secret key (thanks to Ash Hughes)</li>
+<li>fix setting expiry dates on keys (thanks to Ash Hughes)</li>
+<li>more internal fixes when editing keys (thanks to Ash Hughes)</li>
+<li>querying keyservers directly from the import screen</li>
+<li>fix layout and dialog style on Android 2.2-3.0</li>
+<li>fix crash on keys with empty user ids</li>
+<li>fix crash and empty lists when coming back from signing screen</li>
+<li>Bouncy Castle (cryptography library) updated from 1.47 to 1.50 and build from source</li>
+<li>fix upload of key from signing screen</li>
+</ul>
+<h2>2.2</h2>
+<ul>
+<li>new design with navigation drawer</li>
+<li>new public key list design</li>
+<li>new public key view</li>
+<li>bug fixes for importing of keys</li>
+<li>key cross-certification (thanks to Ash Hughes)</li>
+<li>handle UTF-8 passwords properly (thanks to Ash Hughes)</li>
+<li>first version with new languages (thanks to the contributors on Transifex)</li>
+<li>sharing of keys via QR Codes fixed and improved</li>
+<li>package signature verification for API</li>
+</ul>
+<h2>2.1.1</h2>
+<ul>
+<li>API Updates, preparation for K-9 Mail integration</li>
+</ul>
+<h2>2.1</h2>
+<ul>
+<li>lots of bug fixes</li>
+<li>new API for developers</li>
+<li>PRNG bug fix by Google</li>
+</ul>
+<h2>2.0</h2>
+<ul>
+<li>complete redesign</li>
+<li>share public keys via qr codes, nfc beam</li>
+<li>sign keys</li>
+<li>upload keys to server</li>
+<li>fixes import issues</li>
+<li>new AIDL API</li>
+</ul>
+<h2>1.0.8</h2>
+<ul>
+<li>basic keyserver support</li>
+<li>app2sd</li>
+<li>more choices for pass phrase cache: 1, 2, 4, 8, hours</li>
+<li>translations: Norwegian (thanks, Sander Danielsen), Chinese (thanks, Zhang Fredrick)</li>
+<li>bugfixes</li>
+<li>optimizations</li>
+</ul>
+<h2>1.0.7</h2>
+<ul>
+<li>fixed problem with signature verification of texts with trailing newline</li>
+<li>more options for pass phrase cache time to live (20, 40, 60 mins)</li>
+</ul>
+<h2>1.0.6</h2>
+<ul>
+<li>account adding crash on Froyo fixed</li>
+<li>secure file deletion</li>
+<li>option to delete key file after import</li>
+<li>stream encryption/decryption (gallery, etc.)</li>
+<li>new options (language, force v3 signatures)</li>
+<li>interface changes</li>
+<li>bugfixes</li>
+</ul>
+<h2>1.0.5</h2>
+<ul>
+<li>German and Italian translation</li>
+<li>much smaller package, due to reduced BC sources</li>
+<li>new preferences GUI</li>
+<li>layout adjustment for localization</li>
+<li>signature bugfix</li>
+</ul>
+<h2>1.0.4</h2>
+<ul>
+<li>fixed another crash caused by some SDK bug with query builder</li>
+</ul>
+<h2>1.0.3</h2>
+<ul>
+<li>fixed crashes during encryption/signing and possibly key export</li>
+</ul>
+<h2>1.0.2</h2>
+<ul>
+<li>filterable key lists</li>
+<li>smarter pre-selection of encryption keys</li>
+<li>new Intent handling for VIEW and SEND, allows files to be encrypted/decrypted out of file managers</li>
+<li>fixes and additional features (key preselection) for K-9 Mail, new beta build available</li>
+</ul>
+<h2>1.0.1</h2>
+<ul>
+<li>GMail account listing was broken in 1.0.0, fixed again</li>
+</ul>
+<h2>1.0.0</h2>
+<ul>
+<li>K-9 Mail integration, APG supporting beta build of K-9 Mail</li>
+<li>support of more file managers (including ASTRO)</li>
+<li>Slovenian translation</li>
+<li>new database, much faster, less memory usage</li>
+<li>defined Intents and content provider for other apps</li>
+<li>bugfixes</li>
+</ul>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-zh-rTW/help_nfc_beam.html b/OpenKeychain/src/main/res/raw-zh-rTW/help_nfc_beam.html
new file mode 100644
index 000000000..88492731c
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-zh-rTW/help_nfc_beam.html
@@ -0,0 +1,12 @@
+<html>
+<head></head>
+<body>
+<h2>How to receive keys</h2>
+<ol>
+<li>Go to your partners contacts and open the contact you want to share.</li>
+<li>Hold the two devices back to back (they have to be almost touching) and you’ll feel a vibration.</li>
+<li>After it vibrates you’ll see the content on your partners device turn into a card-like object with Star Trek warp speed-looking animation in the background.</li>
+<li>Tap the card and the content will then load on the your device.</li>
+</ol>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-zh-rTW/help_start.html b/OpenKeychain/src/main/res/raw-zh-rTW/help_start.html
new file mode 100644
index 000000000..0e60c17a7
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-zh-rTW/help_start.html
@@ -0,0 +1,19 @@
+<html>
+<head></head>
+<body>
+<h2>Getting started</h2>
+<p>First you need a personal key pair. Create one via the option menus in "Contacts" or import existing key pairs via "Import Keys". Afterwards, you can download your friends' keys or exchange them via QR Codes or NFC.</p>
+
+<p>It is recommended that you install <a href="market://details?id=org.openintents.filemanager">OI File Manager</a> for enhanced file selection and <a href="market://details?id=com.google.zxing.client.android">Barcode Scanner</a> to scan generated QR Codes. Clicking on the links will open Google Play Store or F-Droid for installation.</p>
+
+<h2>I found a bug in OpenKeychain!</h2>
+<p>Please report the bug using the <a href="https://github.com/openpgp-keychain/openpgp-keychain/issues">issue tracker of OpenKeychain</a>.</p>
+
+<h2>Contribute</h2>
+<p>If you want to help us developing OpenKeychain by contributing code <a href="https://github.com/openpgp-keychain/openpgp-keychain#contribute-code">follow our small guide on Github</a>.</p>
+
+<h2>Translations</h2>
+<p>Help translating OpenKeychain! Everybody can participate at <a href="https://www.transifex.com/projects/p/openpgp-keychain/">OpenKeychain on Transifex</a>.</p>
+
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-zh-rTW/nfc_beam_share.html b/OpenKeychain/src/main/res/raw-zh-rTW/nfc_beam_share.html
new file mode 100644
index 000000000..083e055c7
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-zh-rTW/nfc_beam_share.html
@@ -0,0 +1,11 @@
+<html>
+<head></head>
+<body>
+<ol>
+<li>Make sure that NFC is turned on in Settings &gt; More &gt; NFC and make sure that Android Beam is also on in the same section.</li>
+<li>Hold the two devices back to back (they have to be almost touching) and you'll feel a vibration.</li>
+<li>After it vibrates you'll see the content on your device turn into a card-like object with Star Trek warp speed-looking animation in the background.</li>
+<li>Tap the card and the content will then load on the other person’s device.</li>
+</ol>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-zh/help_about.html b/OpenKeychain/src/main/res/raw-zh/help_about.html
new file mode 100644
index 000000000..813676ea2
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-zh/help_about.html
@@ -0,0 +1,49 @@
+<html>
+<head></head>
+<body>
+<p><a href="http://www.openkeychain.org">http://www.openkeychain.org</a></p>
+<p><a href="http://www.openkeychain.org">OpenKeychain</a> is an OpenPGP implementation for Android.</p>
+<p>授權:GPLv3+</p>
+
+<h2>Developers OpenKeychain</h2>
+<ul>
+<li>Dominik Schürmann (Lead developer)</li>
+<li>Ash Hughes (crypto patches)</li>
+<li>Brian C. Barnes</li>
+<li>Bahtiar 'kalkin' Gadimov (介面)</li>
+<li>Daniel Hammann</li>
+<li>Daniel Haß</li>
+<li>Greg Witczak</li>
+<li>Miroojin Bakshi</li>
+<li>Nikhil Peter Raj</li>
+<li>Paul Sarbinowski</li>
+<li>Sreeram Boyapati</li>
+<li>Vincent Breitmoser</li>
+</ul>
+<h2>Developers APG 1.x</h2>
+<ul>
+<li>Thialfihar (Lead developer)</li>
+<li>'Senecaso' (QRCode, sign key, upload key)</li>
+<li>Markus Doits</li>
+</ul>
+<h2>函式庫</h2>
+<ul>
+<li>
+<a href="http://developer.android.com/tools/support-library/index.html">Android Support Library v4</a> (Apache License v2)</li>
+<li>
+<a href="http://developer.android.com/tools/support-library/index.html">Android Support Library v7 'appcompat'</a> (Apache License v2)</li>
+<li>
+<a href="https://github.com/emilsjolander/StickyListHeaders">StickyListHeaders</a> (Apache License v2)</li>
+<li>
+<a href="https://github.com/Bearded-Hen/Android-Bootstrap">Android-Bootstrap</a> (MIT License)</li>
+<li>
+<a href="http://code.google.com/p/zxing/">ZXing</a> (Apache License v2)</li>
+<li>
+<a href="http://rtyley.github.com/spongycastle/">SpongyCastle</a> (MIT X11 License)</li>
+<li>
+<a href="https://github.com/dschuermann/html-textview">HtmlTextView</a> (Apache License v2)</li>
+<li>
+<a href="https://github.com/johnkil/Android-AppMsg">Android AppMsg Library</a> (Apache License v2)</li>
+</ul>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-zh/help_changelog.html b/OpenKeychain/src/main/res/raw-zh/help_changelog.html
new file mode 100644
index 000000000..694c7b046
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-zh/help_changelog.html
@@ -0,0 +1,136 @@
+<html>
+<head></head>
+<body>
+<h2>2.5</h2>
+<ul>
+<li>fix decryption of symmetric pgp messages/files</li>
+<li>refactored edit key screen (thanks to Ash Hughes)</li>
+<li>new modern design for encrypt/decrypt screens</li>
+<li>OpenPGP API version 3 (multiple api accounts, internal fixes, key lookup)</li>
+</ul>
+<h2>2.4</h2>
+<p>Thanks to all applicants of Google Summer of Code 2014 who made this release feature rich and bug free!
+Besides several small patches, a notable number of patches are made by the following people (in alphabetical order):
+Daniel Hammann, Daniel Haß, Greg Witczak, Miroojin Bakshi, Nikhil Peter Raj, Paul Sarbinowski, Sreeram Boyapati, Vincent Breitmoser.</p>
+<ul>
+<li>new unified key list</li>
+<li>colorized key fingerprint</li>
+<li>support for keyserver ports</li>
+<li>deactivate possibility to generate weak keys</li>
+<li>much more internal work on the API</li>
+<li>certify user ids</li>
+<li>keyserver query based on machine-readable output</li>
+<li>lock navigation drawer on tablets</li>
+<li>suggestions for emails on creation of keys</li>
+<li>search in public key lists</li>
+<li>and much more improvements and fixes…</li>
+</ul>
+<h2>2.3.1</h2>
+<ul>
+<li>hotfix for crash when upgrading from old versions</li>
+</ul>
+<h2>2.3</h2>
+<ul>
+<li>remove unnecessary export of public keys when exporting secret key (thanks to Ash Hughes)</li>
+<li>fix setting expiry dates on keys (thanks to Ash Hughes)</li>
+<li>more internal fixes when editing keys (thanks to Ash Hughes)</li>
+<li>querying keyservers directly from the import screen</li>
+<li>fix layout and dialog style on Android 2.2-3.0</li>
+<li>fix crash on keys with empty user ids</li>
+<li>fix crash and empty lists when coming back from signing screen</li>
+<li>Bouncy Castle (cryptography library) updated from 1.47 to 1.50 and build from source</li>
+<li>fix upload of key from signing screen</li>
+</ul>
+<h2>2.2</h2>
+<ul>
+<li>new design with navigation drawer</li>
+<li>new public key list design</li>
+<li>new public key view</li>
+<li>bug fixes for importing of keys</li>
+<li>key cross-certification (thanks to Ash Hughes)</li>
+<li>handle UTF-8 passwords properly (thanks to Ash Hughes)</li>
+<li>first version with new languages (thanks to the contributors on Transifex)</li>
+<li>sharing of keys via QR Codes fixed and improved</li>
+<li>package signature verification for API</li>
+</ul>
+<h2>2.1.1</h2>
+<ul>
+<li>API Updates, preparation for K-9 Mail integration</li>
+</ul>
+<h2>2.1</h2>
+<ul>
+<li>lots of bug fixes</li>
+<li>new API for developers</li>
+<li>PRNG bug fix by Google</li>
+</ul>
+<h2>2.0</h2>
+<ul>
+<li>complete redesign</li>
+<li>share public keys via qr codes, nfc beam</li>
+<li>sign keys</li>
+<li>upload keys to server</li>
+<li>fixes import issues</li>
+<li>new AIDL API</li>
+</ul>
+<h2>1.0.8</h2>
+<ul>
+<li>basic keyserver support</li>
+<li>app2sd</li>
+<li>more choices for pass phrase cache: 1, 2, 4, 8, hours</li>
+<li>translations: Norwegian (thanks, Sander Danielsen), Chinese (thanks, Zhang Fredrick)</li>
+<li>bugfixes</li>
+<li>optimizations</li>
+</ul>
+<h2>1.0.7</h2>
+<ul>
+<li>fixed problem with signature verification of texts with trailing newline</li>
+<li>more options for pass phrase cache time to live (20, 40, 60 mins)</li>
+</ul>
+<h2>1.0.6</h2>
+<ul>
+<li>account adding crash on Froyo fixed</li>
+<li>secure file deletion</li>
+<li>option to delete key file after import</li>
+<li>stream encryption/decryption (gallery, etc.)</li>
+<li>new options (language, force v3 signatures)</li>
+<li>interface changes</li>
+<li>bugfixes</li>
+</ul>
+<h2>1.0.5</h2>
+<ul>
+<li>German and Italian translation</li>
+<li>much smaller package, due to reduced BC sources</li>
+<li>new preferences GUI</li>
+<li>layout adjustment for localization</li>
+<li>signature bugfix</li>
+</ul>
+<h2>1.0.4</h2>
+<ul>
+<li>fixed another crash caused by some SDK bug with query builder</li>
+</ul>
+<h2>1.0.3</h2>
+<ul>
+<li>fixed crashes during encryption/signing and possibly key export</li>
+</ul>
+<h2>1.0.2</h2>
+<ul>
+<li>filterable key lists</li>
+<li>smarter pre-selection of encryption keys</li>
+<li>new Intent handling for VIEW and SEND, allows files to be encrypted/decrypted out of file managers</li>
+<li>fixes and additional features (key preselection) for K-9 Mail, new beta build available</li>
+</ul>
+<h2>1.0.1</h2>
+<ul>
+<li>GMail account listing was broken in 1.0.0, fixed again</li>
+</ul>
+<h2>1.0.0</h2>
+<ul>
+<li>K-9 Mail integration, APG supporting beta build of K-9 Mail</li>
+<li>support of more file managers (including ASTRO)</li>
+<li>Slovenian translation</li>
+<li>new database, much faster, less memory usage</li>
+<li>defined Intents and content provider for other apps</li>
+<li>bugfixes</li>
+</ul>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-zh/help_nfc_beam.html b/OpenKeychain/src/main/res/raw-zh/help_nfc_beam.html
new file mode 100644
index 000000000..7a90a794b
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-zh/help_nfc_beam.html
@@ -0,0 +1,12 @@
+<html>
+<head></head>
+<body>
+<h2>如何接收金要</h2>
+<ol>
+<li>前往你夥伴裝置上的聯絡人清單,並點選你要分享的聯絡人。</li>
+<li>將兩部裝置背對背貼近(幾乎接觸),你會感覺到一股震動。</li>
+<li>震動之後你會看見你夥伴的畫面變成卡片狀,並且背景帶有如 Star Trek 般的特效。</li>
+<li>輕觸卡片,內容隨即顯示在你的裝置上。</li>
+</ol>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-zh/help_start.html b/OpenKeychain/src/main/res/raw-zh/help_start.html
new file mode 100644
index 000000000..22ac99882
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-zh/help_start.html
@@ -0,0 +1,19 @@
+<html>
+<head></head>
+<body>
+<h2>快速上手</h2>
+<p>First you need a personal key pair. Create one via the option menus in "Contacts" or import existing key pairs via "Import Keys". Afterwards, you can download your friends' keys or exchange them via QR Codes or NFC.</p>
+
+<p>It is recommended that you install <a href="market://details?id=org.openintents.filemanager">OI File Manager</a> for enhanced file selection and <a href="market://details?id=com.google.zxing.client.android">Barcode Scanner</a> to scan generated QR Codes. Clicking on the links will open Google Play Store or F-Droid for installation.</p>
+
+<h2>我在OpenKeychain發現問題!</h2>
+<p>請利用 <a href="https://github.com/openpgp-keychain/openpgp-keychain/issues">OpenKeychain 項目回報系統</a>回報問題。</p>
+
+<h2>發布</h2>
+<p>If you want to help us developing OpenKeychain by contributing code <a href="https://github.com/openpgp-keychain/openpgp-keychain#contribute-code">follow our small guide on Github</a>.</p>
+
+<h2>翻譯</h2>
+<p>Help translating OpenKeychain! Everybody can participate at <a href="https://www.transifex.com/projects/p/openpgp-keychain/">OpenKeychain on Transifex</a>.</p>
+
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw-zh/nfc_beam_share.html b/OpenKeychain/src/main/res/raw-zh/nfc_beam_share.html
new file mode 100644
index 000000000..99ffe4c12
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw-zh/nfc_beam_share.html
@@ -0,0 +1,11 @@
+<html>
+<head></head>
+<body>
+<ol>
+<li>確定在 "設定" &gt; "更多內容…" &gt; "NFC" 裡面已經開啟 NFC 和 Android Beam。</li>
+<li>將兩部裝置背對背貼近(幾乎接觸),你會感覺到一股震動。</li>
+<li>震動之後你會看見你夥伴的畫面變成卡片狀,並且背景帶有如 Star Trek 般的特效。</li>
+<li>輕觸卡片,內容隨即顯示在你的裝置上。</li>
+</ol>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw/help_about.html b/OpenKeychain/src/main/res/raw/help_about.html
new file mode 100644
index 000000000..847168446
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw/help_about.html
@@ -0,0 +1,47 @@
+<!-- Maintain structure with headings with h2 tags and content with p tags.
+This makes it easy to translate the values with transifex!
+And don't add newlines before or after p tags because of transifex -->
+<html>
+<head>
+</head>
+<body>
+<p><a href="http://www.openkeychain.org">http://www.openkeychain.org</a></p>
+<p><a href="http://www.openkeychain.org">OpenKeychain</a> is an OpenPGP implementation for Android.</p>
+<p>License: GPLv3+</p>
+
+<h2>Developers OpenKeychain</h2>
+<ul>
+<li>Dominik Schürmann (Lead developer)</li>
+<li>Ash Hughes (crypto patches)</li>
+<li>Brian C. Barnes</li>
+<li>Bahtiar 'kalkin' Gadimov (UI)</li>
+<li>Daniel Hammann</li>
+<li>Daniel Haß</li>
+<li>Greg Witczak</li>
+<li>Miroojin Bakshi</li>
+<li>Nikhil Peter Raj</li>
+<li>Paul Sarbinowski</li>
+<li>Sreeram Boyapati</li>
+<li>Vincent Breitmoser</li>
+</ul>
+
+<h2>Developers APG 1.x</h2>
+<ul>
+<li>Thialfihar (Lead developer)</li>
+<li>'Senecaso' (QRCode, sign key, upload key)</li>
+<li>Markus Doits</li>
+</ul>
+
+<h2>Libraries</h2>
+<ul>
+<li><a href="http://developer.android.com/tools/support-library/index.html">Android Support Library v4</a> (Apache License v2)</li>
+<li><a href="http://developer.android.com/tools/support-library/index.html">Android Support Library v7 'appcompat'</a> (Apache License v2)</li>
+<li><a href="https://github.com/emilsjolander/StickyListHeaders">StickyListHeaders</a> (Apache License v2)</li>
+<li><a href="https://github.com/Bearded-Hen/Android-Bootstrap">Android-Bootstrap</a> (MIT License)</li>
+<li><a href="http://code.google.com/p/zxing/">ZXing</a> (Apache License v2)</li>
+<li><a href="http://rtyley.github.com/spongycastle/">SpongyCastle</a> (MIT X11 License)</li>
+<li><a href="https://github.com/dschuermann/html-textview">HtmlTextView</a> (Apache License v2)</li>
+<li><a href="https://github.com/johnkil/Android-AppMsg">Android AppMsg Library</a> (Apache License v2)</li>
+</ul>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw/help_changelog.html b/OpenKeychain/src/main/res/raw/help_changelog.html
new file mode 100644
index 000000000..64a91e5f1
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw/help_changelog.html
@@ -0,0 +1,156 @@
+<!-- Maintain structure with headings with h2 tags and content with p tags.
+This makes it easy to translate the values with transifex!
+And don't add newlines before or after p tags because of transifex -->
+<html>
+<head>
+</head>
+<body>
+<h2>2.5</h2>
+<ul>
+<li>fix decryption of symmetric pgp messages/files</li>
+<li>refactored edit key screen (thanks to Ash Hughes)</li>
+<li>new modern design for encrypt/decrypt screens</li>
+<li>OpenPGP API version 3 (multiple api accounts, internal fixes, key lookup)</li>
+</ul>
+
+<h2>2.4</h2>
+<p>Thanks to all applicants of Google Summer of Code 2014 who made this release feature rich and bug free!
+Besides several small patches, a notable number of patches are made by the following people (in alphabetical order):
+Daniel Hammann, Daniel Haß, Greg Witczak, Miroojin Bakshi, Nikhil Peter Raj, Paul Sarbinowski, Sreeram Boyapati, Vincent Breitmoser.</p>
+<ul>
+<li>new unified key list</li>
+<li>colorized key fingerprint</li>
+<li>support for keyserver ports</li>
+<li>deactivate possibility to generate weak keys</li>
+<li>much more internal work on the API</li>
+<li>certify user ids</li>
+<li>keyserver query based on machine-readable output</li>
+<li>lock navigation drawer on tablets</li>
+<li>suggestions for emails on creation of keys</li>
+<li>search in public key lists</li>
+<li>and much more improvements and fixes…</li>
+</ul>
+
+<h2>2.3.1</h2>
+<ul>
+<li>hotfix for crash when upgrading from old versions</li>
+</ul>
+
+<h2>2.3</h2>
+<ul>
+<li>remove unnecessary export of public keys when exporting secret key (thanks to Ash Hughes)</li>
+<li>fix setting expiry dates on keys (thanks to Ash Hughes)</li>
+<li>more internal fixes when editing keys (thanks to Ash Hughes)</li>
+<li>querying keyservers directly from the import screen</li>
+<li>fix layout and dialog style on Android 2.2-3.0</li>
+<li>fix crash on keys with empty user ids</li>
+<li>fix crash and empty lists when coming back from signing screen</li>
+<li>Bouncy Castle (cryptography library) updated from 1.47 to 1.50 and build from source</li>
+<li>fix upload of key from signing screen</li>
+</ul>
+
+<h2>2.2</h2>
+<ul>
+<li>new design with navigation drawer</li>
+<li>new public key list design</li>
+<li>new public key view</li>
+<li>bug fixes for importing of keys</li>
+<li>key cross-certification (thanks to Ash Hughes)</li>
+<li>handle UTF-8 passwords properly (thanks to Ash Hughes)</li>
+<li>first version with new languages (thanks to the contributors on Transifex)</li>
+<li>sharing of keys via QR Codes fixed and improved</li>
+<li>package signature verification for API</li>
+</ul>
+
+<h2>2.1.1</h2>
+<ul>
+<li>API Updates, preparation for K-9 Mail integration</li>
+</ul>
+
+<h2>2.1</h2>
+<ul>
+<li>lots of bug fixes</li>
+<li>new API for developers</li>
+<li>PRNG bug fix by Google</li>
+</ul>
+
+<h2>2.0</h2>
+<ul>
+<li>complete redesign</li>
+<li>share public keys via qr codes, nfc beam</li>
+<li>sign keys</li>
+<li>upload keys to server</li>
+<li>fixes import issues</li>
+<li>new AIDL API</li>
+</ul>
+
+<h2>1.0.8</h2>
+<ul>
+<li>basic keyserver support</li>
+<li>app2sd</li>
+<li>more choices for pass phrase cache: 1, 2, 4, 8, hours</li>
+<li>translations: Norwegian (thanks, Sander Danielsen), Chinese (thanks, Zhang Fredrick)</li>
+<li>bugfixes</li>
+<li>optimizations</li>
+</ul>
+
+<h2>1.0.7</h2>
+<ul>
+<li>fixed problem with signature verification of texts with trailing newline</li>
+<li>more options for pass phrase cache time to live (20, 40, 60 mins)</li>
+</ul>
+
+<h2>1.0.6</h2>
+<ul>
+<li>account adding crash on Froyo fixed</li>
+<li>secure file deletion</li>
+<li>option to delete key file after import</li>
+<li>stream encryption/decryption (gallery, etc.)</li>
+<li>new options (language, force v3 signatures)</li>
+<li>interface changes</li>
+<li>bugfixes</li>
+</ul>
+
+<h2>1.0.5</h2>
+<ul>
+<li>German and Italian translation</li>
+<li>much smaller package, due to reduced BC sources</li>
+<li>new preferences GUI</li>
+<li>layout adjustment for localization</li>
+<li>signature bugfix</li>
+</ul>
+
+<h2>1.0.4</h2>
+<ul>
+<li>fixed another crash caused by some SDK bug with query builder</li>
+</ul>
+
+<h2>1.0.3</h2>
+<ul>
+<li>fixed crashes during encryption/signing and possibly key export</li>
+</ul>
+
+<h2>1.0.2</h2>
+<ul>
+<li>filterable key lists</li>
+<li>smarter pre-selection of encryption keys</li>
+<li>new Intent handling for VIEW and SEND, allows files to be encrypted/decrypted out of file managers</li>
+<li>fixes and additional features (key preselection) for K-9 Mail, new beta build available</li>
+</ul>
+
+<h2>1.0.1</h2>
+<ul>
+<li>GMail account listing was broken in 1.0.0, fixed again</li>
+</ul>
+
+<h2>1.0.0</h2>
+<ul>
+<li>K-9 Mail integration, APG supporting beta build of K-9 Mail</li>
+<li>support of more file managers (including ASTRO)</li>
+<li>Slovenian translation</li>
+<li>new database, much faster, less memory usage</li>
+<li>defined Intents and content provider for other apps</li>
+<li>bugfixes</li>
+</ul>
+</body>
+</html> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/raw/help_faq.html b/OpenKeychain/src/main/res/raw/help_faq.html
new file mode 100644
index 000000000..bfd43eafd
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw/help_faq.html
@@ -0,0 +1,13 @@
+<!-- Maintain structure with headings with h2 tags and content with p tags.
+This makes it easy to translate the values with transifex!
+And don't add newlines before or after p tags because of transifex -->
+<html>
+<head>
+</head>
+<body>
+<h2>How can I specify connection port for Keyserver?</h2>
+<p>Add a new Keyserver (or modify existing one) by going to Preferences -> General -> Keyservers. Enter the port number after the Keyserver address and preceded it by a colon. For example, "p80.pool.sks-keyservers.net:80" (without quotation marks) means that server "p80.pool.sks-keyservers.net" is working on a port 80.</p>
+<p>Default connection port is 11371 and it doesn't need to be specified.</p>
+
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw/help_nfc_beam.html b/OpenKeychain/src/main/res/raw/help_nfc_beam.html
new file mode 100644
index 000000000..f3bb9d16e
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw/help_nfc_beam.html
@@ -0,0 +1,16 @@
+<!-- Maintain structure with headings with h2 tags and content with p tags.
+This makes it easy to translate the values with transifex!
+And don't add newlines before or after p tags because of transifex -->
+<html>
+<head>
+</head>
+<body>
+<h2>How to receive keys</h2>
+<ol>
+<li>Go to your partners contacts and open the contact you want to share.</li>
+<li>Hold the two devices back to back (they have to be almost touching) and you’ll feel a vibration.</li>
+<li>After it vibrates you’ll see the content on your partners device turn into a card-like object with Star Trek warp speed-looking animation in the background.</li>
+<li>Tap the card and the content will then load on the your device.</li>
+</ol>
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw/help_start.html b/OpenKeychain/src/main/res/raw/help_start.html
new file mode 100644
index 000000000..56c02b1fd
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw/help_start.html
@@ -0,0 +1,23 @@
+<!-- Maintain structure with headings with h2 tags and content with p tags.
+This makes it easy to translate the values with transifex!
+And don't add newlines before or after p tags because of transifex -->
+<html>
+<head>
+</head>
+<body>
+<h2>Getting started</h2>
+<p>First you need a personal key pair. Create one via the option menus in "Contacts" or import existing key pairs via "Import Keys". Afterwards, you can download your friends' keys or exchange them via QR Codes or NFC.</p>
+
+<p>It is recommended that you install <a href="market://details?id=org.openintents.filemanager">OI File Manager</a> for enhanced file selection and <a href="market://details?id=com.google.zxing.client.android">Barcode Scanner</a> to scan generated QR Codes. Clicking on the links will open Google Play Store or F-Droid for installation.</p>
+
+<h2>I found a bug in OpenKeychain!</h2>
+<p>Please report the bug using the <a href="https://github.com/openpgp-keychain/openpgp-keychain/issues">issue tracker of OpenKeychain</a>.</p>
+
+<h2>Contribute</h2>
+<p>If you want to help us developing OpenKeychain by contributing code <a href="https://github.com/openpgp-keychain/openpgp-keychain#contribute-code">follow our small guide on Github</a>.</p>
+
+<h2>Translations</h2>
+<p>Help translating OpenKeychain! Everybody can participate at <a href="https://www.transifex.com/projects/p/openpgp-keychain/">OpenKeychain on Transifex</a>.</p>
+
+</body>
+</html>
diff --git a/OpenKeychain/src/main/res/raw/nfc_beam_share.html b/OpenKeychain/src/main/res/raw/nfc_beam_share.html
new file mode 100644
index 000000000..873c71777
--- /dev/null
+++ b/OpenKeychain/src/main/res/raw/nfc_beam_share.html
@@ -0,0 +1,15 @@
+<!-- Maintain structure with headings with h2 tags and content with p tags.
+This makes it easy to translate the values with transifex!
+And don't add newlines before or after p tags because of transifex -->
+<html>
+<head>
+</head>
+<body>
+<ol>
+<li>Make sure that NFC is turned on in Settings > More > NFC and make sure that Android Beam is also on in the same section.</li>
+<li>Hold the two devices back to back (they have to be almost touching) and you'll feel a vibration.</li>
+<li>After it vibrates you'll see the content on your device turn into a card-like object with Star Trek warp speed-looking animation in the background.</li>
+<li>Tap the card and the content will then load on the other person’s device.</li>
+</ol>
+</body>
+</html> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/values-cs-rCZ/strings.xml b/OpenKeychain/src/main/res/values-cs-rCZ/strings.xml
new file mode 100644
index 000000000..586785a62
--- /dev/null
+++ b/OpenKeychain/src/main/res/values-cs-rCZ/strings.xml
@@ -0,0 +1,51 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<resources>
+ <!--title-->
+ <string name="title_manage_public_keys">Kontakty</string>
+ <string name="title_manage_secret_keys">Tajné klíče</string>
+ <string name="title_select_recipients">Zvolit veřejný klíč</string>
+ <string name="title_select_secret_key">Zvolit tajný klíč</string>
+ <string name="title_encrypt">Zašifrovat</string>
+ <string name="title_decrypt">Dešifrovat</string>
+ <string name="title_authentication">Heslo</string>
+ <string name="title_create_key">Vytvořit klíč</string>
+ <string name="title_edit_key">Upravit klíč</string>
+ <string name="title_preferences">Nastavení</string>
+ <string name="title_api_registered_apps">Registrované aplikace</string>
+ <string name="title_key_server_preference">Nastavení serveru s klíči</string>
+ <string name="title_set_passphrase">Zadat heslo</string>
+ <string name="title_send_email">Poslat zprávu...</string>
+ <string name="title_import_keys">Importovat klíče</string>
+ <string name="title_export_key">Exportovat klíč</string>
+ <string name="title_export_keys">Exportovat klíče</string>
+ <string name="title_key_not_found">Klíč nenalezen</string>
+ <string name="title_send_key">Nahrát na server s klíči</string>
+ <string name="title_help">Nápověda</string>
+ <!--section-->
+ <string name="section_keys">Klíče</string>
+ <!--button-->
+ <string name="btn_sign">Podepsat</string>
+ <!--menu-->
+ <!--label-->
+ <string name="unknown_status"></string>
+ <!--choice-->
+ <!--key flags-->
+ <!--sentences-->
+ <!--errors
+ no punctuation, all lowercase,
+ they will be put after "error_message", e.g. "Error: file not found"-->
+ <!--progress dialogs, usually ending in '…'-->
+ <!--action strings-->
+ <!--key bit length selections-->
+ <!--compression-->
+ <!--Help-->
+ <!--Import-->
+ <!--Intent labels-->
+ <!--Remote API-->
+ <!--Share-->
+ <!--Key list-->
+ <!--Key view-->
+ <!--Navigation Drawer-->
+ <!--hints-->
+ <!--unsorted-->
+</resources>
diff --git a/OpenKeychain/src/main/res/values-de/strings.xml b/OpenKeychain/src/main/res/values-de/strings.xml
new file mode 100644
index 000000000..567b40f01
--- /dev/null
+++ b/OpenKeychain/src/main/res/values-de/strings.xml
@@ -0,0 +1,436 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<resources>
+ <!--title-->
+ <string name="title_manage_public_keys">Kontakte</string>
+ <string name="title_manage_secret_keys">Private Schlüssel</string>
+ <string name="title_select_recipients">Öffentlichen Schlüssel auswählen</string>
+ <string name="title_select_secret_key">Privaten Schlüssel auswählen</string>
+ <string name="title_encrypt">Verschlüsseln</string>
+ <string name="title_decrypt">Entschlüsseln</string>
+ <string name="title_authentication">Passwort</string>
+ <string name="title_create_key">Schlüssel erstellen</string>
+ <string name="title_edit_key">Schlüssel bearbeiten</string>
+ <string name="title_preferences">Einstellungen</string>
+ <string name="title_api_registered_apps">Registrierte Anwendungen</string>
+ <string name="title_key_server_preference">Schlüsselserver</string>
+ <string name="title_change_passphrase">Passphrase ändern</string>
+ <string name="title_set_passphrase">Passwort setzen</string>
+ <string name="title_send_email">E-Mail senden...</string>
+ <string name="title_send_file">Datei senden</string>
+ <string name="title_encrypt_to_file">In eine Datei verschlüsseln</string>
+ <string name="title_decrypt_to_file">In eine Datei entschlüsseln</string>
+ <string name="title_import_keys">Schlüssel importieren</string>
+ <string name="title_export_key">Schlüssel exportieren</string>
+ <string name="title_export_keys">Schlüssel exportieren</string>
+ <string name="title_key_not_found">Schlüssel nicht gefunden</string>
+ <string name="title_key_server_query">Schlüsselserver abfragen</string>
+ <string name="title_send_key">Auf Schlüsselserver hochladen</string>
+ <string name="title_unknown_signature_key">Unbekannter Signaturschlüssel</string>
+ <string name="title_certify_key">Schlüssel beglaubigen</string>
+ <string name="title_key_details">Schlüsseldetails</string>
+ <string name="title_help">Hilfe</string>
+ <!--section-->
+ <string name="section_user_ids">Benutzer-IDs</string>
+ <string name="section_keys">Schlüssel</string>
+ <string name="section_general">Allgemein</string>
+ <string name="section_defaults">Standardwerte</string>
+ <string name="section_advanced">Fortgeschrittene Einstellungen</string>
+ <string name="section_master_key">Hauptschlüssel</string>
+ <string name="section_master_user_id">Hauptbenutzer-ID</string>
+ <string name="section_actions">Aktionen</string>
+ <string name="section_certification_key">Mit diesem Schlüssel beglaubigen</string>
+ <string name="section_upload_key">Schlüssel hochladen</string>
+ <string name="section_key_server">Schlüsselserver</string>
+ <string name="section_encrypt_and_or_sign">Verschlüsseln und/oder Signieren</string>
+ <string name="section_decrypt_verify">Entschlüsseln und Verifizieren</string>
+ <!--button-->
+ <string name="btn_sign">Signieren</string>
+ <string name="btn_certify">Beglaubigen</string>
+ <string name="btn_decrypt">Entschlüsseln</string>
+ <string name="btn_decrypt_verify">Entschlüsseln und Verifizieren</string>
+ <string name="btn_select_encrypt_keys">Empfänger auswählen</string>
+ <string name="btn_encrypt_file">Datei verschlüsseln</string>
+ <string name="btn_save">Speichern</string>
+ <string name="btn_do_not_save">Abbrechen</string>
+ <string name="btn_delete">Löschen</string>
+ <string name="btn_no_date">Keine</string>
+ <string name="btn_okay">Okay</string>
+ <string name="btn_change_passphrase">Passwort ändern</string>
+ <string name="btn_set_passphrase">Neues Passwort setzen</string>
+ <string name="btn_search">Suchen</string>
+ <string name="btn_export_to_server">Auf Schlüsselserver hochladen</string>
+ <string name="btn_next">Weiter</string>
+ <string name="btn_back">Zurück</string>
+ <string name="btn_clipboard">Zwischenablage</string>
+ <string name="btn_share">Teilen mit…</string>
+ <string name="btn_lookup_key">Schlüssel nachschlagen</string>
+ <string name="btn_encryption_advanced_settings_show">Erweiterte Einstellungen anzeigen</string>
+ <string name="btn_encryption_advanced_settings_hide">Erweiterte Einstellungen verbergen</string>
+ <!--menu-->
+ <string name="menu_preferences">Einstellungen</string>
+ <string name="menu_help">Hilfe</string>
+ <string name="menu_import_from_file">Datei</string>
+ <string name="menu_import_from_qr_code">QR-Code</string>
+ <string name="menu_import">Importieren</string>
+ <string name="menu_import_from_nfc">NFC</string>
+ <string name="menu_export_key">In Datei exportieren</string>
+ <string name="menu_delete_key">Schlüssel löschen</string>
+ <string name="menu_create_key">Schlüssel erstellen</string>
+ <string name="menu_create_key_expert">Schlüssel erstellen (Experte)</string>
+ <string name="menu_search">Suchen</string>
+ <string name="menu_import_from_key_server">Schlüsselserver</string>
+ <string name="menu_key_server">Schlüsselserver…</string>
+ <string name="menu_update_key">Von einem Schlüsselserver aktualisieren</string>
+ <string name="menu_export_key_to_server">Auf Schlüsselserver hochladen</string>
+ <string name="menu_share">Teilen…</string>
+ <string name="menu_share_title_fingerprint">Teile Fingerabdruck…</string>
+ <string name="menu_share_title">Teile gesamten Schlüssel…</string>
+ <string name="menu_share_default_fingerprint">mit…</string>
+ <string name="menu_share_default">mit…</string>
+ <string name="menu_share_qr_code">mit QR-Code</string>
+ <string name="menu_share_qr_code_fingerprint">mit QR-Code</string>
+ <string name="menu_share_nfc">mit NFC</string>
+ <string name="menu_copy_to_clipboard">In die Zwischenablage kopieren</string>
+ <string name="menu_sign_key">Schlüssel signieren</string>
+ <string name="menu_beam_preferences">Beam-Einstellungen</string>
+ <string name="menu_key_edit_cancel">Abbrechen</string>
+ <string name="menu_encrypt_to">Verschlüsseln nach…</string>
+ <string name="menu_select_all">Alles auswählen</string>
+ <string name="menu_add_keys">Schlüssel hinzufügen</string>
+ <string name="menu_export_all_keys">Alle Schlüssel exportieren</string>
+ <!--label-->
+ <string name="label_sign">Signieren</string>
+ <string name="label_message">Nachricht</string>
+ <string name="label_file">Datei</string>
+ <string name="label_no_passphrase">Kein Passwort</string>
+ <string name="label_passphrase">Passwort</string>
+ <string name="label_passphrase_again">Wiederholen</string>
+ <string name="label_algorithm">Algorithmus</string>
+ <string name="label_ascii_armor">ASCII-Armor</string>
+ <string name="label_select_public_keys">Empfänger</string>
+ <string name="label_delete_after_encryption">Nach Verschlüsselung löschen</string>
+ <string name="label_delete_after_decryption">Nach Entschlüsselung löschen</string>
+ <string name="label_share_after_encryption">Nach dem Verschlüsseln teilen</string>
+ <string name="label_encryption_algorithm">Verschlüsselungsalgorithmus</string>
+ <string name="label_hash_algorithm">Hash-Algorithmus</string>
+ <string name="label_asymmetric">mit Öffentlichem Schlüssel</string>
+ <string name="label_symmetric">mit Passwort</string>
+ <string name="label_passphrase_cache_ttl">Passwort-Cache</string>
+ <string name="label_message_compression">Nachrichten-Komprimierung</string>
+ <string name="label_file_compression">Datei-Komprimierung</string>
+ <string name="label_force_v3_signature">Alte OpenPGPv3 Unterschriften erzwingen</string>
+ <string name="label_key_servers">Schlüsselserver</string>
+ <string name="label_key_id">Schlüssel-ID</string>
+ <string name="label_creation">Erstellungsdatum</string>
+ <string name="label_expiry">Ablaufdatum</string>
+ <string name="label_usage">Verwendungszweck</string>
+ <string name="label_key_size">Schlüssellänge</string>
+ <string name="label_main_user_id">Hauptbenutzer-ID</string>
+ <string name="label_name">Name</string>
+ <string name="label_comment">Kommentar</string>
+ <string name="label_email">E-Mail</string>
+ <string name="label_send_key">Schlüssel nach Beglaubigung auf ausgewählten Schlüsselserver hochladen</string>
+ <string name="label_fingerprint">Fingerabdruck</string>
+ <string name="select_keys_button_default">Auswählen</string>
+ <string name="expiry_date_dialog_title">Ablaufdatum festsetzen</string>
+ <plurals name="select_keys_button">
+ <item quantity="one">%d ausgewählt</item>
+ <item quantity="other">%d ausgewählt</item>
+ </plurals>
+ <string name="user_id_no_name">&lt;kein Name&gt;</string>
+ <string name="none">&lt;keine&gt;</string>
+ <string name="no_key">&lt;kein Schlüssel&gt;</string>
+ <string name="no_email">&lt;Keine E-Mail&gt;</string>
+ <string name="unknown_status"></string>
+ <string name="can_encrypt">kann verschlüsseln</string>
+ <string name="can_sign">kann signieren</string>
+ <string name="expired">abgelaufen</string>
+ <string name="revoked">zurückgezogen</string>
+ <string name="user_id">Benutzer ID</string>
+ <plurals name="n_contacts">
+ <item quantity="one">1 Kontakt</item>
+ <item quantity="other">%d Kontakte</item>
+ </plurals>
+ <plurals name="n_key_servers">
+ <item quantity="one">%d Schlüsselserver</item>
+ <item quantity="other">%d Schlüsselserver</item>
+ </plurals>
+ <string name="fingerprint">Fingerabdruck:</string>
+ <string name="secret_key">Privater Schlüssel:</string>
+ <!--choice-->
+ <string name="choice_none">Keine</string>
+ <string name="choice_15secs">15 s</string>
+ <string name="choice_1min">1 min</string>
+ <string name="choice_3mins">3 min</string>
+ <string name="choice_5mins">5 min</string>
+ <string name="choice_10mins">10 min</string>
+ <string name="choice_20mins">20 min</string>
+ <string name="choice_40mins">40 min</string>
+ <string name="choice_1hour">1 Stunde</string>
+ <string name="choice_2hours">2 Stunden</string>
+ <string name="choice_4hours">4 Stunden</string>
+ <string name="choice_8hours">8 Stunden</string>
+ <string name="choice_forever">für immer</string>
+ <string name="dsa">DSA</string>
+ <string name="elgamal">ElGamal</string>
+ <string name="rsa">RSA</string>
+ <string name="filemanager_title_open">Öffnen...</string>
+ <string name="warning">Warnung</string>
+ <string name="error">Fehler</string>
+ <string name="error_message">Fehler: %s</string>
+ <!--key flags-->
+ <string name="flag_certify">Beglaubigen</string>
+ <string name="flag_sign">Signieren</string>
+ <string name="flag_encrypt">Verschlüsseln</string>
+ <string name="flag_authenticate">Authentifizieren</string>
+ <!--sentences-->
+ <string name="wrong_passphrase">Falsches Passwort.</string>
+ <string name="using_clipboard_content">Verwende Inhalt der Zwischenablage.</string>
+ <string name="set_a_passphrase">Zuerst ein Passwort setzen.</string>
+ <string name="no_filemanager_installed">Kein passender Dateimanager installiert.</string>
+ <string name="passphrases_do_not_match">Die Passwörter stimmten nicht überein.</string>
+ <string name="passphrase_for_symmetric_encryption">Symmetrische Verschlüsselung.</string>
+ <string name="passphrase_for">Passwort für \'%s\' eingeben</string>
+ <string name="file_delete_confirmation">%s\nwirklich löschen?</string>
+ <string name="file_delete_successful">Erfolgreich gelöscht.</string>
+ <string name="no_file_selected">Zuerst eine Datei auswählen.</string>
+ <string name="enter_passphrase_twice">Passwort zweimal eingeben.</string>
+ <string name="select_encryption_key">Mindestens einen Schlüssel zum verschlüsseln auswählen.</string>
+ <string name="select_encryption_or_signature_key">Mindestens einen Schlüssel zum Verschlüsseln oder einen zum Signieren auswählen.</string>
+ <string name="specify_file_to_encrypt_to">Bitte angeben, in welche Datei verschlüsselt werden soll.\nWARNUNG: Datei wird überschrieben, wenn sie bereits existiert. </string>
+ <string name="specify_file_to_decrypt_to">Bitte angeben, in welche Datei entschlüsselt werden soll.\nWARNUNG: Datei wird überschrieben, wenn sie bereits existiert. </string>
+ <string name="specify_file_to_export_to">Bitte angeben, in welche Datei exportiert werden soll.\nWARNUNG: Datei wird überschrieben, wenn sie bereits existiert. </string>
+ <string name="specify_file_to_export_secret_keys_to">Bitte angeben, in welche Datei exportiert werden soll.\nWARNUNG! Sie exportieren GEHEIME Schlüssel.\nWARNUNG! Datei wird überschrieben, wenn sie bereits existiert. </string>
+ <string name="key_deletion_confirmation">Soll der Schlüssel \'%s\' wirklich gelöscht werden?\nDies kann nicht rückgängig gemacht werden! </string>
+ <string name="key_deletion_confirmation_multi">Möchtest du wirklich alle ausgewählten Schlüssel löschen?\nDies kann nicht rückgängig gemacht werden!</string>
+ <string name="secret_key_deletion_confirmation">Soll der PRIVATE Schlüssel \'%s\' wirklich gelöscht werden?\nDies kann nicht rückgängig gemacht werden!</string>
+ <string name="public_key_deletetion_confirmation">Soll der öffentliche Schlüssel \'%s\' wirklich gelöscht werden?\nDies kann nicht rückgängig gemacht werden! </string>
+ <string name="also_export_secret_keys">Private Schlüssel auch exportieren</string>
+ <plurals name="keys_added_and_updated_1">
+ <item quantity="one">%d Schlüssel erfolgreich hinzugefügt</item>
+ <item quantity="other">%d Schlüssel erfolgreich hinzugefügt</item>
+ </plurals>
+ <plurals name="keys_added_and_updated_2">
+ <item quantity="one">und %d Schlüssel erfolgreich aktualisiert.</item>
+ <item quantity="other">und %d Schlüssel erfolgreich aktualisiert.</item>
+ </plurals>
+ <plurals name="keys_added">
+ <item quantity="one">%d Schlüssel erfolgreich hinzugefügt.</item>
+ <item quantity="other">%d Schlüssel erfolgreich hinzugefügt.</item>
+ </plurals>
+ <plurals name="keys_updated">
+ <item quantity="one">%d Schlüssel erfolgreich aktualisiert.</item>
+ <item quantity="other">%d Schlüssel erfolgreich aktualisiert.</item>
+ </plurals>
+ <string name="no_keys_added_or_updated">Keine Schlüssel hinzugefügt oder aktualisiert.</string>
+ <string name="key_exported">1 Schlüssel erfolgreich exportiert.</string>
+ <string name="keys_exported">%d Schlüssel erfolgreich exportiert.</string>
+ <string name="no_keys_exported">Keine Schlüssel exportiert.</string>
+ <string name="key_creation_el_gamal_info">Beachte: nur Unterschlüssel unterstützen ElGamal. Für ElGamal wird die am nächsten liegende Schlüssellänge von 1536, 2048, 3072, 4096 oder 8192 verwendet.</string>
+ <string name="key_creation_weak_rsa_info">Beachte: RSA-Schlüssel mit einer Schlüssellänge von 1024-Bits oder weniger werden als unsicher angesehen und können daher nicht für neue Schlüssel erstellt werden.</string>
+ <string name="key_not_found">Schlüssel %08X konnte nicht gefunden werden.</string>
+ <plurals name="keys_found">
+ <item quantity="one">%d Schlüssel gefunden.</item>
+ <item quantity="other">%d Schlüssel gefunden.</item>
+ </plurals>
+ <string name="unknown_signature">Unbekannte Signatur. Benutze den Button um den fehlenden Schlüssel nachzuschlagen.</string>
+ <plurals name="bad_keys_encountered">
+ <item quantity="one">%d schlechter privater Schlüssel ignoriert. Evtl. wurde er mit folgender Option exportiert:\n --export-secret-subkeys\nUnbedingt mit der Option \n --export-secret-keys\nexportieren.</item>
+ <item quantity="other">%d schlechte private Schlüssel ignoriert. Evtl. wurden sie mit folgender Option exportiert:\n --export-secret-subkeys\nUnbedingt mit der Option \n --export-secret-keys\nexportieren.</item>
+ </plurals>
+ <string name="key_send_success">Schlüssel wurde erfolgreich hochgeladen.</string>
+ <string name="key_sign_success">Schlüssel erfolgreich signiert</string>
+ <string name="list_empty">Diese Liste ist leer!</string>
+ <string name="nfc_successfull">Schlüssel erfolgreich mit NFC Beam gesendet!</string>
+ <string name="key_copied_to_clipboard">Schlüssel wurde in die Zwischenablage kopiert!</string>
+ <string name="key_has_already_been_signed">Dieser Schlüssel wurde schon signiert!</string>
+ <string name="select_key_to_sign">Bitte wähle einen Signaturschlüssel!</string>
+ <string name="key_too_big_for_sharing">Schlüssel ist zu groß um so geteilt zu werden!</string>
+ <!--errors
+ no punctuation, all lowercase,
+ they will be put after "error_message", e.g. "Error: file not found"-->
+ <string name="error_file_delete_failed">Löschen von \'%s\' ist fehlgeschlagen</string>
+ <string name="error_file_not_found">Datei nicht gefunden</string>
+ <string name="error_no_secret_key_found">kein geeigneter privater Schlüssel gefunden</string>
+ <string name="error_no_known_encryption_found">keine bekannte Art von Verschlüsselung gefunden</string>
+ <string name="error_external_storage_not_ready">Externes Laufwerk ist nicht bereit</string>
+ <string name="error_invalid_email">ungültige Email \'%s\'</string>
+ <string name="error_key_size_minimum512bit">Schlüssel muss mindestens 512 Bit lang sein</string>
+ <string name="error_master_key_must_not_be_el_gamal">Der Hauptschlüssel kann kein ElGamal Schlüssel sein</string>
+ <string name="error_unknown_algorithm_choice">Unbekannte Auswahl für Algorithmus</string>
+ <string name="error_user_id_needs_a_name">ein Name muss angegeben werden</string>
+ <string name="error_user_id_no_email">keine E-Mail gefunden</string>
+ <string name="error_user_id_needs_an_email_address">eine E-Mail-Adresse muss angegeben werden</string>
+ <string name="error_key_needs_a_user_id">Mindestens eine Benutzer-ID wird benötigt</string>
+ <string name="error_main_user_id_must_not_be_empty">Hauptbenutzer-ID darf nicht leer sein</string>
+ <string name="error_key_needs_master_key">Mindestens ein Hauptschlüssel wird benötigt</string>
+ <string name="error_no_encryption_keys_or_passphrase">Keine Schlüssel zur Verschlüsselung gegeben bzw. kein symmetrisches Passwort festgelegt</string>
+ <string name="error_signature_failed">Signieren fehlgeschlagen</string>
+ <string name="error_no_signature_passphrase">kein Passwort angegeben</string>
+ <string name="error_no_signature_key">kein Signaturschlüssel angegeben</string>
+ <string name="error_invalid_data">Verschlüsselte Daten nicht gültig</string>
+ <string name="error_corrupt_data">beschädigte Daten</string>
+ <string name="error_integrity_check_failed">Integritätscheck fehlgeschlagen! Die Daten wurden modifiziert!</string>
+ <string name="error_no_symmetric_encryption_packet">Paket mit symmetrischer Verschlüsselung konnte nicht gefunden werden</string>
+ <string name="error_wrong_passphrase">falsches Passwort</string>
+ <string name="error_saving_keys">Es trat ein Fehler beim Speichern einiger Schlüssel auf</string>
+ <string name="error_could_not_extract_private_key">Privater Schlüssel konnte nicht extrahiert werden</string>
+ <string name="error_only_files_are_supported">Binäre Daten ohne Datei im Dateisystem werden nicht unterstützt. Dies wird nur durch ACTION_ENCRYPT_STREAM_AND_RETURN unterstützt.</string>
+ <string name="error_jelly_bean_needed">Android 4.1 alias Jelly Bean wird benötigt um Androids NFC-Beam nutzen zu können!</string>
+ <string name="error_nfc_needed">NFC steht auf diesem Gerät nicht zur Verfügung!</string>
+ <string name="error_nothing_import">Nichts zu importieren!</string>
+ <string name="error_expiry_must_come_after_creation">Ablaufdatum muss später sein als das Erstellungsdatum</string>
+ <string name="error_can_not_delete_contact">Sie können diesen Kontakt nicht löschen, denn es ist ihr eigener.</string>
+ <string name="error_can_not_delete_contacts">Sie können folgende Kontakte nicht löschen, denn sie gehören Ihnen selbst:\n%s</string>
+ <string name="error_keyserver_insufficient_query">Unzureichende Serveranfrage</string>
+ <string name="error_keyserver_query">Keyserveranfrage fehlgeschlagen</string>
+ <string name="error_keyserver_too_many_responses">Zu viele Antworten</string>
+ <string name="error_import_file_no_content">Datei ist leer</string>
+ <string name="error_generic_report_bug">Ein allgemeiner Fehler trat auf, bitte schreiben Sie einen neuen Bugreport für OpenKeychain.</string>
+ <plurals name="error_can_not_delete_info">
+ <item quantity="one">Bitte lösche ihn unter \'Meine Schlüssel\'!</item>
+ <item quantity="other">Bitte lösche sie unter \'Meine Schlüssel\'!</item>
+ </plurals>
+ <plurals name="error_import_non_pgp_part">
+ <item quantity="one">Ein Teil der geladenen Datei ist ein gültiges OpenPGP Objekt aber kein OpenPGP Schlüssel</item>
+ <item quantity="other">Teile der geladenen Dateien sind gültige OpenPGP Objekte aber keine OpenPGP Schlüssel</item>
+ </plurals>
+ <!--progress dialogs, usually ending in '…'-->
+ <string name="progress_done">Erledigt</string>
+ <string name="progress_cancel">Abbrechen</string>
+ <string name="progress_saving">speichern…</string>
+ <string name="progress_importing">importieren…</string>
+ <string name="progress_exporting">exportieren…</string>
+ <string name="progress_building_key">erstelle Schlüssel…</string>
+ <string name="progress_preparing_master_key">Hauptschlüssel wird vorbereitet…</string>
+ <string name="progress_certifying_master_key">Hauptschlüssel wird beglaubigt…</string>
+ <string name="progress_building_master_key">erstelle Hauptring…</string>
+ <string name="progress_adding_sub_keys">füge Unterschlüssel hinzu…</string>
+ <string name="progress_saving_key_ring">Schlüssel wird gespeichert…</string>
+ <plurals name="progress_exporting_key">
+ <item quantity="one">Schlüssel wird exportiert…</item>
+ <item quantity="other">Schlüssel werden exportiert…</item>
+ </plurals>
+ <plurals name="progress_generating">
+ <item quantity="one">erstelle Schlüssel, das kann bis zu 3 Minuten dauern…</item>
+ <item quantity="other">erstelle Schlüssel, das kann bis zu 3 Minuten dauern…</item>
+ </plurals>
+ <string name="progress_extracting_signature_key">extrahiere Signaturschlüssel…</string>
+ <string name="progress_extracting_key">extrahiere Schlüssel…</string>
+ <string name="progress_preparing_streams">Datenstrom wird vorbereitet…</string>
+ <string name="progress_encrypting">Daten werden verschlüsselt…</string>
+ <string name="progress_decrypting">Daten werden entschlüsselt…</string>
+ <string name="progress_preparing_signature">Signatur wird vorbereitet…</string>
+ <string name="progress_generating_signature">Signatur wird erstellt…</string>
+ <string name="progress_processing_signature">Signatur wird verarbeitet…</string>
+ <string name="progress_verifying_signature">Signatur wird verifiziert…</string>
+ <string name="progress_signing">signiere…</string>
+ <string name="progress_reading_data">Daten werden gelesen…</string>
+ <string name="progress_finding_key">Schlüssel wird gesucht…</string>
+ <string name="progress_decompressing_data">Daten werden entpackt…</string>
+ <string name="progress_verifying_integrity">Integrität wird überprüft…</string>
+ <string name="progress_deleting_securely">\'%s\' wird sicher gelöscht…</string>
+ <string name="progress_querying">Anfrage wird gestellt…</string>
+ <!--action strings-->
+ <string name="hint_public_keys">Öffentliche Schlüssel suchen</string>
+ <string name="hint_secret_keys">Private Schlüssel suchen</string>
+ <string name="action_share_key_with">Teile Schlüssel über…</string>
+ <!--key bit length selections-->
+ <string name="key_size_512">512</string>
+ <string name="key_size_1024">1024</string>
+ <string name="key_size_2048">2048</string>
+ <string name="key_size_4096">4096</string>
+ <!--compression-->
+ <string name="compression_fast">schnell</string>
+ <string name="compression_very_slow">sehr langsam</string>
+ <!--Help-->
+ <string name="help_tab_start">Start</string>
+ <string name="help_tab_faq">FAQ</string>
+ <string name="help_tab_nfc_beam">NFC-Beam</string>
+ <string name="help_tab_changelog">Changelog</string>
+ <string name="help_tab_about">Über</string>
+ <string name="help_about_version">Version:</string>
+ <!--Import-->
+ <string name="import_import">Ausgewählte Schlüssel importieren</string>
+ <string name="import_sign_and_upload">Ausgewählte Schlüssel importieren, signieren und hochladen</string>
+ <string name="import_from_clipboard">Zwischenablage</string>
+ <plurals name="import_qr_code_missing">
+ <item quantity="one">QR-Codes mit folgender ID fehlt: %s</item>
+ <item quantity="other">QR-Codes mit folgenden IDs fehlen: %s</item>
+ </plurals>
+ <string name="import_qr_code_start_with_one">Bitte fange mit QR-Code der ID 1 an</string>
+ <string name="import_qr_code_wrong">Falsch formatierter QR-Code! Bitte erneut versuchen!</string>
+ <string name="import_qr_code_finished">QR-Code wurde erfolgreich eingescannt!</string>
+ <string name="import_qr_code_too_short_fingerprint">Der Fingerabdruck ist zu kurz (&lt; 16 Zeichen)</string>
+ <string name="import_qr_scan_button">Qr-Code mittels \'Barcode Scanner\' einscannen</string>
+ <string name="import_nfc_text">Um Schlüssel über NFC zu erhalten muss das Gerät entsperrt sein.</string>
+ <string name="import_nfc_help_button">Hilfe</string>
+ <string name="import_clipboard_button">Füge den Schlüssel aus der Zwischenablage ein</string>
+ <!--Intent labels-->
+ <string name="intent_decrypt_file">Datei mit OpenKeychain entschlüsseln</string>
+ <string name="intent_import_key">Schlüssel mit OpenKeychain importieren</string>
+ <string name="intent_send_encrypt">Mit OpenKeychain verschlüsseln</string>
+ <string name="intent_send_decrypt">Mit OpenKeychain entschlüsseln</string>
+ <!--Remote API-->
+ <string name="api_no_apps">Keine registrierten Anwendungen vorhanden!\n\nAnwendungen von Dritten können Zugriff auf OpenKeychain erbitten. Nachdem Zugriff gewährt wurde, werden diese hier aufgelistet.</string>
+ <string name="api_settings_show_info">Erweiterte Informationen anzeigen</string>
+ <string name="api_settings_hide_info">Erweiterte Informationen ausblenden</string>
+ <string name="api_settings_show_advanced">Erweiterte Einstellungen anzeigen</string>
+ <string name="api_settings_hide_advanced">Erweiterte Einstellungen ausblenden</string>
+ <string name="api_settings_no_key">Kein Schlüssel ausgewählt</string>
+ <string name="api_settings_select_key">Schlüssel auswählen</string>
+ <string name="api_settings_save">Speichern</string>
+ <string name="api_settings_cancel">Abbrechen</string>
+ <string name="api_settings_revoke">Zugriff widerufen</string>
+ <string name="api_settings_package_name">Paketname</string>
+ <string name="api_settings_package_signature">SHA-256 der Paketsignatur</string>
+ <string name="api_settings_accounts">Konten</string>
+ <string name="api_register_text">Folgende Anwendung beantragt Zugriff zur API von OpenKeychain.\nZugriff erlauben?\n\nVORSICHT: Sollten Sie nicht wissen, warum dieses Fenster erscheint, sollten Sie den Zugriff nicht gewähren! Sie können Zugriffe später über das Menü \'Registrierte Anwendungen\' widerrufen.</string>
+ <string name="api_register_allow">Zugriff erlauben</string>
+ <string name="api_register_disallow">Zugriff verbieten</string>
+ <string name="api_register_error_select_key">Bitte einen Schlüssel auswählen!</string>
+ <string name="api_select_pub_keys_missing_text">Für diese Benutzer-IDs wurden keine öffentlichen Schlüssel gefunden:</string>
+ <string name="api_select_pub_keys_dublicates_text">Für diese Benutzer-IDs existieren mehrere öffentliche Schlüssel:</string>
+ <string name="api_select_pub_keys_text">Bitte die Liste der Empfänger überprüfen!</string>
+ <string name="api_error_wrong_signature">Signaturüberprüfung fehlgeschlagen! Haben Sie diese App von einer anderen Quelle installiert? Wenn Sie eine Attacke ausschliessen können, sollten Sie die Registrierung der App in OpenKeychain widerrufen und die App erneut registrieren.</string>
+ <!--Share-->
+ <string name="share_qr_code_dialog_title">Über QR Code teilen</string>
+ <string name="share_qr_code_dialog_start">Mit \'Weiter\' durch alle QR-Codes gehen und diese nacheinander scannen.</string>
+ <string name="share_qr_code_dialog_fingerprint_text">Fingerabdruck:</string>
+ <string name="share_qr_code_dialog_progress">QR-Code mit ID %1$d von %2$d</string>
+ <string name="share_nfc_dialog">Über NFC teilen</string>
+ <!--Key list-->
+ <plurals name="key_list_selected_keys">
+ <item quantity="one">1 Schlüssel ausgewählt.</item>
+ <item quantity="other">%d Schlüssel ausgewählt.</item>
+ </plurals>
+ <string name="key_list_empty_text1">Keine Schlüssel verfügbar…</string>
+ <string name="key_list_empty_text2">Du kannst anfangen OpenPGP Keychain zu benutzen indem du</string>
+ <string name="key_list_empty_text3">oder</string>
+ <string name="key_list_empty_button_create">deinen eigenen Schlüssel erstellst</string>
+ <string name="key_list_empty_button_import">existierende Schlüssel importierst.</string>
+ <!--Key view-->
+ <string name="key_view_action_edit">Diesen Schlüssel bearbeiten</string>
+ <string name="key_view_action_encrypt">Für diesen Kontakt verschlüsseln</string>
+ <string name="key_view_action_certify">Schlüssel dieses Kontakts beglaubigen</string>
+ <string name="key_view_tab_main">Info</string>
+ <string name="key_view_tab_certs">Zertifikationen</string>
+ <!--Navigation Drawer-->
+ <string name="nav_contacts">Schlüssel</string>
+ <string name="nav_encrypt">Signieren und Verschlüsseln</string>
+ <string name="nav_decrypt">Entschlüsseln und Verifizieren</string>
+ <string name="nav_import">Schlüssel Importieren</string>
+ <string name="nav_secret_keys">Meine Schlüssel</string>
+ <string name="nav_apps">Registrierte Anwendungen</string>
+ <string name="drawer_open">Menu öffnen</string>
+ <string name="drawer_close">Menu schließen</string>
+ <string name="edit">Bearbeiten</string>
+ <string name="my_keys">Meine Schlüssel</string>
+ <string name="label_secret_key">Geheime Schlüssel</string>
+ <string name="secret_key_yes">verfügbar</string>
+ <string name="secret_key_no">nicht verfügbar</string>
+ <!--hints-->
+ <!--unsorted-->
+ <string name="section_uids_to_sign">Benutzer-IDs, die beglaubigt werden sollen</string>
+ <string name="progress_re_adding_certs">Wiederhinzufügen der Zertifikate</string>
+</resources>
diff --git a/OpenKeychain/src/main/res/values-el/strings.xml b/OpenKeychain/src/main/res/values-el/strings.xml
new file mode 100644
index 000000000..73a736766
--- /dev/null
+++ b/OpenKeychain/src/main/res/values-el/strings.xml
@@ -0,0 +1,52 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<resources>
+ <!--title-->
+ <string name="title_select_recipients">Επιλογή Δημόσιου Κλειδιού</string>
+ <string name="title_select_secret_key">Επιλογή Ιδιωτικού Κλειδιού</string>
+ <string name="title_authentication">Κωδικός</string>
+ <string name="title_create_key">Δημιουργία Κλειδιού</string>
+ <string name="title_edit_key">Επεξεργασία Κλειδιού</string>
+ <string name="title_preferences">Επιλογές</string>
+ <!--section-->
+ <!--button-->
+ <string name="btn_sign">Υπόγραψε</string>
+ <string name="btn_save">Αποθήκευση</string>
+ <string name="btn_do_not_save">Ακύρωση</string>
+ <string name="btn_delete">Διαγραφή</string>
+ <string name="btn_no_date">Κανένα</string>
+ <string name="btn_okay">ΟΚ</string>
+ <!--menu-->
+ <string name="menu_delete_key">Διαγραφής κλειδιού</string>
+ <string name="menu_create_key">Δημιουργίας κλειδιού</string>
+ <!--label-->
+ <string name="label_sign">Υπόγραψε</string>
+ <string name="label_message">Μήνυμα</string>
+ <string name="label_file">Αρχείο</string>
+ <string name="label_passphrase">Κωδικός</string>
+ <string name="label_passphrase_again">Ξανά</string>
+ <string name="label_algorithm">Αλγόριθμος</string>
+ <string name="label_encryption_algorithm">Αλγόριθμος κρυπτογράφησης</string>
+ <string name="label_key_size">Μέγεθος κλειδιού</string>
+ <string name="label_email">Ηλεκτρονικό ταχυδρομίο</string>
+ <string name="unknown_status"></string>
+ <!--choice-->
+ <!--key flags-->
+ <!--sentences-->
+ <!--errors
+ no punctuation, all lowercase,
+ they will be put after "error_message", e.g. "Error: file not found"-->
+ <!--progress dialogs, usually ending in '…'-->
+ <!--action strings-->
+ <!--key bit length selections-->
+ <!--compression-->
+ <!--Help-->
+ <!--Import-->
+ <!--Intent labels-->
+ <!--Remote API-->
+ <!--Share-->
+ <!--Key list-->
+ <!--Key view-->
+ <!--Navigation Drawer-->
+ <!--hints-->
+ <!--unsorted-->
+</resources>
diff --git a/OpenKeychain/src/main/res/values-es-rCO/strings.xml b/OpenKeychain/src/main/res/values-es-rCO/strings.xml
new file mode 100644
index 000000000..0def5eba7
--- /dev/null
+++ b/OpenKeychain/src/main/res/values-es-rCO/strings.xml
@@ -0,0 +1,98 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<resources>
+ <!--title-->
+ <string name="title_select_recipients">Escoger llave pública</string>
+ <string name="title_select_secret_key">Escoger llave privada</string>
+ <string name="title_encrypt">Cifrar</string>
+ <string name="title_decrypt">Descifrar</string>
+ <string name="title_authentication">Contraseña</string>
+ <string name="title_create_key">Crear clave</string>
+ <string name="title_edit_key">Editar clave</string>
+ <string name="title_preferences">Preferencias</string>
+ <string name="title_api_registered_apps">Aplicaciones registradas</string>
+ <string name="title_set_passphrase">Establecer contraseña</string>
+ <string name="title_send_email">Enviar correo electrónico...</string>
+ <string name="title_encrypt_to_file">Cifrar a archivo</string>
+ <string name="title_decrypt_to_file">Descifrar a archivo</string>
+ <string name="title_import_keys">Importar claves</string>
+ <string name="title_export_key">Exportar clave</string>
+ <string name="title_export_keys">Exportar claves</string>
+ <string name="title_key_not_found">Clave no encontrada</string>
+ <string name="title_unknown_signature_key">Clave de firma desconocida</string>
+ <string name="title_help">Ayuda</string>
+ <!--section-->
+ <string name="section_user_ids">IDs de usuario</string>
+ <string name="section_keys">Claves</string>
+ <string name="section_general">General</string>
+ <string name="section_defaults">Por defecto</string>
+ <string name="section_advanced">Avanzado</string>
+ <!--button-->
+ <string name="btn_sign">Firmar</string>
+ <string name="btn_decrypt">Descifrar</string>
+ <string name="btn_select_encrypt_keys">Escoger destinatarios</string>
+ <string name="btn_encrypt_file">Cifrar archivo</string>
+ <string name="btn_save">Guardar</string>
+ <string name="btn_do_not_save">Cancelar</string>
+ <string name="btn_delete">Borrar</string>
+ <string name="btn_no_date">Ninguno</string>
+ <string name="btn_okay">Ok</string>
+ <string name="btn_search">Buscar</string>
+ <string name="btn_next">Siguiente</string>
+ <string name="btn_back">Atrás</string>
+ <!--menu-->
+ <string name="menu_preferences">Ajustes</string>
+ <string name="menu_import_from_file">Importar desde archivo</string>
+ <string name="menu_import_from_qr_code">Importar desde código QR</string>
+ <string name="menu_import_from_nfc">Importar desde NFC</string>
+ <string name="menu_export_key">Exportar a archivo</string>
+ <string name="menu_delete_key">Borrar clave</string>
+ <string name="menu_create_key">Crear clave</string>
+ <string name="menu_create_key_expert">Crear clave (experto)</string>
+ <string name="menu_search">Buscar</string>
+ <string name="menu_sign_key">Clave de firma</string>
+ <!--label-->
+ <string name="label_sign">Firmar</string>
+ <string name="label_message">Mensaje</string>
+ <string name="label_file">Archivo</string>
+ <string name="label_no_passphrase">Sin contraseña</string>
+ <string name="label_passphrase">Contraseña</string>
+ <string name="label_passphrase_again">De nuevo</string>
+ <string name="label_algorithm">Algoritmo</string>
+ <string name="label_ascii_armor">Armadura ASCII</string>
+ <string name="label_delete_after_encryption">Borrar después de cifrar</string>
+ <string name="label_delete_after_decryption">Borrar después de descifrar</string>
+ <string name="label_encryption_algorithm">Algoritmo de cifrado</string>
+ <string name="label_hash_algorithm">Algoritmo de Hash</string>
+ <string name="label_message_compression">Compresión de mensaje</string>
+ <string name="label_file_compression">Compresión de archivo</string>
+ <string name="label_key_id">ID de clave</string>
+ <string name="label_creation">Creación</string>
+ <string name="label_expiry">Expiración</string>
+ <string name="label_usage">Uso</string>
+ <string name="label_key_size">Tamaño de la clave</string>
+ <string name="label_main_user_id">ID de usuario principal</string>
+ <string name="label_name">Nombre</string>
+ <string name="label_comment">Comentario</string>
+ <string name="label_email">Correo electrónico</string>
+ <string name="unknown_status"></string>
+ <!--choice-->
+ <!--key flags-->
+ <!--sentences-->
+ <!--errors
+ no punctuation, all lowercase,
+ they will be put after "error_message", e.g. "Error: file not found"-->
+ <!--progress dialogs, usually ending in '…'-->
+ <!--action strings-->
+ <!--key bit length selections-->
+ <!--compression-->
+ <!--Help-->
+ <!--Import-->
+ <!--Intent labels-->
+ <!--Remote API-->
+ <!--Share-->
+ <!--Key list-->
+ <!--Key view-->
+ <!--Navigation Drawer-->
+ <!--hints-->
+ <!--unsorted-->
+</resources>
diff --git a/OpenKeychain/src/main/res/values-es/strings.xml b/OpenKeychain/src/main/res/values-es/strings.xml
new file mode 100644
index 000000000..a3394f23a
--- /dev/null
+++ b/OpenKeychain/src/main/res/values-es/strings.xml
@@ -0,0 +1,451 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<resources>
+ <!--title-->
+ <string name="title_manage_public_keys">Contactos</string>
+ <string name="title_manage_secret_keys">Claves secretas</string>
+ <string name="title_select_recipients">Seleccionar la clave pública</string>
+ <string name="title_select_secret_key">Seleccionar la clave secreta</string>
+ <string name="title_encrypt">Cifrar</string>
+ <string name="title_decrypt">Descifrar</string>
+ <string name="title_authentication">Frase de contraseña</string>
+ <string name="title_create_key">Crear clave</string>
+ <string name="title_edit_key"> Editar clave</string>
+ <string name="title_preferences"> Preferencias</string>
+ <string name="title_api_registered_apps">Aplicaciones registradas</string>
+ <string name="title_key_server_preference">Prioridad del servidor de claves</string>
+ <string name="title_change_passphrase">Cambiar frase de contraseña</string>
+ <string name="title_set_passphrase">Establecer frase de contraseña</string>
+ <string name="title_send_email">Enviar email...</string>
+ <string name="title_send_file">Enviar archivo...</string>
+ <string name="title_encrypt_to_file">Cifrar hacia archivo</string>
+ <string name="title_decrypt_to_file">Descifrar hacia archivo</string>
+ <string name="title_import_keys">Importar claves</string>
+ <string name="title_export_key">Exportar clave</string>
+ <string name="title_export_keys">Exportar claves</string>
+ <string name="title_key_not_found">Clave no encontrada</string>
+ <string name="title_key_server_query">Consultar servidor de claves</string>
+ <string name="title_send_key">Cargar al servidor de claves</string>
+ <string name="title_unknown_signature_key">Clave de firma desconocida</string>
+ <string name="title_certify_key">Certificar clave</string>
+ <string name="title_key_details">Detalles de la clave</string>
+ <string name="title_help">Ayuda</string>
+ <!--section-->
+ <string name="section_user_ids">IDs de usuario</string>
+ <string name="section_keys">Claves</string>
+ <string name="section_general">General</string>
+ <string name="section_defaults">Predeterminados</string>
+ <string name="section_advanced">Avanzado</string>
+ <string name="section_master_key">Clave maestra</string>
+ <string name="section_master_user_id">ID del usuario principal</string>
+ <string name="section_actions">Acciones</string>
+ <string name="section_certification_key">Tu clave usada para las certificaciones</string>
+ <string name="section_upload_key">Cargar clave</string>
+ <string name="section_key_server">Servidor de claves</string>
+ <string name="section_encrypt_and_or_sign">Cifrar y/o firmar</string>
+ <string name="section_decrypt_verify">Descifrar y verificar</string>
+ <!--button-->
+ <string name="btn_sign">Firmar</string>
+ <string name="btn_certify">Certificar</string>
+ <string name="btn_decrypt">Descifrar</string>
+ <string name="btn_decrypt_verify">Descifrar y verificar</string>
+ <string name="btn_decrypt_verify_clipboard">Desde el portapapeles</string>
+ <string name="btn_select_encrypt_keys">Seleccionar destinatarios</string>
+ <string name="btn_encrypt_file">Cifrar archivo</string>
+ <string name="btn_save">Guardar</string>
+ <string name="btn_do_not_save">Cancelar</string>
+ <string name="btn_delete">Eliminar</string>
+ <string name="btn_no_date">Ninguno</string>
+ <string name="btn_okay">De acuerdo</string>
+ <string name="btn_change_passphrase">Cambiar nueva frase de contraseña</string>
+ <string name="btn_set_passphrase">Establecer nueva frase de contraseña</string>
+ <string name="btn_search">Buscar</string>
+ <string name="btn_export_to_server">Cargar al servidor de claves</string>
+ <string name="btn_next">Siguiente</string>
+ <string name="btn_back">Volver</string>
+ <string name="btn_clipboard">Portapapeles</string>
+ <string name="btn_share">Compartir con...</string>
+ <string name="btn_lookup_key">Buscar clave</string>
+ <string name="btn_encryption_advanced_settings_show">Mostrar ajustes avanzados</string>
+ <string name="btn_encryption_advanced_settings_hide">Ocultar ajustes avanzados</string>
+ <!--menu-->
+ <string name="menu_preferences">Ajustes</string>
+ <string name="menu_help">Ayuda</string>
+ <string name="menu_import_from_file">Importar desde archivo</string>
+ <string name="menu_import_from_qr_code">Importar desde código QR</string>
+ <string name="menu_import">Importar</string>
+ <string name="menu_import_from_nfc">Importar desde NFC</string>
+ <string name="menu_export_key">Exportar hacia archivo</string>
+ <string name="menu_delete_key">Borrar clave</string>
+ <string name="menu_create_key">Crear clave</string>
+ <string name="menu_create_key_expert">Crear clave (experto)</string>
+ <string name="menu_search">Buscar</string>
+ <string name="menu_import_from_key_server">Servidor de claves...</string>
+ <string name="menu_key_server">Servidor de claves...</string>
+ <string name="menu_update_key">Actualizar desde servidor de claves</string>
+ <string name="menu_export_key_to_server">Cargar al servidor de claves</string>
+ <string name="menu_share">Compartir...</string>
+ <string name="menu_share_title_fingerprint">Compartir la huella digital...</string>
+ <string name="menu_share_title">Compartir la clave completa...</string>
+ <string name="menu_share_default_fingerprint">con...</string>
+ <string name="menu_share_default">con...</string>
+ <string name="menu_share_qr_code">con código QR</string>
+ <string name="menu_share_qr_code_fingerprint">con código QR</string>
+ <string name="menu_share_nfc">con NFC</string>
+ <string name="menu_copy_to_clipboard">Copiar al portapapeles</string>
+ <string name="menu_sign_key"> Clave de firma</string>
+ <string name="menu_beam_preferences">Ajustes de Beam</string>
+ <string name="menu_key_edit_cancel">Cancelar</string>
+ <string name="menu_encrypt_to">Cifrar hacia...</string>
+ <string name="menu_select_all">Seleccionar todo</string>
+ <string name="menu_add_keys">Añadir claves</string>
+ <!--label-->
+ <string name="label_sign">Firmar</string>
+ <string name="label_message">Mensaje</string>
+ <string name="label_file">Archivo</string>
+ <string name="label_no_passphrase">No hay frase de contraseña</string>
+ <string name="label_passphrase">Frase de contraseña</string>
+ <string name="label_passphrase_again">De nuevo</string>
+ <string name="label_algorithm">Algoritmo</string>
+ <string name="label_ascii_armor">Armadura ASCII</string>
+ <string name="label_select_public_keys">Destinatarios</string>
+ <string name="label_delete_after_encryption">Borrar después del cifrado</string>
+ <string name="label_delete_after_decryption">Borrar después del descifrado</string>
+ <string name="label_share_after_encryption">Compartir después del cifrado</string>
+ <string name="label_encryption_algorithm">Algoritmo de cifrado</string>
+ <string name="label_hash_algorithm">Algoritmo de Hash</string>
+ <string name="label_asymmetric">con clave pública</string>
+ <string name="label_symmetric">con frase contraseña</string>
+ <string name="label_passphrase_cache_ttl">Caché de frase de contraseña</string>
+ <string name="label_message_compression">Compresión de mensaje</string>
+ <string name="label_file_compression">Compresión de archivo</string>
+ <string name="label_force_v3_signature">Forzar firmas OpenPGPv3 antiguas</string>
+ <string name="label_key_servers">Servidores de claves</string>
+ <string name="label_key_id">ID de clave</string>
+ <string name="label_creation">Creación</string>
+ <string name="label_expiry">Caducidad</string>
+ <string name="label_usage">Uso</string>
+ <string name="label_key_size">Tamaño de clave</string>
+ <string name="label_main_user_id">ID del usuario principal</string>
+ <string name="label_name">Nombre</string>
+ <string name="label_comment">Comentario</string>
+ <string name="label_email">Email</string>
+ <string name="label_send_key">Cargar clave al servidor de claves seleccionado después de la certificación</string>
+ <string name="label_fingerprint">Huella digital</string>
+ <string name="select_keys_button_default">Seleccionar</string>
+ <string name="expiry_date_dialog_title">Establer la fecha de vencimiento</string>
+ <plurals name="select_keys_button">
+ <item quantity="one">%d seleccionado</item>
+ <item quantity="other">%d seleccionados</item>
+ </plurals>
+ <string name="user_id_no_name">&lt;sin nombre&gt;</string>
+ <string name="none">&lt;ninguna&gt;</string>
+ <string name="no_key">&lt;sin clave&gt;</string>
+ <string name="no_email">&lt;No hay un email&gt;</string>
+ <string name="unknown_status"></string>
+ <string name="can_encrypt">se puede cifrar</string>
+ <string name="can_sign">se puede firmar</string>
+ <string name="expired">caducado</string>
+ <string name="revoked">revocado</string>
+ <string name="user_id">ID de usuario</string>
+ <plurals name="n_contacts">
+ <item quantity="one">1 contacto</item>
+ <item quantity="other">%d contactos</item>
+ </plurals>
+ <plurals name="n_key_servers">
+ <item quantity="one">%d servidor de claves</item>
+ <item quantity="other">%d servidores de claves</item>
+ </plurals>
+ <string name="fingerprint">Huella digital:</string>
+ <string name="secret_key">Clave secreta:</string>
+ <!--choice-->
+ <string name="choice_none">Ninguna</string>
+ <string name="choice_15secs">15 segs</string>
+ <string name="choice_1min">1 min</string>
+ <string name="choice_3mins">3 mins</string>
+ <string name="choice_5mins">5 mins</string>
+ <string name="choice_10mins">10 mins</string>
+ <string name="choice_20mins">20 mins</string>
+ <string name="choice_40mins">40 mins</string>
+ <string name="choice_1hour">1 hora</string>
+ <string name="choice_2hours">2 horas</string>
+ <string name="choice_4hours">4 horas</string>
+ <string name="choice_8hours">8 horas</string>
+ <string name="choice_forever">para siempre</string>
+ <string name="dsa">DSA</string>
+ <string name="elgamal">ElGamal</string>
+ <string name="rsa">RSA</string>
+ <string name="filemanager_title_open">Abrir...</string>
+ <string name="warning">Advertencia</string>
+ <string name="error">Error</string>
+ <string name="error_message">Error: %s</string>
+ <!--key flags-->
+ <string name="flag_certify">Certificar</string>
+ <string name="flag_sign">Firmar</string>
+ <string name="flag_encrypt">Cifrar</string>
+ <string name="flag_authenticate">Autentificar</string>
+ <!--sentences-->
+ <string name="wrong_passphrase">Frase de contraseña incorrecta.</string>
+ <string name="using_clipboard_content">Usando el contenido del portapapeles.</string>
+ <string name="set_a_passphrase">Establece una frase de contraseña antes.</string>
+ <string name="no_filemanager_installed">No hay un gestor de archivos compatible instalado.</string>
+ <string name="passphrases_do_not_match">Las frases de contraseña no coinciden.</string>
+ <string name="passphrase_must_not_be_empty">Por favor, introduce una frase de contraseña.</string>
+ <string name="passphrase_for_symmetric_encryption">Cifrado simétrico.</string>
+ <string name="passphrase_for">Introducir la frase de contraseña para \'%s\'</string>
+ <string name="file_delete_confirmation">¿Estás seguro de que quieres borrar\n%s?</string>
+ <string name="file_delete_successful">Borrado satisfactoriamente.</string>
+ <string name="no_file_selected">Selecciona un archivo antes.</string>
+ <string name="decryption_successful">Descifrado y/o verificado satisfactoriamente.</string>
+ <string name="encryption_successful">Firmado y/o cifrado satisfactoriamente.</string>
+ <string name="encryption_to_clipboard_successful">Firmado y/o cifrado al portapapeles satisfactoriamente.</string>
+ <string name="enter_passphrase_twice">Introduce la frase de contraseña dos veces.</string>
+ <string name="select_encryption_key">Selecciona al menos una clave de cifrado.</string>
+ <string name="select_encryption_or_signature_key">Selecciona al menos una clave de cifrado o de firma.</string>
+ <string name="specify_file_to_encrypt_to">Por favor, especifica hacia qué archivo quieres cifrar.\nADVERTENCIA: El archivo se sobreescribirá si ya existiese.</string>
+ <string name="specify_file_to_decrypt_to">Por favor, especifica hacia qué archivo quieres descifrar.\nADVERTENCIA: El archivo se sobreescribirá si ya existiese.</string>
+ <string name="specify_file_to_export_to">Por favor, especifica hacia qué archivo quieres exportar.\nADVERTENCIA: El archivo se sobreescribirá si ya existiese.</string>
+ <string name="specify_file_to_export_secret_keys_to">Por favor, especifica hacia qué archivo quieres exportar.\nADVERTENCIA: Estás a punto de exportar claves SECRETAS.\nADVERTENCIA: El archivo se sobreescribirá si ya existiese.</string>
+ <string name="key_deletion_confirmation">¿Quieres realmente borrar la clave \'%s\'?\n¡No podrás deshacerlo!</string>
+ <string name="key_deletion_confirmation_multi">¿Quieres realmente borrar todas las claves seleccionadas?\n¡No podrás deshacerlo!</string>
+ <string name="secret_key_deletion_confirmation">¿Quieres realmente borrar la clave SECRETA \'%s\'?\n¡No podrás deshacerlo!</string>
+ <string name="ask_save_changed_key">Has hecho cambios en el almacén de claves, ¿quieres guardarlos?</string>
+ <string name="ask_empty_id_ok">Has añadido una ID de usuario vacía, ¿Estás seguro que quieres continuar?</string>
+ <string name="public_key_deletetion_confirmation">¿Quieres realmente borrar la clave PÚBLICA \'%s\'?\n¡No podrás deshacerlo!</string>
+ <string name="secret_key_delete_text">¿Borrar claves secretas?</string>
+ <string name="also_export_secret_keys">¿Exportar también las claves secretas?</string>
+ <plurals name="keys_added_and_updated_1">
+ <item quantity="one">%d clave añadida satisfactoriamente</item>
+ <item quantity="other">%d claves añadidas satisfactoriamente</item>
+ </plurals>
+ <plurals name="keys_added_and_updated_2">
+ <item quantity="one">y actualizada %d clave.</item>
+ <item quantity="other">y actualizadas %d claves.</item>
+ </plurals>
+ <plurals name="keys_added">
+ <item quantity="one">%d clave añadida satisfactoriamente.</item>
+ <item quantity="other">%d claves añadidas satisfactoriamente.</item>
+ </plurals>
+ <plurals name="keys_updated">
+ <item quantity="one">%d clave actualizada satisfactoriamente.</item>
+ <item quantity="other">%d claves actualizadas satisfactoriamente.</item>
+ </plurals>
+ <string name="no_keys_added_or_updated">No se han añadido o actualizado claves.</string>
+ <string name="key_exported">Se ha exportado 1 clave satisfactoriamente.</string>
+ <string name="keys_exported">%d claves exportadas satisfactoriamente.</string>
+ <string name="no_keys_exported">No se han exportado claves.</string>
+ <string name="key_creation_el_gamal_info">Nota: solo las subclaves son compatibles con ElGamal, y para ElGamal debe usarse el tamaño de clave más próximo de 1536, 2048, 3072, 4096, o 8192.</string>
+ <string name="key_creation_weak_rsa_info">Nota: generar una clave RSA de longitud 1024-bit o menos está considerado inseguro y desactivado para generar nuevas claves.</string>
+ <string name="key_not_found">No se puede encontrar la clave %08X.</string>
+ <plurals name="keys_found">
+ <item quantity="one">Se ha encontrado %d clave.</item>
+ <item quantity="other">Se han encontrado %d claves.</item>
+ </plurals>
+ <string name="unknown_signature">Firma desconocida, pulsa el botón para buscar la clave extraviada.</string>
+ <plurals name="bad_keys_encountered">
+ <item quantity="one">%d mala clave secreta ignorada. Quizás hayas exportado con la opción\n--export-secret-subkeys\nAsegúrate de que exportas con\n--export-secret-keys\nen su lugar.</item>
+ <item quantity="other">%d malas claves secretas ignoradas. Quizás hayas exportado con la opción\n--export-secret-subkeys\nAsegúrate de que exportas con\n--export-secret-keys\nen su lugar.</item>
+ </plurals>
+ <string name="key_send_success">Clave cargada al servidor satisfactoriamente</string>
+ <string name="key_sign_success">Clave firmada satisfactoriamente</string>
+ <string name="list_empty">¡Esta lista está vacía!</string>
+ <string name="nfc_successfull">¡Clave enviada satisfactoriamente con NFC Beam!</string>
+ <string name="key_copied_to_clipboard">¡La clave ha sido copiada al portapapeles!</string>
+ <string name="key_has_already_been_signed">¡La clave ya ha sido firmada!</string>
+ <string name="select_key_to_sign">¡Por favor, selecciona la clave que se usará para firmar!</string>
+ <string name="key_too_big_for_sharing">¡La clave es demasiado grande para ser compartida de esta forma!</string>
+ <!--errors
+ no punctuation, all lowercase,
+ they will be put after "error_message", e.g. "Error: file not found"-->
+ <string name="error_file_delete_failed">ha fallado el borrado de \'%s\'</string>
+ <string name="error_file_not_found">archivo no encontrado</string>
+ <string name="error_no_secret_key_found">no se ha encontrado una clave secreta adecuada</string>
+ <string name="error_no_known_encryption_found">se ha encontrado un tipo de cifrado no conocido</string>
+ <string name="error_external_storage_not_ready">el almacenamiento externo no está preparado</string>
+ <string name="error_invalid_email">email incorrecto \'%s\'</string>
+ <string name="error_key_size_minimum512bit">el tamaño de clave debe ser de al menos 512bit</string>
+ <string name="error_master_key_must_not_be_el_gamal">la clave maestra no puede ser una clave ElGamal</string>
+ <string name="error_unknown_algorithm_choice">elegido algoritmo desconocido</string>
+ <string name="error_user_id_needs_a_name">necesitas determinar un nombre</string>
+ <string name="error_user_id_no_email">no se ha encontrado un email</string>
+ <string name="error_user_id_needs_an_email_address">tienes que determinar una dirección de email</string>
+ <string name="error_key_needs_a_user_id">necesitas al menos una ID de usuario</string>
+ <string name="error_main_user_id_must_not_be_empty">la ID del usuario principal no puede estar vacía</string>
+ <string name="error_key_needs_master_key">necesitas al menos una clave maestra</string>
+ <string name="error_no_encryption_keys_or_passphrase">no has proporcionado ninguna clave de cifrado o frase de contraseña</string>
+ <string name="error_signature_failed">la firma ha fallado</string>
+ <string name="error_no_signature_passphrase">no has proporcionado una frase de contraseña</string>
+ <string name="error_no_signature_key">no has proporcionado una clave de firma</string>
+ <string name="error_invalid_data">cifrado de datos no válido</string>
+ <string name="error_corrupt_data">datos corrompidos</string>
+ <string name="error_integrity_check_failed">¡ha fallado la comprobación de integridad! ¡Los datos han sido modificados!</string>
+ <string name="error_no_symmetric_encryption_packet">no se ha podido encontrar un paquete con cifrado simétrico</string>
+ <string name="error_wrong_passphrase">frase de contraseña incorrecta</string>
+ <string name="error_saving_keys">error al guardar algunas claves</string>
+ <string name="error_could_not_extract_private_key">no se puede extraer la clave privada</string>
+ <string name="error_only_files_are_supported">Dirigir datos binarios sin un archivo real en el sistema de archivos es incompatible. Esto solo es compatible con ACTION_ENCRYPT_STREAM_AND_RETURN.</string>
+ <string name="error_jelly_bean_needed">!Necesitas Android 4.1 alias Jelly Bean para poder usar la característica NFC Beam!</string>
+ <string name="error_nfc_needed">¡NFC no está disponible en tu dispositivo!</string>
+ <string name="error_nothing_import">¡Nada que importar!</string>
+ <string name="error_expiry_must_come_after_creation">la fecha de caducidad debe ser posterior a la fecha de creación</string>
+ <string name="error_save_first">Por favor, guarda el almacén de claves antes</string>
+ <string name="error_can_not_delete_contact">no puedes eliminar este contacto porque eres tú mismo.</string>
+ <string name="error_can_not_delete_contacts">no puedes eliminar los siguientes contactos porque son tú mismo:\n%s</string>
+ <string name="error_keyserver_insufficient_query">Consulta al servidor insuficiente</string>
+ <string name="error_keyserver_query">La consulta al servidor de claves ha fallado</string>
+ <string name="error_keyserver_too_many_responses">Demasiadas respuestas</string>
+ <string name="error_import_file_no_content">El archivo está vacio</string>
+ <string name="error_generic_report_bug">Ha ocurrido un error genérico, por favor, informa de este bug a OpenKeychain</string>
+ <plurals name="error_can_not_delete_info">
+ <item quantity="one">Por favor, bórralo desde la pantalla \'Mis claves\'!</item>
+ <item quantity="other">Por favor, bórralos desde la pantalla \'Mis claves\'!</item>
+ </plurals>
+ <plurals name="error_import_non_pgp_part">
+ <item quantity="one">parte del archivo cargado es un objeto OpenPGP válido pero no una clave OpenPGP</item>
+ <item quantity="other">partes del archivo cargado son objetos OpenPGP válidos pero no claves OpenPGP</item>
+ </plurals>
+ <string name="error_change_something_first">Debes hacer cambios en el almacén de claves antes de que puedas guardarlo</string>
+ <!--progress dialogs, usually ending in '…'-->
+ <string name="progress_done">Hecho.</string>
+ <string name="progress_cancel">Cancelar</string>
+ <string name="progress_saving">guardando...</string>
+ <string name="progress_importing">importando...</string>
+ <string name="progress_exporting">exportando...</string>
+ <string name="progress_building_key">construyendo la clave...</string>
+ <string name="progress_preparing_master_key">preparando la clave maestra...</string>
+ <string name="progress_certifying_master_key">certificando la clave maestra...</string>
+ <string name="progress_building_master_key">construyendo el anillo maestro...</string>
+ <string name="progress_adding_sub_keys">añadiendo las subclaves...</string>
+ <string name="progress_saving_key_ring">guardando claves...</string>
+ <plurals name="progress_exporting_key">
+ <item quantity="one">exportando clave...</item>
+ <item quantity="other">exportando claves...</item>
+ </plurals>
+ <plurals name="progress_generating">
+ <item quantity="one">generando clave, esto puede tardar más de 3 minutos...</item>
+ <item quantity="other">generando claves, esto puede tardar más de 3 minutos...</item>
+ </plurals>
+ <string name="progress_extracting_signature_key">extrayendo la clave de firma...</string>
+ <string name="progress_extracting_key">extrayendo la clave...</string>
+ <string name="progress_preparing_streams">preparando las transmisiones...</string>
+ <string name="progress_encrypting">cifrando los datos...</string>
+ <string name="progress_decrypting">descifrando los datos...</string>
+ <string name="progress_preparing_signature">preparando la firma...</string>
+ <string name="progress_generating_signature">generando la firma...</string>
+ <string name="progress_processing_signature">procesando la firma...</string>
+ <string name="progress_verifying_signature">verificando la firma...</string>
+ <string name="progress_signing">firmando...</string>
+ <string name="progress_reading_data">leyendo los datos...</string>
+ <string name="progress_finding_key">localizando la clave...</string>
+ <string name="progress_decompressing_data">descomprimiendo los datos...</string>
+ <string name="progress_verifying_integrity">verificando la integridad...</string>
+ <string name="progress_deleting_securely">borrando \'%s\' de forma segura…</string>
+ <string name="progress_querying">consultando...</string>
+ <!--action strings-->
+ <string name="hint_public_keys">Buscar claves públicas</string>
+ <string name="hint_secret_keys">Buscar claves secretas</string>
+ <string name="action_share_key_with">Compartir la clave con...</string>
+ <!--key bit length selections-->
+ <string name="key_size_512">512</string>
+ <string name="key_size_1024">1024</string>
+ <string name="key_size_2048">2048</string>
+ <string name="key_size_4096">4096</string>
+ <!--compression-->
+ <string name="compression_fast">rápido</string>
+ <string name="compression_very_slow">muy lento</string>
+ <!--Help-->
+ <string name="help_tab_start">Comenzar</string>
+ <string name="help_tab_faq">FAQ</string>
+ <string name="help_tab_nfc_beam">NFC Beam</string>
+ <string name="help_tab_changelog">Registro de cambios</string>
+ <string name="help_tab_about">A cerca de</string>
+ <string name="help_about_version">Versión:</string>
+ <!--Import-->
+ <string name="import_import">Importar las claves seleccionadas</string>
+ <string name="import_sign_and_upload">Importar, firmar y cargar las claves seleccionadas</string>
+ <string name="import_from_clipboard">Importar desde el portapapeles</string>
+ <plurals name="import_qr_code_missing">
+ <item quantity="one">El código QR con ID %s se ha extraviado</item>
+ <item quantity="other">Los códigos QR con IDs %s se han extraviado</item>
+ </plurals>
+ <string name="import_qr_code_start_with_one">Por favor, comienza con el código QR de ID 1</string>
+ <string name="import_qr_code_wrong">¡El código QR está deformado! ¡Por favor, prueba de nuevo!</string>
+ <string name="import_qr_code_finished">¡El escaneo del código QR ha finalizado!</string>
+ <string name="import_qr_code_too_short_fingerprint">La huella de validación (fingerprint) de clave es demasiado corta (&lt; 16 caracteres)</string>
+ <string name="import_qr_scan_button">Escanea el código QR con \'Barcode Scanner\'</string>
+ <string name="import_nfc_text">Para recibir las claves a través de NFC, el dispositivo tiene que estar desbloqueado.</string>
+ <string name="import_nfc_help_button">Ayuda</string>
+ <string name="import_clipboard_button">Tomar la clave desde el portapapeles</string>
+ <!--Intent labels-->
+ <string name="intent_decrypt_file">Descifrar archivo con OpenKeychain</string>
+ <string name="intent_import_key">Importar clave con OpenKeychain</string>
+ <string name="intent_send_encrypt">Cifrar con OpenKeychain</string>
+ <string name="intent_send_decrypt">Descifrar con OpenKeychain</string>
+ <!--Remote API-->
+ <string name="api_no_apps">¡No hay aplicaciones registradas!\n\nLas aplicaciones de terceros pueden pedir permiso de acceso a OpenKeychain. Después de obtener acceso, serán enumeradas aquí.</string>
+ <string name="api_settings_show_info">Mostrar información avanzada</string>
+ <string name="api_settings_hide_info">Ocultar información avanzada</string>
+ <string name="api_settings_show_advanced">Mostrar la configuración avanzada</string>
+ <string name="api_settings_hide_advanced">Ocultar la configuración avanzada</string>
+ <string name="api_settings_no_key">No se ha seleccionado ninguna clave</string>
+ <string name="api_settings_select_key">Seleccionar clave</string>
+ <string name="api_settings_create_key">Crear una nueva clave para esta cuenta</string>
+ <string name="api_settings_save">Guardar</string>
+ <string name="api_settings_cancel">Cancelar</string>
+ <string name="api_settings_revoke">Revocar acceso</string>
+ <string name="api_settings_delete_account">Borrar cuenta</string>
+ <string name="api_settings_package_name">Nombre de paquete</string>
+ <string name="api_settings_package_signature">SHA-256 de firma de paquete</string>
+ <string name="api_settings_accounts">Cuentas</string>
+ <string name="api_settings_accounts_empty">No hay cuentas asociadas a esta aplicación.</string>
+ <string name="api_create_account_text">La aplicación solicita la creación de una nueva cuenta. Por favor, selecciona una clave privada que ya exista o crea una nueva.\n¡Las aplicaciones tienen restringido el uso de claves a las que tú selecciones aquí!</string>
+ <string name="api_register_text">La aplicación mostrada solicita acceso a OpenKeychain.\n¿Permitir el acceso?\n\nAVISO: Si no sabes por qué aparece esta pantalla, ¡deniega el acceso! Puedes revocarlo después usando la pantalla \'Aplicaciones registradas\'.</string>
+ <string name="api_register_allow">Permitir el acceso</string>
+ <string name="api_register_disallow">Denegar el acceso</string>
+ <string name="api_register_error_select_key">¡Por favor, selecciona una clave!</string>
+ <string name="api_select_pub_keys_missing_text">No se han encontrado claves públicas para estas IDs de usuario:</string>
+ <string name="api_select_pub_keys_dublicates_text">Existe más de una clave pública para estos IDs de usuario:</string>
+ <string name="api_select_pub_keys_text">¡Por favor, revisa la lista de destinatarios!</string>
+ <string name="api_error_wrong_signature">¡La comprobación de la firma ha fallado! ¿Has instalado esta app desde una fuente distinta? Si estás seguro de que esto no es un ataque, revoca el registro de esta app en OpenKeychain y regístrala de nuevo.</string>
+ <!--Share-->
+ <string name="share_qr_code_dialog_title">Compartir con código QR</string>
+ <string name="share_qr_code_dialog_start">Pasa por todos los códigos QR usando \'Siguiente\', y escanéalos de uno en uno.</string>
+ <string name="share_qr_code_dialog_fingerprint_text">Huella digital:</string>
+ <string name="share_qr_code_dialog_progress">Código QR con ID %1$d de %2$d</string>
+ <string name="share_nfc_dialog">Compartir con NFC</string>
+ <!--Key list-->
+ <plurals name="key_list_selected_keys">
+ <item quantity="one">1 clave seleccionada.</item>
+ <item quantity="other">%d claves seleccionadas.</item>
+ </plurals>
+ <string name="key_list_empty_text1">Aún no hay claves disponibles...</string>
+ <string name="key_list_empty_text2">Puedes empezar por</string>
+ <string name="key_list_empty_text3">o</string>
+ <string name="key_list_empty_button_create">crear tu propia clave</string>
+ <string name="key_list_empty_button_import">importar claves</string>
+ <!--Key view-->
+ <string name="key_view_action_edit">Editar esta clave</string>
+ <string name="key_view_action_encrypt">Cifrar hacia este contacto</string>
+ <string name="key_view_action_certify">Certificar la clave de este contacto</string>
+ <string name="key_view_tab_main">Información</string>
+ <string name="key_view_tab_certs">Certificaciones</string>
+ <!--Navigation Drawer-->
+ <string name="nav_contacts">Claves</string>
+ <string name="nav_encrypt">Firmar y cifrar</string>
+ <string name="nav_decrypt">Descifrar y verificar</string>
+ <string name="nav_import">Importar claves</string>
+ <string name="nav_secret_keys">Mis claves</string>
+ <string name="nav_apps">Aplicaciones registradas</string>
+ <string name="drawer_open">Abrir el Navigation Drawer</string>
+ <string name="drawer_close">Cerrar el Navigation Drawer</string>
+ <string name="edit">Editar</string>
+ <string name="my_keys">Mis claves</string>
+ <string name="label_secret_key">Claves secretas</string>
+ <string name="secret_key_yes">disponible</string>
+ <string name="secret_key_no">no disponible</string>
+ <!--hints-->
+ <string name="encrypt_content_edit_text_hint">Escribe aquí el mensaje que quieras cifrar y/o firmar...</string>
+ <string name="decrypt_content_edit_text_hint">Introduce aquí el texto cifrado para descifrarlo y/o verificarlo...</string>
+ <!--unsorted-->
+ <string name="section_uids_to_sign">IDs de usuario para firmar</string>
+ <string name="progress_re_adding_certs">Nueva aplicación de certificados</string>
+</resources>
diff --git a/OpenKeychain/src/main/res/values-et/strings.xml b/OpenKeychain/src/main/res/values-et/strings.xml
new file mode 100644
index 000000000..e1e725234
--- /dev/null
+++ b/OpenKeychain/src/main/res/values-et/strings.xml
@@ -0,0 +1,118 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<resources>
+ <!--title-->
+ <string name="title_manage_public_keys">Kontaktid</string>
+ <string name="title_manage_secret_keys">Salajased võtmed</string>
+ <string name="title_select_recipients">Vali avalik võti</string>
+ <string name="title_select_secret_key">Vali salajane võti</string>
+ <string name="title_encrypt">Krüpteeri</string>
+ <string name="title_decrypt">Dekrüpteeri</string>
+ <string name="title_authentication">Salasõne</string>
+ <string name="title_create_key">Loo võti</string>
+ <string name="title_edit_key">Muuda võtit</string>
+ <string name="title_preferences">Seaded</string>
+ <string name="title_api_registered_apps">Registreeritud rakendused</string>
+ <string name="title_key_server_preference">Võtmeserveri seaded</string>
+ <string name="title_set_passphrase">Määra salasõne</string>
+ <string name="title_send_email">Saada kiri...</string>
+ <string name="title_import_keys">Impordi võtmeid</string>
+ <string name="title_export_key">Ekspordi võti</string>
+ <string name="title_export_keys">Ekspordi võtmed</string>
+ <string name="title_key_not_found">Võtit ei leitud</string>
+ <string name="title_key_server_query">Päri võtmeserverist</string>
+ <string name="title_send_key">Lae võtmeserverisse</string>
+ <string name="title_unknown_signature_key">Võõras allkirjastamise võti</string>
+ <string name="title_help">Abi</string>
+ <!--section-->
+ <string name="section_user_ids">Kasutaja ID-d</string>
+ <string name="section_keys">Võtmed</string>
+ <string name="section_general">Üldine</string>
+ <string name="section_defaults">Vaikeseaded</string>
+ <!--button-->
+ <string name="btn_sign">Allkirjasta</string>
+ <string name="btn_decrypt">Dekrüpteeri</string>
+ <string name="btn_select_encrypt_keys">Vali saajad</string>
+ <string name="btn_save">Salvesta</string>
+ <string name="btn_do_not_save">Katkesta</string>
+ <string name="btn_delete">Kustuta</string>
+ <string name="btn_search">Otsi</string>
+ <string name="btn_export_to_server">Saada võtmeserverisse</string>
+ <string name="btn_next">Järgmine</string>
+ <string name="btn_back">Tagasi</string>
+ <!--menu-->
+ <string name="menu_preferences">Seaded</string>
+ <string name="menu_delete_key">Kustuta võti</string>
+ <string name="menu_create_key">Loo võti</string>
+ <string name="menu_search">Otsi</string>
+ <string name="menu_key_server">Võtmeserver...</string>
+ <string name="menu_update_key">Uuenda võtmeserverist</string>
+ <string name="menu_export_key_to_server">Saada võtmeserverisse</string>
+ <string name="menu_share">Jaga...</string>
+ <string name="menu_sign_key">Allkirjasta võti</string>
+ <!--label-->
+ <string name="label_sign">Allkirjasta</string>
+ <string name="label_message">Sõnum</string>
+ <string name="label_file">Fail</string>
+ <string name="label_no_passphrase">Salasõnet pole</string>
+ <string name="label_passphrase">Salasõne</string>
+ <string name="label_passphrase_again">Uuesti</string>
+ <string name="label_algorithm">Algoritm</string>
+ <string name="label_select_public_keys">Saajad</string>
+ <string name="label_delete_after_encryption">Kustuta peale šifreerimist</string>
+ <string name="label_hash_algorithm">Räsialgoritm</string>
+ <string name="label_passphrase_cache_ttl">Salasõne puhverdus</string>
+ <string name="label_key_servers">Võtmeserverid</string>
+ <string name="label_creation">Loodud</string>
+ <string name="label_expiry">Aegub</string>
+ <string name="label_usage">Kasutusvaldkond</string>
+ <string name="label_key_size">Võtmepikkus</string>
+ <string name="label_name">Nimi</string>
+ <string name="label_comment">Kommentaar</string>
+ <string name="label_email">E-mail</string>
+ <string name="unknown_status"></string>
+ <string name="expired">aegunud</string>
+ <string name="fingerprint">Sõrmejälg:</string>
+ <string name="secret_key">Salajane võti:</string>
+ <!--choice-->
+ <string name="choice_15secs">15 sekundit</string>
+ <string name="choice_1min">1 minut</string>
+ <string name="choice_3mins">3 minutit</string>
+ <string name="choice_5mins">5 minutit</string>
+ <string name="choice_10mins">10 minutit</string>
+ <string name="choice_20mins">20 minutit</string>
+ <string name="choice_40mins">40 minutit</string>
+ <string name="choice_1hour">1 tund</string>
+ <string name="choice_2hours">2 tundi</string>
+ <string name="choice_4hours">4 tundi</string>
+ <string name="choice_8hours">8 tundi</string>
+ <string name="dsa">DSA</string>
+ <string name="elgamal">ElGamal</string>
+ <string name="rsa">RSA</string>
+ <string name="filemanager_title_open">Ava...</string>
+ <string name="warning">Hoiatus</string>
+ <string name="error">Viga</string>
+ <string name="error_message">Viga: %s</string>
+ <!--key flags-->
+ <!--sentences-->
+ <string name="wrong_passphrase">Vale salasõne</string>
+ <string name="set_a_passphrase">Määra enne salasõne.</string>
+ <string name="passphrases_do_not_match">Salasõned ei ühti.</string>
+ <string name="passphrase_for_symmetric_encryption">Sümmeetriline krüpteering</string>
+ <!--errors
+ no punctuation, all lowercase,
+ they will be put after "error_message", e.g. "Error: file not found"-->
+ <!--progress dialogs, usually ending in '…'-->
+ <!--action strings-->
+ <!--key bit length selections-->
+ <!--compression-->
+ <!--Help-->
+ <!--Import-->
+ <!--Intent labels-->
+ <!--Remote API-->
+ <!--Share-->
+ <!--Key list-->
+ <!--Key view-->
+ <!--Navigation Drawer-->
+ <!--hints-->
+ <!--unsorted-->
+</resources>
diff --git a/OpenKeychain/src/main/res/values-fa-rIR/strings.xml b/OpenKeychain/src/main/res/values-fa-rIR/strings.xml
new file mode 100644
index 000000000..fc802092c
--- /dev/null
+++ b/OpenKeychain/src/main/res/values-fa-rIR/strings.xml
@@ -0,0 +1,29 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<resources>
+ <!--title-->
+ <!--section-->
+ <!--button-->
+ <!--menu-->
+ <!--label-->
+ <string name="unknown_status"></string>
+ <!--choice-->
+ <!--key flags-->
+ <!--sentences-->
+ <!--errors
+ no punctuation, all lowercase,
+ they will be put after "error_message", e.g. "Error: file not found"-->
+ <!--progress dialogs, usually ending in '…'-->
+ <!--action strings-->
+ <!--key bit length selections-->
+ <!--compression-->
+ <!--Help-->
+ <!--Import-->
+ <!--Intent labels-->
+ <!--Remote API-->
+ <!--Share-->
+ <!--Key list-->
+ <!--Key view-->
+ <!--Navigation Drawer-->
+ <!--hints-->
+ <!--unsorted-->
+</resources>
diff --git a/OpenKeychain/src/main/res/values-fr/strings.xml b/OpenKeychain/src/main/res/values-fr/strings.xml
new file mode 100644
index 000000000..5d4c63432
--- /dev/null
+++ b/OpenKeychain/src/main/res/values-fr/strings.xml
@@ -0,0 +1,451 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<resources>
+ <!--title-->
+ <string name="title_manage_public_keys">Contacts</string>
+ <string name="title_manage_secret_keys">Clefs secrètes</string>
+ <string name="title_select_recipients">Choisir la clef publique</string>
+ <string name="title_select_secret_key">Choisir la clef secrète</string>
+ <string name="title_encrypt">Chiffrer</string>
+ <string name="title_decrypt">Déchiffrer</string>
+ <string name="title_authentication">Phrase de passe</string>
+ <string name="title_create_key">Créer une clef</string>
+ <string name="title_edit_key">Modifier une clef</string>
+ <string name="title_preferences">Préférences</string>
+ <string name="title_api_registered_apps">Applications enregistrées</string>
+ <string name="title_key_server_preference">Préférences du serveur de clefs</string>
+ <string name="title_change_passphrase">Modifier la phrase de passe</string>
+ <string name="title_set_passphrase">Définir la phrase de passe</string>
+ <string name="title_send_email">Envoyer un courriel...</string>
+ <string name="title_send_file">Envoyer un fichier...</string>
+ <string name="title_encrypt_to_file">Chiffrer vers un fichier</string>
+ <string name="title_decrypt_to_file">Déchiffrer vers un fichier</string>
+ <string name="title_import_keys">importer des clefs</string>
+ <string name="title_export_key">Exporter la clef</string>
+ <string name="title_export_keys">Exporter les clefs</string>
+ <string name="title_key_not_found">Clef introuvable</string>
+ <string name="title_key_server_query">Interroger le serveur de clefs</string>
+ <string name="title_send_key">Téléverser vers le serveur de clefs</string>
+ <string name="title_unknown_signature_key">Clef de signature inconnue</string>
+ <string name="title_certify_key">Certifier la clef</string>
+ <string name="title_key_details">Détails sur la clef</string>
+ <string name="title_help">Aide</string>
+ <!--section-->
+ <string name="section_user_ids">IDs utilisateurs</string>
+ <string name="section_keys">Clefs</string>
+ <string name="section_general">Général</string>
+ <string name="section_defaults">Valeur par défaut</string>
+ <string name="section_advanced">Avancé</string>
+ <string name="section_master_key">Clef maîtresse</string>
+ <string name="section_master_user_id">ID utilisateur maître</string>
+ <string name="section_actions">Actions</string>
+ <string name="section_certification_key">Votre clef utilisée pour la certification</string>
+ <string name="section_upload_key">Téléverser la clef</string>
+ <string name="section_key_server">Serveur de clefs</string>
+ <string name="section_encrypt_and_or_sign">Chiffrer et/ou signer</string>
+ <string name="section_decrypt_verify">Déchiffrer et vérifier</string>
+ <!--button-->
+ <string name="btn_sign">Signer</string>
+ <string name="btn_certify">Certifier</string>
+ <string name="btn_decrypt">Déchiffrer</string>
+ <string name="btn_decrypt_verify">Déchiffrer et vérifier</string>
+ <string name="btn_decrypt_verify_clipboard">À partir du presse-papiers</string>
+ <string name="btn_select_encrypt_keys">Choisir les destinataires</string>
+ <string name="btn_encrypt_file">Chiffrer le fichier</string>
+ <string name="btn_save">Enregistrer</string>
+ <string name="btn_do_not_save">Annuler</string>
+ <string name="btn_delete">Supprimer</string>
+ <string name="btn_no_date">Aucune</string>
+ <string name="btn_okay">OK</string>
+ <string name="btn_change_passphrase">Changer la nouvelle phrase de passe</string>
+ <string name="btn_set_passphrase">Définir la nouvelle phrase de passe</string>
+ <string name="btn_search">Rechercher</string>
+ <string name="btn_export_to_server">Téléverser vers le serveur de clefs</string>
+ <string name="btn_next">Suivant</string>
+ <string name="btn_back">Retour</string>
+ <string name="btn_clipboard">Presse-papiers</string>
+ <string name="btn_share">Partager avec...</string>
+ <string name="btn_lookup_key">Rechercher la clef</string>
+ <string name="btn_encryption_advanced_settings_show">Afficher les paramètres avancés</string>
+ <string name="btn_encryption_advanced_settings_hide">Masquer les paramètres avancés</string>
+ <!--menu-->
+ <string name="menu_preferences">Paramètres</string>
+ <string name="menu_help">Aide</string>
+ <string name="menu_import_from_file">Importer depuis un fichier</string>
+ <string name="menu_import_from_qr_code">Importer depuis un code QR</string>
+ <string name="menu_import">Importer</string>
+ <string name="menu_import_from_nfc">Importer avec NFC</string>
+ <string name="menu_export_key">Exporter vers un fichier</string>
+ <string name="menu_delete_key">Supprimer la clef</string>
+ <string name="menu_create_key">Créer une clef</string>
+ <string name="menu_create_key_expert">Créer une clef (expert)</string>
+ <string name="menu_search">Rechercher</string>
+ <string name="menu_import_from_key_server">Serveur de clefs</string>
+ <string name="menu_key_server">Serveur de clefs...</string>
+ <string name="menu_update_key">Mettre à jour depuis le serveur de clefs</string>
+ <string name="menu_export_key_to_server">Téléverser vers le serveur de clefs</string>
+ <string name="menu_share">Partager...</string>
+ <string name="menu_share_title_fingerprint">Partager l\'empreinte...</string>
+ <string name="menu_share_title">Partager la clef entière...</string>
+ <string name="menu_share_default_fingerprint">avec...</string>
+ <string name="menu_share_default">avec...</string>
+ <string name="menu_share_qr_code">par un code QR</string>
+ <string name="menu_share_qr_code_fingerprint">par un code QR</string>
+ <string name="menu_share_nfc">par la NFC</string>
+ <string name="menu_copy_to_clipboard">Copier vers le presse-papiers</string>
+ <string name="menu_sign_key">Signer la clef</string>
+ <string name="menu_beam_preferences">Paramètres Beam</string>
+ <string name="menu_key_edit_cancel">Annuler</string>
+ <string name="menu_encrypt_to">Chiffrer vers...</string>
+ <string name="menu_select_all">Tout sélectionner</string>
+ <string name="menu_add_keys">Ajouter des clefs</string>
+ <!--label-->
+ <string name="label_sign">Signer</string>
+ <string name="label_message">Message</string>
+ <string name="label_file">Fichier</string>
+ <string name="label_no_passphrase">Aucune phrase de passe</string>
+ <string name="label_passphrase">Phrase de passe</string>
+ <string name="label_passphrase_again">Confirmation</string>
+ <string name="label_algorithm">Algorithme</string>
+ <string name="label_ascii_armor">Armure ASCII</string>
+ <string name="label_select_public_keys">Destinataires</string>
+ <string name="label_delete_after_encryption">Supprimer après le chiffrement</string>
+ <string name="label_delete_after_decryption">Supprimer après le chiffrement</string>
+ <string name="label_share_after_encryption">Partager après chiffrement</string>
+ <string name="label_encryption_algorithm">Algorithme de chiffrement</string>
+ <string name="label_hash_algorithm">Algorithme de hachage</string>
+ <string name="label_asymmetric">avec une clef publique</string>
+ <string name="label_symmetric">avec une phrase de passe</string>
+ <string name="label_passphrase_cache_ttl">Cache de la phrase de passe</string>
+ <string name="label_message_compression">Compression des messages</string>
+ <string name="label_file_compression">Compression des fichiers</string>
+ <string name="label_force_v3_signature">Forcer les anciennes signatures OpenPGP v3</string>
+ <string name="label_key_servers">Serveurs de clefs</string>
+ <string name="label_key_id">ID de le clef</string>
+ <string name="label_creation">Création</string>
+ <string name="label_expiry">Expiration</string>
+ <string name="label_usage">Utilisation</string>
+ <string name="label_key_size">Taille de la clef</string>
+ <string name="label_main_user_id">ID utilisateur principal</string>
+ <string name="label_name">Nom</string>
+ <string name="label_comment">Commentaire</string>
+ <string name="label_email">Courriel</string>
+ <string name="label_send_key">Téléverser la clef vers le serveur de clefs choisi après certification</string>
+ <string name="label_fingerprint">Empreinte</string>
+ <string name="select_keys_button_default">Choisir</string>
+ <string name="expiry_date_dialog_title">Définir une date d\'expiration</string>
+ <plurals name="select_keys_button">
+ <item quantity="one">%d choisie</item>
+ <item quantity="other">%d choisies</item>
+ </plurals>
+ <string name="user_id_no_name">&lt;aucun nom&gt;</string>
+ <string name="none">&lt;aucune&gt;</string>
+ <string name="no_key">&lt;pas de clef&gt;</string>
+ <string name="no_email">&lt;aucun courriel&gt;</string>
+ <string name="unknown_status"></string>
+ <string name="can_encrypt">peut chiffrer</string>
+ <string name="can_sign">peut signer</string>
+ <string name="expired">expiré</string>
+ <string name="revoked">révoquée</string>
+ <string name="user_id">ID utilisateur</string>
+ <plurals name="n_contacts">
+ <item quantity="one">1 contact</item>
+ <item quantity="other">%d contacts</item>
+ </plurals>
+ <plurals name="n_key_servers">
+ <item quantity="one">%d serveur de clefs</item>
+ <item quantity="other">%d serveurs de clefs</item>
+ </plurals>
+ <string name="fingerprint">Empreinte :</string>
+ <string name="secret_key">Clef secrète :</string>
+ <!--choice-->
+ <string name="choice_none">Aucune</string>
+ <string name="choice_15secs">15 s</string>
+ <string name="choice_1min">1 min</string>
+ <string name="choice_3mins">3 min</string>
+ <string name="choice_5mins">5 min</string>
+ <string name="choice_10mins">10 min</string>
+ <string name="choice_20mins">20 min</string>
+ <string name="choice_40mins">40 min</string>
+ <string name="choice_1hour">1 heure</string>
+ <string name="choice_2hours">2 heures</string>
+ <string name="choice_4hours">4 heures</string>
+ <string name="choice_8hours">8 heures</string>
+ <string name="choice_forever">pour toujours</string>
+ <string name="dsa">DSA</string>
+ <string name="elgamal">ElGamal</string>
+ <string name="rsa">RSA</string>
+ <string name="filemanager_title_open">Ouvrir...</string>
+ <string name="warning">Avertissement</string>
+ <string name="error">Erreur</string>
+ <string name="error_message">Erreur : %s</string>
+ <!--key flags-->
+ <string name="flag_certify">Certifier</string>
+ <string name="flag_sign">Signer</string>
+ <string name="flag_encrypt">Chiffrer</string>
+ <string name="flag_authenticate">Authentifier</string>
+ <!--sentences-->
+ <string name="wrong_passphrase">Phrase de passe erronée</string>
+ <string name="using_clipboard_content">Utiliser le contenu du presse-papiers.</string>
+ <string name="set_a_passphrase">Définir d\'abord une phrase de passe.</string>
+ <string name="no_filemanager_installed">Aucun gestionnaire de fichiers compatible installé.</string>
+ <string name="passphrases_do_not_match">Les phrases de passe ne correspondent pas.</string>
+ <string name="passphrase_must_not_be_empty">Veuillez saisir une phrase de passe</string>
+ <string name="passphrase_for_symmetric_encryption">Chriffrement symétrique.</string>
+ <string name="passphrase_for">Saisir une phrase de passe pour « %s »</string>
+ <string name="file_delete_confirmation">Êtes-vous sûr de vouloir supprimer\n%s ?</string>
+ <string name="file_delete_successful">Supprimé avec succès.</string>
+ <string name="no_file_selected">Choisir d\'abord un fichier.</string>
+ <string name="decryption_successful">Déchiffré et/ou vérifié avec succès</string>
+ <string name="encryption_successful">Signé et/ou chiffré avec succès</string>
+ <string name="encryption_to_clipboard_successful">Signé et/ou chiffré vers le presse-papiers avec succès</string>
+ <string name="enter_passphrase_twice">Saisir la phrase de passe deux fois.</string>
+ <string name="select_encryption_key">Choisir au moins une clef de chiffrement.</string>
+ <string name="select_encryption_or_signature_key">Choisir au moins une clef de chiffrement ou de signature.</string>
+ <string name="specify_file_to_encrypt_to">Veuillez spécifier vers quel fichier chiffrer.\nAVERTISSEMENT ! Le fichier sera écrasé s\'il existe.</string>
+ <string name="specify_file_to_decrypt_to">Veuillez spécifier vers quel fichier déchiffrer.\nAVERTISSEMENT ! Le fichier sera écrasé s\'il existe.</string>
+ <string name="specify_file_to_export_to">Veuillez spécifier vers quel fichier exporter.\nAVERTISSEMENT ! Le fichier sera écrasé s\'il existe.</string>
+ <string name="specify_file_to_export_secret_keys_to">Veuillez spécifier vers quel fichier exporter.\nAVERTISSEMENT ! Vous allez exporter les clefs SECRÈTES.\nAVERTISSEMENT ! Le fichier sera écrasé s\'il existe.</string>
+ <string name="key_deletion_confirmation">Voulez-vous vraiment supprimer la clef %s ?\nVous ne pourrez pas la restituer !</string>
+ <string name="key_deletion_confirmation_multi">Voulez-vous vraiment supprimer toutes les clefs choisies ?\nCeci est irréversible !</string>
+ <string name="secret_key_deletion_confirmation">Voulez-vous vraiment supprimer la clef SECRÈTE %s ?\nVous ne pourrez pas la restituer !</string>
+ <string name="ask_save_changed_key">Vous avez apporté des changements au trousseau, voulez-vous l\'enregistrer ?</string>
+ <string name="ask_empty_id_ok">Vous avez ajouté un ID utilisateur vide, êtes-vous certain de vouloir continuer?</string>
+ <string name="public_key_deletetion_confirmation">Voulez-vous vraiment supprimer la clef PUBLIQUE « %s » ?\nVous ne pourrez pas la restituer !</string>
+ <string name="secret_key_delete_text">Supprimer les clefs privées ?</string>
+ <string name="also_export_secret_keys">Exporter aussi les clefs secrètes?</string>
+ <plurals name="keys_added_and_updated_1">
+ <item quantity="one">%d clef ajoutée avec succès</item>
+ <item quantity="other">%d clefs ajoutées avec succès</item>
+ </plurals>
+ <plurals name="keys_added_and_updated_2">
+ <item quantity="one">et %d clef mise à jour.</item>
+ <item quantity="other">et %d clefs mises à jour.</item>
+ </plurals>
+ <plurals name="keys_added">
+ <item quantity="one">%d clef ajoutée avec succès.</item>
+ <item quantity="other">%d clefs ajoutées avec succès.</item>
+ </plurals>
+ <plurals name="keys_updated">
+ <item quantity="one">%d clef mise à jour avec succès.</item>
+ <item quantity="other">%d clefs mises à jour avec succès.</item>
+ </plurals>
+ <string name="no_keys_added_or_updated">Aucune clef ajoutée ou mise à jour.</string>
+ <string name="key_exported">1 clef exportée avec succès.</string>
+ <string name="keys_exported">%d clefs exportées avec succès.</string>
+ <string name="no_keys_exported">Aucune clef exportée.</string>
+ <string name="key_creation_el_gamal_info">Note : seules les sous-clefs prennent en charge ElGamal, et pour ElGamal la taille de clef la plus proche de 1 536, 2 048, 3 072, 4 096 ou 8 192 sera utilisée.</string>
+ <string name="key_creation_weak_rsa_info">Note : générer des clefs RSA d\'une longueur de 1024 bits ou moins est considéré non sécuritaire et est désactivé pour la génération de nouvelles clefs.</string>
+ <string name="key_not_found">Clef %08X introuvable.</string>
+ <plurals name="keys_found">
+ <item quantity="one">%d clef trouvée.</item>
+ <item quantity="other">%d clefs trouvées.</item>
+ </plurals>
+ <string name="unknown_signature">Signature inconnue. Cliquer sur le bouton pour rechercher la clef manquante.</string>
+ <plurals name="bad_keys_encountered">
+ <item quantity="one">%d mauvaise clef ignorée. Vous avez peut-être exporté avec l\'option\n --export-secret-subkeys\nAssurez-vous d\'exporter plutôt avec\n --export-secret-keys.</item>
+ <item quantity="other">%d mauvaises clefs ignorées. Vous avez peut-être exporté avec l\'option\n --export-secret-subkeys\nAssurez-vous d\'exporter plutôt avec\n --export-secret-keys.</item>
+ </plurals>
+ <string name="key_send_success">Clef téléversée vers le serveur avec succès</string>
+ <string name="key_sign_success">Clef signée avec succès</string>
+ <string name="list_empty">Cette liste est vide !</string>
+ <string name="nfc_successfull">Clef envoyée par NFC BEAM avec succès !</string>
+ <string name="key_copied_to_clipboard">La clef a été copié vers le presse-papiers !</string>
+ <string name="key_has_already_been_signed">La clef a déjà été signée !</string>
+ <string name="select_key_to_sign">Veuillez choisir une clef a utiliser pour la signature !</string>
+ <string name="key_too_big_for_sharing">La clef est trop grosse pour être partagée ainsi !</string>
+ <!--errors
+ no punctuation, all lowercase,
+ they will be put after "error_message", e.g. "Error: file not found"-->
+ <string name="error_file_delete_failed">échec lors de la suppression de « %s »</string>
+ <string name="error_file_not_found">fichier introuvable</string>
+ <string name="error_no_secret_key_found">aucune clefs secrète adéquate n\'a été trouvée</string>
+ <string name="error_no_known_encryption_found">aucune sorte de chiffrement connu n\'a été trouvé</string>
+ <string name="error_external_storage_not_ready">le stockage externe n\'est pas prêt</string>
+ <string name="error_invalid_email">courriel « %s » invalide</string>
+ <string name="error_key_size_minimum512bit">la taille de la clef doit être d\'au moins 512 bits</string>
+ <string name="error_master_key_must_not_be_el_gamal">la clef maîtresse ne peut être une clef ElGama</string>
+ <string name="error_unknown_algorithm_choice">choix d\'algorhitme inconnu</string>
+ <string name="error_user_id_needs_a_name">vous devez spécifier un nom</string>
+ <string name="error_user_id_no_email">aucun courriel trouvé</string>
+ <string name="error_user_id_needs_an_email_address">vous devez spécifier une adresse courriel</string>
+ <string name="error_key_needs_a_user_id">vous avez besoin d\'au moins un ID utilisateur</string>
+ <string name="error_main_user_id_must_not_be_empty">l\'ID utilisateur principal ne doit pas être vide</string>
+ <string name="error_key_needs_master_key">au moins une clef maîtresse est nécessaire</string>
+ <string name="error_no_encryption_keys_or_passphrase">aucune clef ni phrase de passe n\'a été donnée</string>
+ <string name="error_signature_failed">échec lors de la signature</string>
+ <string name="error_no_signature_passphrase">aucune phrase de passe n\'a été donnée</string>
+ <string name="error_no_signature_key">aucune clef de signature n\'a été donnée</string>
+ <string name="error_invalid_data">aucune donnée de chiffrement valide</string>
+ <string name="error_corrupt_data">données corrompues</string>
+ <string name="error_integrity_check_failed">la vérification de l\'intégrité a échoué ! Les données ont été modifiées !</string>
+ <string name="error_no_symmetric_encryption_packet">paquet avec chiffrement symétrique introuvable</string>
+ <string name="error_wrong_passphrase">phrase de passe erronnée</string>
+ <string name="error_saving_keys">erreur lors de la sauvegarde de certaines clefs</string>
+ <string name="error_could_not_extract_private_key">impossible d\'extraire la clef privée</string>
+ <string name="error_only_files_are_supported">Les données binaires directes sans fichier dans le système de fichiers ne sont pas prises en charge. C\'est uniquement pris en charge par ACTION_ENCRYPT_STREAM_AND_RETURN.</string>
+ <string name="error_jelly_bean_needed">Vous devez avoir Android 4.1 Jelly Bean pour utiliser la fonction NFC Beam !</string>
+ <string name="error_nfc_needed">NFC n\'est pas disponible sur votre appareil !</string>
+ <string name="error_nothing_import">Rien à importer !</string>
+ <string name="error_expiry_must_come_after_creation">la date d\'expiration doit venir après la date de création</string>
+ <string name="error_save_first">veuillez d\'abord enregistrer le trousseau</string>
+ <string name="error_can_not_delete_contact">vous ne pouvez pas supprimer ce contact car c\'est le vôtre.</string>
+ <string name="error_can_not_delete_contacts">vous ne pouvez pas supprimer les contacts suivants car c\'est les vôtres.\n%s</string>
+ <string name="error_keyserver_insufficient_query">Requête serveur insuffisante</string>
+ <string name="error_keyserver_query">Échec lors de l\'interrogation du serveur de clefs</string>
+ <string name="error_keyserver_too_many_responses">Trop de réponses</string>
+ <string name="error_import_file_no_content">Le fichier n\'a pas de contenu</string>
+ <string name="error_generic_report_bug">Une erreur générique est survenue, veuillez créer un nouveau rapport de bogue pour OpenKeychain.</string>
+ <plurals name="error_can_not_delete_info">
+ <item quantity="one">Veuillez le supprimer depuis l\'écran « Mes Clefs »!</item>
+ <item quantity="other">Veuillez les supprimer depuis l\'écran « Mes Clefs »!</item>
+ </plurals>
+ <plurals name="error_import_non_pgp_part">
+ <item quantity="one">une partie du fichier chargé est un objet OpenPGP valide mais pas une clef OpenPGP</item>
+ <item quantity="other">certaines parties du fichier chargé sont des objets OpenPGP valides mais pas des clefs OpenPGP</item>
+ </plurals>
+ <string name="error_change_something_first">Vous devez apporter des changements au trousseau avant de pouvoir l\'enregistrer</string>
+ <!--progress dialogs, usually ending in '…'-->
+ <string name="progress_done">Terminé.</string>
+ <string name="progress_cancel">Annuler</string>
+ <string name="progress_saving">sauvegarde...</string>
+ <string name="progress_importing">importation...</string>
+ <string name="progress_exporting">exportation...</string>
+ <string name="progress_building_key">assemblage de la clef...</string>
+ <string name="progress_preparing_master_key">préparation de la clef maîtresse...</string>
+ <string name="progress_certifying_master_key">certification de la clef maîtresse...</string>
+ <string name="progress_building_master_key">assemblage du trousseau maître...</string>
+ <string name="progress_adding_sub_keys">ajout des sous-clefs...</string>
+ <string name="progress_saving_key_ring">sauvegarde de la clef...</string>
+ <plurals name="progress_exporting_key">
+ <item quantity="one">exportation de la clef...</item>
+ <item quantity="other">exportation des clefs...</item>
+ </plurals>
+ <plurals name="progress_generating">
+ <item quantity="one">génération de la clef, ceci peut prendre jusqu\'à 3 min...</item>
+ <item quantity="other">génération des clefs, ceci peut prendre jusqu\'à 3 min...</item>
+ </plurals>
+ <string name="progress_extracting_signature_key">extraction de la clef de signature...</string>
+ <string name="progress_extracting_key">extraction de la clef...</string>
+ <string name="progress_preparing_streams">préparation des flux...</string>
+ <string name="progress_encrypting">chiffrement des données...</string>
+ <string name="progress_decrypting">déchiffrement des données...</string>
+ <string name="progress_preparing_signature">préparation de la signature...</string>
+ <string name="progress_generating_signature">génération de la signature...</string>
+ <string name="progress_processing_signature">traitement de la signature...</string>
+ <string name="progress_verifying_signature">vérification de la signature...</string>
+ <string name="progress_signing">signature...</string>
+ <string name="progress_reading_data">lecture des données...</string>
+ <string name="progress_finding_key">recherche de la clef...</string>
+ <string name="progress_decompressing_data">décompression des données...</string>
+ <string name="progress_verifying_integrity">vérification de l\'intégrité...</string>
+ <string name="progress_deleting_securely">suppression sûre de « %s »...</string>
+ <string name="progress_querying">interrogation...</string>
+ <!--action strings-->
+ <string name="hint_public_keys">Rechercher des clefs publiques</string>
+ <string name="hint_secret_keys">Rechercher des clefs secrètes</string>
+ <string name="action_share_key_with">Partager la clef avec...</string>
+ <!--key bit length selections-->
+ <string name="key_size_512">512</string>
+ <string name="key_size_1024">1024</string>
+ <string name="key_size_2048">2048</string>
+ <string name="key_size_4096">4096</string>
+ <!--compression-->
+ <string name="compression_fast">rapide</string>
+ <string name="compression_very_slow">très lent</string>
+ <!--Help-->
+ <string name="help_tab_start">Commencer</string>
+ <string name="help_tab_faq">FAQ</string>
+ <string name="help_tab_nfc_beam">NFC Beam</string>
+ <string name="help_tab_changelog">Journal des changements</string>
+ <string name="help_tab_about">À propos de</string>
+ <string name="help_about_version">Version :</string>
+ <!--Import-->
+ <string name="import_import">Importer les clefs choisies</string>
+ <string name="import_sign_and_upload">Importer, signer et téléverser les clefs choisies</string>
+ <string name="import_from_clipboard">Importer à partir du presse-papiers</string>
+ <plurals name="import_qr_code_missing">
+ <item quantity="one">Le code QR avec l\'ID %s est manquant</item>
+ <item quantity="other">Les codes QR avec les IDs %s sont manquants</item>
+ </plurals>
+ <string name="import_qr_code_start_with_one">Veuillez commencer par le code QR avec l\'ID 1</string>
+ <string name="import_qr_code_wrong">Code QR incorrecte ! Veuillez réessayer !</string>
+ <string name="import_qr_code_finished">Balayage de code QR terminé !</string>
+ <string name="import_qr_code_too_short_fingerprint">L\'empreinte est trop courte (&lt; 16 caractères)</string>
+ <string name="import_qr_scan_button">Numériser le code QR avec le lecteur de code-barres</string>
+ <string name="import_nfc_text">Pour recevoir des clefs par NFC, les appareils doivent être déverrouillés.</string>
+ <string name="import_nfc_help_button">Aide</string>
+ <string name="import_clipboard_button">Obtenir la clef depuis le presse-papiers</string>
+ <!--Intent labels-->
+ <string name="intent_decrypt_file">Déchiffrer le fichier avec OpenKeychain</string>
+ <string name="intent_import_key">Importer la clef avec OpenKeychain</string>
+ <string name="intent_send_encrypt">Chiffrer avec OpenKeychain</string>
+ <string name="intent_send_decrypt">Déchiffrer avec OpenKeychain</string>
+ <!--Remote API-->
+ <string name="api_no_apps">Aucune application enregistrée !\n\nLes applications tierces peuvent demander l\'accès à OpenKeychain. Après avoir autorisé l\'accès, elles seront listées ici.</string>
+ <string name="api_settings_show_info">Afficher les informations avancées</string>
+ <string name="api_settings_hide_info">Masquer les informations avancées</string>
+ <string name="api_settings_show_advanced">Afficher les paramètres avancés</string>
+ <string name="api_settings_hide_advanced">Masquer les paramètres avancés</string>
+ <string name="api_settings_no_key">Aucune clef choisie</string>
+ <string name="api_settings_select_key">Choisir une clef</string>
+ <string name="api_settings_create_key">Créer une nouvelle clef pour ce compte</string>
+ <string name="api_settings_save">Enregistrer</string>
+ <string name="api_settings_cancel">Annuler</string>
+ <string name="api_settings_revoke">Révoquer l\'accès</string>
+ <string name="api_settings_delete_account">Supprimer le compte</string>
+ <string name="api_settings_package_name">Nom du paquet</string>
+ <string name="api_settings_package_signature">SHA-256 de la signature du paquet</string>
+ <string name="api_settings_accounts">Comptes</string>
+ <string name="api_settings_accounts_empty">Aucun compte n\'est attaché à cette application.</string>
+ <string name="api_create_account_text">L\'application demande la création d\'un nouveau compte. Veuillez choisir un clef privée existante ou en créer une.\nLes applications sont restreintes à l\'utilisation de clefs choisies ici.</string>
+ <string name="api_register_text">L\'application affichée demande l\'accès à OpenKeychain.\nPermettre l\'accès ?\n\nAvertissement : si vous ne savez pas pourquoi cet écran est apparu, refusez l\'accès ! Vous pourrez révoquer l\'accès plus tard en utilisant l\'écran « Applications enregistrées ».</string>
+ <string name="api_register_allow">Permettre l\'accès</string>
+ <string name="api_register_disallow">Enlever l\'accès</string>
+ <string name="api_register_error_select_key">Veuillez choisir une clef !</string>
+ <string name="api_select_pub_keys_missing_text">Aucune clef publique n\'a été trouvée pour ces IDs utilisateur :</string>
+ <string name="api_select_pub_keys_dublicates_text">Plus d\'une clef publique existe pour ces IDs utilisateur</string>
+ <string name="api_select_pub_keys_text">Veuillez revoir la liste des destinataires !</string>
+ <string name="api_error_wrong_signature">La vérification de la signature a échoué ! Avez-vous installé cette appli à partir d\'une source différente ? Si vous êtes sûr que ce n\'est pas une attaque, révoquez l\'enregistrement de cette appli dans OpenKeychain et enregistrez-la à nouveau.</string>
+ <!--Share-->
+ <string name="share_qr_code_dialog_title">Partager par un code QR</string>
+ <string name="share_qr_code_dialog_start">Balayer tous les codes QR un par un en utilisant « Suivant ».</string>
+ <string name="share_qr_code_dialog_fingerprint_text">Empreinte :</string>
+ <string name="share_qr_code_dialog_progress">Code QR avec l\'ID %1$d de %2$d</string>
+ <string name="share_nfc_dialog">Partager par la NFC</string>
+ <!--Key list-->
+ <plurals name="key_list_selected_keys">
+ <item quantity="one">1 clef choisie</item>
+ <item quantity="other">%d clefs choisies</item>
+ </plurals>
+ <string name="key_list_empty_text1">Aucune clef encore disponible...</string>
+ <string name="key_list_empty_text2">Vous pouvez commencer par</string>
+ <string name="key_list_empty_text3">ou</string>
+ <string name="key_list_empty_button_create">créer votre propre clef</string>
+ <string name="key_list_empty_button_import">Importer des clefs.</string>
+ <!--Key view-->
+ <string name="key_view_action_edit">Modifier cette clef</string>
+ <string name="key_view_action_encrypt">Chiffrer vers ce contact</string>
+ <string name="key_view_action_certify">Certifier la clef de ce contact</string>
+ <string name="key_view_tab_main">Infos</string>
+ <string name="key_view_tab_certs">Certifications</string>
+ <!--Navigation Drawer-->
+ <string name="nav_contacts">Clefs</string>
+ <string name="nav_encrypt">Signer et chiffrer</string>
+ <string name="nav_decrypt">Déchiffrer et vérifier</string>
+ <string name="nav_import">Importer les clefs</string>
+ <string name="nav_secret_keys">Mes clefs</string>
+ <string name="nav_apps">Applis enregistrées</string>
+ <string name="drawer_open">Ouvrir le tiroir de navigation</string>
+ <string name="drawer_close">Fermer le tiroir de navigation</string>
+ <string name="edit">Modifier</string>
+ <string name="my_keys">Mes clefs</string>
+ <string name="label_secret_key">Clef secrète</string>
+ <string name="secret_key_yes">disponible</string>
+ <string name="secret_key_no">non disponible</string>
+ <!--hints-->
+ <string name="encrypt_content_edit_text_hint">Écrire ici le message à chiffrer et/ou signer...</string>
+ <string name="decrypt_content_edit_text_hint">Saisir le cryptogramme à déchiffrer et/ou à vérifier ici...</string>
+ <!--unsorted-->
+ <string name="section_uids_to_sign">ID utilisateur pour signer</string>
+ <string name="progress_re_adding_certs">Nouvel application des certificats</string>
+</resources>
diff --git a/OpenKeychain/src/main/res/values-it-rIT/strings.xml b/OpenKeychain/src/main/res/values-it-rIT/strings.xml
new file mode 100644
index 000000000..4f2f67d33
--- /dev/null
+++ b/OpenKeychain/src/main/res/values-it-rIT/strings.xml
@@ -0,0 +1,451 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<resources>
+ <!--title-->
+ <string name="title_manage_public_keys">Contatti</string>
+ <string name="title_manage_secret_keys">Chiavi Private</string>
+ <string name="title_select_recipients">Seleziona Chiave Pubblica</string>
+ <string name="title_select_secret_key">Seleziona Chiave Privata</string>
+ <string name="title_encrypt">Codifica</string>
+ <string name="title_decrypt">Decodifica</string>
+ <string name="title_authentication">Frase di accesso</string>
+ <string name="title_create_key">Crea Chiave</string>
+ <string name="title_edit_key">Modifica Chiave</string>
+ <string name="title_preferences">Preferenze</string>
+ <string name="title_api_registered_apps">App Registrate</string>
+ <string name="title_key_server_preference">Preferenze Server delle Chiavi</string>
+ <string name="title_change_passphrase">Cambia Frase Di Accesso</string>
+ <string name="title_set_passphrase">Imposta Frase di Accesso</string>
+ <string name="title_send_email">Invia Mail...</string>
+ <string name="title_send_file">Invia file...</string>
+ <string name="title_encrypt_to_file">Codifica File</string>
+ <string name="title_decrypt_to_file">Decodifica File</string>
+ <string name="title_import_keys">Importa Chiavi</string>
+ <string name="title_export_key">Esporta Chiave</string>
+ <string name="title_export_keys">Esporta Chiavi</string>
+ <string name="title_key_not_found">Chiave Non Trovata</string>
+ <string name="title_key_server_query">Interroga Server delle Chiavi</string>
+ <string name="title_send_key">Carica sul Server delle Chiavi</string>
+ <string name="title_unknown_signature_key">Chiave di Firma Sconosciuta</string>
+ <string name="title_certify_key">Certifica Chiave</string>
+ <string name="title_key_details">Dettagli Chiave</string>
+ <string name="title_help">Aiuto</string>
+ <!--section-->
+ <string name="section_user_ids">ID Utente</string>
+ <string name="section_keys">Chiavi</string>
+ <string name="section_general">Generale</string>
+ <string name="section_defaults">Predefiniti</string>
+ <string name="section_advanced">Avanzato</string>
+ <string name="section_master_key">Chiave Principale</string>
+ <string name="section_master_user_id">ID Utente Principale</string>
+ <string name="section_actions">Azioni</string>
+ <string name="section_certification_key">La Tua Chiave usata per la certificazione</string>
+ <string name="section_upload_key">Carica Chiave</string>
+ <string name="section_key_server">Server delle Chiavi</string>
+ <string name="section_encrypt_and_or_sign">Codifica e/o Firma</string>
+ <string name="section_decrypt_verify">Decodifica e Verifica</string>
+ <!--button-->
+ <string name="btn_sign">Firma</string>
+ <string name="btn_certify">Certifica</string>
+ <string name="btn_decrypt">Decodifica</string>
+ <string name="btn_decrypt_verify">Decodifica e Verifica</string>
+ <string name="btn_decrypt_verify_clipboard">Dagli Appunti</string>
+ <string name="btn_select_encrypt_keys">Seleziona Destinatari</string>
+ <string name="btn_encrypt_file">Codifica File</string>
+ <string name="btn_save">Salva</string>
+ <string name="btn_do_not_save">Annulla</string>
+ <string name="btn_delete">Elimina</string>
+ <string name="btn_no_date">Nessuno</string>
+ <string name="btn_okay">OK</string>
+ <string name="btn_change_passphrase">Cambia Nuova Frase di Accesso</string>
+ <string name="btn_set_passphrase">Imposta Nuova Frase di Accesso</string>
+ <string name="btn_search">Cerca</string>
+ <string name="btn_export_to_server">Carica sul Server delle Chiavi</string>
+ <string name="btn_next">Prossimo</string>
+ <string name="btn_back">Precedente</string>
+ <string name="btn_clipboard">Appunti</string>
+ <string name="btn_share">Condividi...</string>
+ <string name="btn_lookup_key">Chiave di ricerca</string>
+ <string name="btn_encryption_advanced_settings_show">Mostra impostazioni avanzate</string>
+ <string name="btn_encryption_advanced_settings_hide">Nascondi impostazioni avanzate</string>
+ <!--menu-->
+ <string name="menu_preferences">Impostazioni</string>
+ <string name="menu_help">Aiuto</string>
+ <string name="menu_import_from_file">Importa da file</string>
+ <string name="menu_import_from_qr_code">Importa da Codice QR</string>
+ <string name="menu_import">Importa</string>
+ <string name="menu_import_from_nfc">Importa tramite NFC</string>
+ <string name="menu_export_key">Esporta su un file</string>
+ <string name="menu_delete_key">Cancella chiave</string>
+ <string name="menu_create_key">Crea chiave</string>
+ <string name="menu_create_key_expert">Crea chiave (avanzato)</string>
+ <string name="menu_search">Cerca</string>
+ <string name="menu_import_from_key_server">Server delle Chiavi</string>
+ <string name="menu_key_server">Server delle Chiavi...</string>
+ <string name="menu_update_key">Aggiorna dal server delle chiavi</string>
+ <string name="menu_export_key_to_server">Carica chiave nel server</string>
+ <string name="menu_share">Condividi...</string>
+ <string name="menu_share_title_fingerprint">Condivi impronta...</string>
+ <string name="menu_share_title">Condividi intera chiave...</string>
+ <string name="menu_share_default_fingerprint">con..</string>
+ <string name="menu_share_default">con...</string>
+ <string name="menu_share_qr_code">con Codice QR</string>
+ <string name="menu_share_qr_code_fingerprint">con Codice QR</string>
+ <string name="menu_share_nfc">con NFC</string>
+ <string name="menu_copy_to_clipboard">Copia negli appunti</string>
+ <string name="menu_sign_key">Firma chiave</string>
+ <string name="menu_beam_preferences">Impostazioni Beam</string>
+ <string name="menu_key_edit_cancel">Annulla</string>
+ <string name="menu_encrypt_to">Codifica su...</string>
+ <string name="menu_select_all">Seleziona tutto</string>
+ <string name="menu_add_keys">Aggiungi chiavi</string>
+ <!--label-->
+ <string name="label_sign">Firma</string>
+ <string name="label_message">Messaggio</string>
+ <string name="label_file">File</string>
+ <string name="label_no_passphrase">Nessuna Frase di Accesso</string>
+ <string name="label_passphrase">Frase di Accesso</string>
+ <string name="label_passphrase_again">Di nuovo</string>
+ <string name="label_algorithm">Algortimo</string>
+ <string name="label_ascii_armor">Armatura ASCII</string>
+ <string name="label_select_public_keys">Destinatari</string>
+ <string name="label_delete_after_encryption">Cancella Dopo Codifica</string>
+ <string name="label_delete_after_decryption">Cancella Dopo Decodifica</string>
+ <string name="label_share_after_encryption">Condividi Dopo la Codifica</string>
+ <string name="label_encryption_algorithm">Algoritmo di Codifica</string>
+ <string name="label_hash_algorithm">Algoritmo di Hash</string>
+ <string name="label_asymmetric">con Chiave Pubblica</string>
+ <string name="label_symmetric">con Frase di Accesso</string>
+ <string name="label_passphrase_cache_ttl">Cache Frase di Accesso</string>
+ <string name="label_message_compression">Compressione Messaggio</string>
+ <string name="label_file_compression">Compressione File</string>
+ <string name="label_force_v3_signature">Forza vecchie Firme OpenPGPv3</string>
+ <string name="label_key_servers">Server Chiavi</string>
+ <string name="label_key_id">ID Chiave</string>
+ <string name="label_creation">Creazione</string>
+ <string name="label_expiry">Scadenza</string>
+ <string name="label_usage">Utilizzo</string>
+ <string name="label_key_size">Dimensione Chiave</string>
+ <string name="label_main_user_id">ID Utente Principale</string>
+ <string name="label_name">Nome</string>
+ <string name="label_comment">Commento</string>
+ <string name="label_email">Email</string>
+ <string name="label_send_key">Carica chiave nel server delle chiavi selezionati dopo la certificazione</string>
+ <string name="label_fingerprint">Impronta</string>
+ <string name="select_keys_button_default">Seleziona</string>
+ <string name="expiry_date_dialog_title">Impostare la data di scadenza</string>
+ <plurals name="select_keys_button">
+ <item quantity="one">%d selezionato</item>
+ <item quantity="other">%d selezionati</item>
+ </plurals>
+ <string name="user_id_no_name">&lt;nessun nome&gt;</string>
+ <string name="none">&lt;nessuno&gt;</string>
+ <string name="no_key">&lt;nessuna chiave&gt;</string>
+ <string name="no_email">&lt;Nessuna Email&gt;</string>
+ <string name="unknown_status"></string>
+ <string name="can_encrypt">puo\' codificare</string>
+ <string name="can_sign">puo\' firmare</string>
+ <string name="expired">scaduto</string>
+ <string name="revoked">revocato</string>
+ <string name="user_id">ID Utente</string>
+ <plurals name="n_contacts">
+ <item quantity="one">1 contatto</item>
+ <item quantity="other">%d contatti</item>
+ </plurals>
+ <plurals name="n_key_servers">
+ <item quantity="one">%d server delle chiavi</item>
+ <item quantity="other">%d server delle chiavi</item>
+ </plurals>
+ <string name="fingerprint">Impronta:</string>
+ <string name="secret_key">Chiave Privata:</string>
+ <!--choice-->
+ <string name="choice_none">Nessuno</string>
+ <string name="choice_15secs">15 sec</string>
+ <string name="choice_1min">1 min</string>
+ <string name="choice_3mins">3 min</string>
+ <string name="choice_5mins">5 min</string>
+ <string name="choice_10mins">10 min</string>
+ <string name="choice_20mins">20 min</string>
+ <string name="choice_40mins">40 min</string>
+ <string name="choice_1hour">1 ora</string>
+ <string name="choice_2hours">2 ore</string>
+ <string name="choice_4hours">4 ore</string>
+ <string name="choice_8hours">8 ore</string>
+ <string name="choice_forever">sempre</string>
+ <string name="dsa">DSA</string>
+ <string name="elgamal">ElGamal</string>
+ <string name="rsa">RSA</string>
+ <string name="filemanager_title_open">Apri...</string>
+ <string name="warning">Attenzione</string>
+ <string name="error">Errore</string>
+ <string name="error_message">Errore: %s</string>
+ <!--key flags-->
+ <string name="flag_certify">Certifica</string>
+ <string name="flag_sign">Firma</string>
+ <string name="flag_encrypt">Codifica</string>
+ <string name="flag_authenticate">Convalida</string>
+ <!--sentences-->
+ <string name="wrong_passphrase">Frase di Accesso errata</string>
+ <string name="using_clipboard_content">Utilizzo il contenuto degli appunti.</string>
+ <string name="set_a_passphrase">Imposta prima una frase di accesso.</string>
+ <string name="no_filemanager_installed">Nessun gestore file compatibile installato.</string>
+ <string name="passphrases_do_not_match">Le frasi di accesso non corrispondono.</string>
+ <string name="passphrase_must_not_be_empty">Si prega di inserire una frase di accesso.</string>
+ <string name="passphrase_for_symmetric_encryption">Codifica Simmetrica.</string>
+ <string name="passphrase_for">Inserisci la frase di accesso per \'%s\'</string>
+ <string name="file_delete_confirmation">Sei sicuro di voler cancellare\n%s?</string>
+ <string name="file_delete_successful">Eliminato correttamente.</string>
+ <string name="no_file_selected">Seleziona un file prima.</string>
+ <string name="decryption_successful">Decodifica e/o verifica eseguita con successo.</string>
+ <string name="encryption_successful">Firmato e/o codificato con successo.</string>
+ <string name="encryption_to_clipboard_successful">Firmato e/o codificato con successo negli appunti.</string>
+ <string name="enter_passphrase_twice">Inserisci la frase di accesso due volte.</string>
+ <string name="select_encryption_key">Seleziona almeno una chiave di codifica.</string>
+ <string name="select_encryption_or_signature_key">Seleziona almeno una chiave di codifica o di firma.</string>
+ <string name="specify_file_to_encrypt_to">Perfavore specifica quale file codificare.\nATTENZIONE: Il file sara\' sovrascritto se esistente.</string>
+ <string name="specify_file_to_decrypt_to">Perfavore specifica quale file decodificare.\nATTENZIONE: Il file sara\' sovrascritto se esistente.</string>
+ <string name="specify_file_to_export_to">Perfavore specifica su quale file esportare.\nATTENZIONE: Il file sara\' sovrascritto se esistente.</string>
+ <string name="specify_file_to_export_secret_keys_to">Perfavore specifica quale file esportare.\nATTENZIONE: Stai esportanto chiavi PRIVATE.\nATTENZIONE: Il file sara\' sovrascritto se esistente.</string>
+ <string name="key_deletion_confirmation">Vuoi veramente eliminare la chiave \'%s\'?\nNon potrai annullare!</string>
+ <string name="key_deletion_confirmation_multi">Vuoi veramente eliminare le chiavi selezionate?\nNon potrai annullare!</string>
+ <string name="secret_key_deletion_confirmation">Vuoi veramente eliminare la chiave PRIVATA \'%s\'?\nNon potrai annullare!</string>
+ <string name="ask_save_changed_key">Hai apportato modifiche al tuo portachiavi, vuoi salvarlo?</string>
+ <string name="ask_empty_id_ok">È stato aggiunto un ID utente vuoto, sei sicuro di voler continuare?</string>
+ <string name="public_key_deletetion_confirmation">Vuoi veramente eliminare la chiave PUBBLICA \'%s\'?\nNon potrai annullare!</string>
+ <string name="secret_key_delete_text">Eliminare le Chiavi Segrete?</string>
+ <string name="also_export_secret_keys">Esportare anche le chiavi segrete?</string>
+ <plurals name="keys_added_and_updated_1">
+ <item quantity="one">%d chiave aggiunta correttamente</item>
+ <item quantity="other">%d chiavi aggiunte correttamente</item>
+ </plurals>
+ <plurals name="keys_added_and_updated_2">
+ <item quantity="one">e %d chiave aggiornata.</item>
+ <item quantity="other">e %d chiavi aggiornate.</item>
+ </plurals>
+ <plurals name="keys_added">
+ <item quantity="one">%d chiave aggiunta correttamente.</item>
+ <item quantity="other">%d chiavi aggiunte correttamente.</item>
+ </plurals>
+ <plurals name="keys_updated">
+ <item quantity="one">%d chiave aggiornata correttamente.</item>
+ <item quantity="other">%d chiavi aggiornate correttamente.</item>
+ </plurals>
+ <string name="no_keys_added_or_updated">Nessuna chiave aggiunta o aggiornata.</string>
+ <string name="key_exported">1 chiave esportata correttamente.</string>
+ <string name="keys_exported">%d chiavi esportate correttamente.</string>
+ <string name="no_keys_exported">Nessuna chiave esportata.</string>
+ <string name="key_creation_el_gamal_info">Nota: solo le sottochiavi supportano ElGamal, e per ElGamal verra\' usata la grandezza chiave piu\' vicina a 1536, 2048, 3072, 4096 o 8192.</string>
+ <string name="key_creation_weak_rsa_info">Nota: la generazione di chiavi RSA con lunghezza pari a 1024 bit o inferiore è considerata non sicura ed è disabilitata per la generazione di nuove chiavi.</string>
+ <string name="key_not_found">Impossibile trovare la chiave %08X.</string>
+ <plurals name="keys_found">
+ <item quantity="one">Trovata %d chiave.</item>
+ <item quantity="other">Trovate %d chiavi.</item>
+ </plurals>
+ <string name="unknown_signature">Firma sconosciuta, clicca il pulsante per ricercare la chiave mancante.</string>
+ <plurals name="bad_keys_encountered">
+ <item quantity="one">%d chiave segreta non valida ignorata. Forse hai esportato con opzione\n--export-secret-subkeys\nAssicurati di esportare con\n--export-secret-keys\ninvece.</item>
+ <item quantity="other">%d chiavi private non valide ignorate. Forse hai esportato con opzione\n--export-secret-subkeys\nAssicurati di esportare con\n--export-secret-keys\ninvece.</item>
+ </plurals>
+ <string name="key_send_success">Chiave caricata con successo sul server</string>
+ <string name="key_sign_success">Chiave firmata correttamente</string>
+ <string name="list_empty">Lista vuota!</string>
+ <string name="nfc_successfull">Chiave inviata tramite NFC Beam!</string>
+ <string name="key_copied_to_clipboard">Chiave copiata negli appunti!</string>
+ <string name="key_has_already_been_signed">La chiave e\' gia\' firmata!</string>
+ <string name="select_key_to_sign">Per favore seleziona la chiave per la firma!</string>
+ <string name="key_too_big_for_sharing">Chiave troppo grande per essere condivisa in questo modo!</string>
+ <!--errors
+ no punctuation, all lowercase,
+ they will be put after "error_message", e.g. "Error: file not found"-->
+ <string name="error_file_delete_failed">Cancellazione di \'%s\' fallita</string>
+ <string name="error_file_not_found">File non trovato</string>
+ <string name="error_no_secret_key_found">nessuna chiave privata adatta trovata</string>
+ <string name="error_no_known_encryption_found">nessun tipo conosciuto di codifica trovata</string>
+ <string name="error_external_storage_not_ready">memoria esterna non pronta</string>
+ <string name="error_invalid_email">email non valida \'%s\'</string>
+ <string name="error_key_size_minimum512bit">La grandezza della chiave deve essere almeno di 512bit</string>
+ <string name="error_master_key_must_not_be_el_gamal">La chiave principale non puo\' essere ElGamal</string>
+ <string name="error_unknown_algorithm_choice">opzione algoritmo sconosciuta</string>
+ <string name="error_user_id_needs_a_name">devi specificare un nome</string>
+ <string name="error_user_id_no_email">Nessuna email trovata</string>
+ <string name="error_user_id_needs_an_email_address">devi specificare un indirizzo email</string>
+ <string name="error_key_needs_a_user_id">necessario almeno un id utente</string>
+ <string name="error_main_user_id_must_not_be_empty">id utente principale non puo\' essere vuoto</string>
+ <string name="error_key_needs_master_key">necessaria almeno una chiave principale</string>
+ <string name="error_no_encryption_keys_or_passphrase">nessuna chiave di codifica o frase di accesso fornita</string>
+ <string name="error_signature_failed">firma fallita</string>
+ <string name="error_no_signature_passphrase">nessuna frase di accesso inserita</string>
+ <string name="error_no_signature_key">nessuna chiave di firma inserita</string>
+ <string name="error_invalid_data">dati di codifica non validi</string>
+ <string name="error_corrupt_data">dati corrotti</string>
+ <string name="error_integrity_check_failed">Controllo di integrita\' fallito! I dati sono stati modificati!</string>
+ <string name="error_no_symmetric_encryption_packet">impossibile trovare una pacchetto con codifica simmetrica</string>
+ <string name="error_wrong_passphrase">frase di accesso errata</string>
+ <string name="error_saving_keys">errore nel salvataggio di alcune chiavi</string>
+ <string name="error_could_not_extract_private_key">impossibile estrarre la chiave privata</string>
+ <string name="error_only_files_are_supported">Flusso di dati diretto senza file corrispettivo nel filesystem non e\' supportato. Supportato soltanto da ACTION_ENCRYPT_STREAM_AND_RETURN.</string>
+ <string name="error_jelly_bean_needed">Devi avere Android 4.1 alias Jelly Bean per usare Android NFC Beam!</string>
+ <string name="error_nfc_needed">NFC non disponibile nel tuo dispositivo!</string>
+ <string name="error_nothing_import">Niente da importare!</string>
+ <string name="error_expiry_must_come_after_creation">La data di scadenza deve essere postuma quella di creazione</string>
+ <string name="error_save_first">si prega di salvare il portachiavi primo</string>
+ <string name="error_can_not_delete_contact">Non è possibile eliminare questo contatto, perché è il proprio.</string>
+ <string name="error_can_not_delete_contacts">Non è possibile eliminare i seguenti contatti perché sono i propri:\n%s</string>
+ <string name="error_keyserver_insufficient_query">Query di server insufficiente</string>
+ <string name="error_keyserver_query">Interrogazione del server delle chiavi fallita</string>
+ <string name="error_keyserver_too_many_responses">Troppi responsi</string>
+ <string name="error_import_file_no_content">Il File non ha contenuti</string>
+ <string name="error_generic_report_bug">Si è verificato un errore generico, si prega di creare una nuova segnalazione di errore per OpenKeychain.</string>
+ <plurals name="error_can_not_delete_info">
+ <item quantity="one">Per favore cancellala dalla schermata \'Mie Chavi\'</item>
+ <item quantity="other">Per favore cancellatele dalla schermata \'Mie Chavi\'</item>
+ </plurals>
+ <plurals name="error_import_non_pgp_part">
+ <item quantity="one">parte del file caricato e\' un oggetto OpenPGP valido, ma non una chave OpenPGP</item>
+ <item quantity="other">parti del file caricato sono oggetti OpenPGP validi, ma non chavi OpenPGP</item>
+ </plurals>
+ <string name="error_change_something_first">È necessario apportare modifiche al portachiavi prima prima che sia possibile salvarlo</string>
+ <!--progress dialogs, usually ending in '…'-->
+ <string name="progress_done">Fatto.</string>
+ <string name="progress_cancel">Annulla</string>
+ <string name="progress_saving">salvataggio...</string>
+ <string name="progress_importing">importazione...</string>
+ <string name="progress_exporting">esportazione...</string>
+ <string name="progress_building_key">fabbricazione chiave...</string>
+ <string name="progress_preparing_master_key">preparazione chiave principale...</string>
+ <string name="progress_certifying_master_key">certificazione chiave principale...</string>
+ <string name="progress_building_master_key">fabbricazione portachiavi principale...</string>
+ <string name="progress_adding_sub_keys">aggiunta sottochiavi...</string>
+ <string name="progress_saving_key_ring">salvataggio chiavi...</string>
+ <plurals name="progress_exporting_key">
+ <item quantity="one">esportazione chiave...</item>
+ <item quantity="other">esportazione chiavi...</item>
+ </plurals>
+ <plurals name="progress_generating">
+ <item quantity="one">generazione chiave, sono necessari fino a 3 minuti...</item>
+ <item quantity="other">generazione chiavi, sono necessari fino a 3 minuti...</item>
+ </plurals>
+ <string name="progress_extracting_signature_key">estrazione chiavi di firma...</string>
+ <string name="progress_extracting_key">estrazione chiave...</string>
+ <string name="progress_preparing_streams">preparazione flussi...</string>
+ <string name="progress_encrypting">codifica dati...</string>
+ <string name="progress_decrypting">decodifica dati...</string>
+ <string name="progress_preparing_signature">preparazione firma...</string>
+ <string name="progress_generating_signature">generazione firma...</string>
+ <string name="progress_processing_signature">elaborazione firma...</string>
+ <string name="progress_verifying_signature">verifica firma...</string>
+ <string name="progress_signing">firma...</string>
+ <string name="progress_reading_data">lettura dati...</string>
+ <string name="progress_finding_key">ricerca chiave...</string>
+ <string name="progress_decompressing_data">decompressione dati...</string>
+ <string name="progress_verifying_integrity">verifica integrita\'...</string>
+ <string name="progress_deleting_securely">eliminazione sicura di \'%s\'...</string>
+ <string name="progress_querying">interrogazione...</string>
+ <!--action strings-->
+ <string name="hint_public_keys">Ricerca Chiavi Pubbliche</string>
+ <string name="hint_secret_keys">Cerca Chiave Privata</string>
+ <string name="action_share_key_with">Condividi chiave con...</string>
+ <!--key bit length selections-->
+ <string name="key_size_512">512</string>
+ <string name="key_size_1024">1024</string>
+ <string name="key_size_2048">2048</string>
+ <string name="key_size_4096">4096</string>
+ <!--compression-->
+ <string name="compression_fast">veloce</string>
+ <string name="compression_very_slow">molto lento</string>
+ <!--Help-->
+ <string name="help_tab_start">Inizia</string>
+ <string name="help_tab_faq">FAQ</string>
+ <string name="help_tab_nfc_beam">NFC Beam</string>
+ <string name="help_tab_changelog">Novita\'</string>
+ <string name="help_tab_about">Info</string>
+ <string name="help_about_version">Versione:</string>
+ <!--Import-->
+ <string name="import_import">Importa chiavi selezionate</string>
+ <string name="import_sign_and_upload">Importa, Firma e carica le chiavi selezionate</string>
+ <string name="import_from_clipboard">Importa dagli appunti</string>
+ <plurals name="import_qr_code_missing">
+ <item quantity="one">Codice QR con ID %s mancante</item>
+ <item quantity="other">Codici QR con ID %s mancanti</item>
+ </plurals>
+ <string name="import_qr_code_start_with_one">Perfavore inizia col Codice QR con ID 1</string>
+ <string name="import_qr_code_wrong">Codica QR deformato! Prova di nuovo!</string>
+ <string name="import_qr_code_finished">Scansione codice QR completata!</string>
+ <string name="import_qr_code_too_short_fingerprint">Impronta troppo corta (&lt; 16 caratteri)</string>
+ <string name="import_qr_scan_button">Scansiona il Codice QR con \'Barcode Scanner\'</string>
+ <string name="import_nfc_text">Per ricevere le chiavi via NFC, il dispositivo deve essere sbloccato.</string>
+ <string name="import_nfc_help_button">Aiuto</string>
+ <string name="import_clipboard_button">Ottieni chiave dagli appunti</string>
+ <!--Intent labels-->
+ <string name="intent_decrypt_file">Decodifica File con OpenKeychain</string>
+ <string name="intent_import_key">Importa Chiave con OpenKeychain</string>
+ <string name="intent_send_encrypt">Codifica con OpenKeychain</string>
+ <string name="intent_send_decrypt">Decodifica con OpenKeychain</string>
+ <!--Remote API-->
+ <string name="api_no_apps">Nessuna app registrata!\n\nApp di terze parti possono richiedere l\'accesso a OpenKeychain. Dopo aver concesso l\'accesso, le app saranno elencate qui.</string>
+ <string name="api_settings_show_info">Mostra informazioni dettagliate</string>
+ <string name="api_settings_hide_info">Nascondi informazioni dettagliate</string>
+ <string name="api_settings_show_advanced">Mostra impostazioni avanzate</string>
+ <string name="api_settings_hide_advanced">Nascondi impostazioni avanzate</string>
+ <string name="api_settings_no_key">Nessuna chiave selezionata</string>
+ <string name="api_settings_select_key">Seleziona chiave</string>
+ <string name="api_settings_create_key">Crea una nuova chiave per questo account</string>
+ <string name="api_settings_save">Salva</string>
+ <string name="api_settings_cancel">Annulla</string>
+ <string name="api_settings_revoke">Revoca accesso</string>
+ <string name="api_settings_delete_account">Cancella account</string>
+ <string name="api_settings_package_name">Nome Pacchetto</string>
+ <string name="api_settings_package_signature">SHA-256 della Firma del Pacchetto</string>
+ <string name="api_settings_accounts">Account</string>
+ <string name="api_settings_accounts_empty">Nessun account collegato a questa applicazione</string>
+ <string name="api_create_account_text">L\'applicazione richiede la creazione di un nuovo account. Si prega di selezionare una chiave privata esistente o crearne una nuova.\nLe applicazioni sono limitate all\'utilizzo delle chiavi selezionate qui!</string>
+ <string name="api_register_text">Le app visualizzate hanno richiesto l\'accesso a OpenKeychain.\nPermetti accesso?\n\nATTENZIONE: Se non sai perche\' questo schermata e\' apparsa, nega l\'accesso! Puoi revocare l\'accesso dopo, usando la schermata \'App Registrate\'.</string>
+ <string name="api_register_allow">Permetti accesso</string>
+ <string name="api_register_disallow">Nega accesso</string>
+ <string name="api_register_error_select_key">Per favore selezionare una chiave!</string>
+ <string name="api_select_pub_keys_missing_text">Nessuna chiave pubblica trovata per id utente:</string>
+ <string name="api_select_pub_keys_dublicates_text">Esistono piu\' di una chiave pubblica per gli id utenti:</string>
+ <string name="api_select_pub_keys_text">Per favore ricontrolla la lista destinatari!</string>
+ <string name="api_error_wrong_signature">Controllo della firma fallito! Hai installato questa app da una fonte diversa? Se sei sicuro che non sia un attacco, revoca la registrazione di questa app in OpenKeychain e dopo registra di nuovo l\'app.</string>
+ <!--Share-->
+ <string name="share_qr_code_dialog_title">Condividi tramite Codice QR</string>
+ <string name="share_qr_code_dialog_start">Scorri tutti i Codici QR usando \'Prossimo\', a scansionali uno ad uno.</string>
+ <string name="share_qr_code_dialog_fingerprint_text">Impronta:</string>
+ <string name="share_qr_code_dialog_progress">Codice QR con ID %1$d di %2$d</string>
+ <string name="share_nfc_dialog">Condividi tramite NFC</string>
+ <!--Key list-->
+ <plurals name="key_list_selected_keys">
+ <item quantity="one">1 chiave selezionata.</item>
+ <item quantity="other">%d chiavi selezionate.</item>
+ </plurals>
+ <string name="key_list_empty_text1">Nessuna chiave disponibile...</string>
+ <string name="key_list_empty_text2">Puoi iniziare da</string>
+ <string name="key_list_empty_text3">o</string>
+ <string name="key_list_empty_button_create">creazione della tua chiave</string>
+ <string name="key_list_empty_button_import">importazione chiavi.</string>
+ <!--Key view-->
+ <string name="key_view_action_edit">Modifica chiave</string>
+ <string name="key_view_action_encrypt">Codifica a questo contatto</string>
+ <string name="key_view_action_certify">Certifica la chiave di questo contatto</string>
+ <string name="key_view_tab_main">Info</string>
+ <string name="key_view_tab_certs">Certificazioni</string>
+ <!--Navigation Drawer-->
+ <string name="nav_contacts">Chiavi</string>
+ <string name="nav_encrypt">Firma e Codifica</string>
+ <string name="nav_decrypt">Decodifica e Verifica</string>
+ <string name="nav_import">Importa Chiavi</string>
+ <string name="nav_secret_keys">Le Mie Chiavi</string>
+ <string name="nav_apps">App Registrate</string>
+ <string name="drawer_open">Apri drawer di navigazione</string>
+ <string name="drawer_close">Chiudi drawer di navigazione</string>
+ <string name="edit">Modifica</string>
+ <string name="my_keys">Le Mie Chiavi</string>
+ <string name="label_secret_key">Chiave Segreta</string>
+ <string name="secret_key_yes">disponibile</string>
+ <string name="secret_key_no">non disponibile</string>
+ <!--hints-->
+ <string name="encrypt_content_edit_text_hint">Scrivi qui il messaggio da codificare e/o firmare...</string>
+ <string name="decrypt_content_edit_text_hint">Inserisci il testo cifrato qui per la decodifica e/o verifica...</string>
+ <!--unsorted-->
+ <string name="section_uids_to_sign">ID Utente da firmare</string>
+ <string name="progress_re_adding_certs">Riapplicazione certificati</string>
+</resources>
diff --git a/OpenKeychain/src/main/res/values-ja/strings.xml b/OpenKeychain/src/main/res/values-ja/strings.xml
new file mode 100644
index 000000000..2cd0630a8
--- /dev/null
+++ b/OpenKeychain/src/main/res/values-ja/strings.xml
@@ -0,0 +1,436 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<resources>
+ <!--title-->
+ <string name="title_manage_public_keys">連絡先</string>
+ <string name="title_manage_secret_keys">秘密鍵</string>
+ <string name="title_select_recipients">公開鍵の選択</string>
+ <string name="title_select_secret_key">秘密鍵の選択</string>
+ <string name="title_encrypt">暗号化</string>
+ <string name="title_decrypt">復号化</string>
+ <string name="title_authentication">パスフレーズ</string>
+ <string name="title_create_key">鍵の生成</string>
+ <string name="title_edit_key">鍵の編集</string>
+ <string name="title_preferences">設定</string>
+ <string name="title_api_registered_apps">登録済みのアプリケーション</string>
+ <string name="title_key_server_preference">鍵サーバ設定</string>
+ <string name="title_change_passphrase">パスフレーズの変更</string>
+ <string name="title_set_passphrase">パスフレーズの設定</string>
+ <string name="title_send_email">メールの送信...</string>
+ <string name="title_send_file">ファイルの送信...</string>
+ <string name="title_encrypt_to_file">暗号化してファイルに</string>
+ <string name="title_decrypt_to_file">復号化してファイルに</string>
+ <string name="title_import_keys">鍵のインポート</string>
+ <string name="title_export_key">鍵のエクスポート</string>
+ <string name="title_export_keys">複数鍵のエクスポート</string>
+ <string name="title_key_not_found">鍵が見当りません</string>
+ <string name="title_key_server_query">鍵サーバへの要求</string>
+ <string name="title_send_key">鍵サーバへアップロード</string>
+ <string name="title_unknown_signature_key">不明な署名の鍵です</string>
+ <string name="title_certify_key">鍵検証</string>
+ <string name="title_key_details">鍵の概要</string>
+ <string name="title_help">ヘルプ</string>
+ <!--section-->
+ <string name="section_user_ids">ユーザID</string>
+ <string name="section_keys">鍵</string>
+ <string name="section_general">一般</string>
+ <string name="section_defaults">デフォルト</string>
+ <string name="section_advanced">拡張</string>
+ <string name="section_master_key">主鍵</string>
+ <string name="section_master_user_id">主ユーザID</string>
+ <string name="section_actions">アクション</string>
+ <string name="section_certification_key">あなたの鍵を証明に利用します</string>
+ <string name="section_upload_key">鍵のアップロード</string>
+ <string name="section_key_server">鍵サーバ</string>
+ <string name="section_encrypt_and_or_sign">暗号化/署名</string>
+ <string name="section_decrypt_verify">復号化と検証</string>
+ <!--button-->
+ <string name="btn_sign">署名</string>
+ <string name="btn_certify">検証</string>
+ <string name="btn_decrypt">復号化</string>
+ <string name="btn_decrypt_verify">復号化と検証</string>
+ <string name="btn_decrypt_verify_clipboard">クリップボードから</string>
+ <string name="btn_select_encrypt_keys">受信者の選択</string>
+ <string name="btn_encrypt_file">ファイル暗号化</string>
+ <string name="btn_save">保存</string>
+ <string name="btn_do_not_save">キャンセル</string>
+ <string name="btn_delete">削除</string>
+ <string name="btn_no_date">無し</string>
+ <string name="btn_okay">OK</string>
+ <string name="btn_change_passphrase">新しいパスフレーズに変更</string>
+ <string name="btn_set_passphrase">新しいパスフレーズを設定</string>
+ <string name="btn_search">検索</string>
+ <string name="btn_export_to_server">鍵サーバへアップロード</string>
+ <string name="btn_next">次</string>
+ <string name="btn_back">戻る</string>
+ <string name="btn_clipboard">クリップボード</string>
+ <string name="btn_share">...で共有</string>
+ <string name="btn_lookup_key">鍵検出</string>
+ <string name="btn_encryption_advanced_settings_show">拡張設定を表示</string>
+ <string name="btn_encryption_advanced_settings_hide">拡張設定を隠す</string>
+ <!--menu-->
+ <string name="menu_preferences">設定</string>
+ <string name="menu_help">ヘルプ</string>
+ <string name="menu_import_from_file">ファイルからインポート</string>
+ <string name="menu_import_from_qr_code">QRコードからインポート</string>
+ <string name="menu_import">インポート</string>
+ <string name="menu_import_from_nfc">NFCからインポート</string>
+ <string name="menu_export_key">ファイルへのエクスポート</string>
+ <string name="menu_delete_key">鍵の削除</string>
+ <string name="menu_create_key">鍵の生成</string>
+ <string name="menu_create_key_expert">鍵の生成(上級)</string>
+ <string name="menu_search">検索</string>
+ <string name="menu_import_from_key_server">鍵サーバ</string>
+ <string name="menu_key_server">鍵サーバ...</string>
+ <string name="menu_update_key">鍵サーバからの更新</string>
+ <string name="menu_export_key_to_server">鍵サーバへのアップロード</string>
+ <string name="menu_share">共有...</string>
+ <string name="menu_share_title_fingerprint">指紋の共有...</string>
+ <string name="menu_share_title">すべての鍵の共有...</string>
+ <string name="menu_share_default_fingerprint">...(指紋)</string>
+ <string name="menu_share_default">...(鍵)</string>
+ <string name="menu_share_qr_code">QRコードで共有(鍵)</string>
+ <string name="menu_share_qr_code_fingerprint">QRコードで共有(指紋)</string>
+ <string name="menu_share_nfc">NFCで共有</string>
+ <string name="menu_copy_to_clipboard">クリップボードへコピー</string>
+ <string name="menu_sign_key">鍵を署名</string>
+ <string name="menu_beam_preferences">Beamの設定</string>
+ <string name="menu_key_edit_cancel">キャンセル</string>
+ <string name="menu_encrypt_to">暗号化...</string>
+ <string name="menu_select_all">すべて選択</string>
+ <string name="menu_add_keys">鍵の追加</string>
+ <!--label-->
+ <string name="label_sign">署名</string>
+ <string name="label_message">メッセージ</string>
+ <string name="label_file">ファイル</string>
+ <string name="label_no_passphrase">パスフレーズなし</string>
+ <string name="label_passphrase">パスフレーズ</string>
+ <string name="label_passphrase_again">もう一度</string>
+ <string name="label_algorithm">アルゴリズム</string>
+ <string name="label_ascii_armor">アスキー形式</string>
+ <string name="label_select_public_keys">受信者</string>
+ <string name="label_delete_after_encryption">暗号化後に削除</string>
+ <string name="label_delete_after_decryption">復号化後に削除</string>
+ <string name="label_share_after_encryption">暗号化して共有</string>
+ <string name="label_encryption_algorithm">暗号化アルゴリズム</string>
+ <string name="label_hash_algorithm">ハッシュアルゴリズム</string>
+ <string name="label_asymmetric">公開鍵で</string>
+ <string name="label_symmetric">パスフレーズで</string>
+ <string name="label_passphrase_cache_ttl">パスフレーズキャッシュ</string>
+ <string name="label_message_compression">メッセージの圧縮</string>
+ <string name="label_file_compression">ファイルの圧縮</string>
+ <string name="label_force_v3_signature">強制的に古いOpenPGPV3形式の署名にする</string>
+ <string name="label_key_servers">鍵サーバ</string>
+ <string name="label_key_id">鍵ID</string>
+ <string name="label_creation">生成</string>
+ <string name="label_expiry">満了</string>
+ <string name="label_usage">使い方</string>
+ <string name="label_key_size">鍵サイズ</string>
+ <string name="label_main_user_id">主ユーザID</string>
+ <string name="label_name">名前</string>
+ <string name="label_comment">コメント</string>
+ <string name="label_email">Eメールアドレス</string>
+ <string name="label_send_key">証明後選択した鍵サーバに鍵をアップロード</string>
+ <string name="label_fingerprint">指紋</string>
+ <string name="select_keys_button_default">選択</string>
+ <string name="expiry_date_dialog_title">期限日時を設定</string>
+ <plurals name="select_keys_button">
+ <item quantity="other">%d を選択</item>
+ </plurals>
+ <string name="user_id_no_name">&lt;名前なし&gt;</string>
+ <string name="none">&lt;無し&gt;</string>
+ <string name="no_key">&lt;鍵無し&gt;</string>
+ <string name="no_email">&lt;メールなし&gt;</string>
+ <string name="unknown_status"></string>
+ <string name="can_encrypt">暗号化可能</string>
+ <string name="can_sign">署名可能</string>
+ <string name="expired">期限切れ</string>
+ <string name="revoked">破棄</string>
+ <string name="user_id">ユーザーID</string>
+ <plurals name="n_contacts">
+ <item quantity="other">%d個の連絡先</item>
+ </plurals>
+ <plurals name="n_key_servers">
+ <item quantity="other">%d の鍵サーバ</item>
+ </plurals>
+ <string name="fingerprint">指紋:</string>
+ <string name="secret_key">秘密鍵:</string>
+ <!--choice-->
+ <string name="choice_none">無し</string>
+ <string name="choice_15secs">15秒</string>
+ <string name="choice_1min">1分</string>
+ <string name="choice_3mins">3分</string>
+ <string name="choice_5mins">5分</string>
+ <string name="choice_10mins">10分</string>
+ <string name="choice_20mins">20分</string>
+ <string name="choice_40mins">40分</string>
+ <string name="choice_1hour">1時間</string>
+ <string name="choice_2hours">2時間</string>
+ <string name="choice_4hours">4時間</string>
+ <string name="choice_8hours">8時間</string>
+ <string name="choice_forever">永遠</string>
+ <string name="dsa">DSA</string>
+ <string name="elgamal">ElGamal</string>
+ <string name="rsa">RSA</string>
+ <string name="filemanager_title_open">開く...</string>
+ <string name="warning">注意</string>
+ <string name="error">エラー</string>
+ <string name="error_message">エラー: %s</string>
+ <!--key flags-->
+ <string name="flag_certify">検証</string>
+ <string name="flag_sign">署名</string>
+ <string name="flag_encrypt">暗号化</string>
+ <string name="flag_authenticate">証明</string>
+ <!--sentences-->
+ <string name="wrong_passphrase">良くないパスフレーズ</string>
+ <string name="using_clipboard_content">クリップボードの内容を使う。</string>
+ <string name="set_a_passphrase">最初にパスフレーズを設定してください。</string>
+ <string name="no_filemanager_installed">互換性のないファイルマネージャがインストールされています。</string>
+ <string name="passphrases_do_not_match">パスフレーズが一致しません。</string>
+ <string name="passphrase_must_not_be_empty">パスフレーズを入れてください。</string>
+ <string name="passphrase_for_symmetric_encryption">対称暗号。</string>
+ <string name="passphrase_for">\'%s\' にパスフレーズを入れてください。</string>
+ <string name="file_delete_confirmation">%s を削除してもかまいませんか?</string>
+ <string name="file_delete_successful">削除に成功しました。</string>
+ <string name="no_file_selected">最初にファイルを選択してください。</string>
+ <string name="decryption_successful">復号化/検証に成功しました。</string>
+ <string name="encryption_successful">署名/暗号化に成功しました。</string>
+ <string name="encryption_to_clipboard_successful">クリップボードの中身の署名/暗号化に成功しました。</string>
+ <string name="enter_passphrase_twice">もう一度パスフレーズを入れてください。</string>
+ <string name="select_encryption_key">少なくとも1つの暗号化鍵を選択して下さい。</string>
+ <string name="select_encryption_or_signature_key">少なくとも1つの暗号化鍵か署名鍵を選択して下さい。</string>
+ <string name="specify_file_to_encrypt_to">どのファイルを暗号化するか決めてください。\n注意: 既存のファイルがあると上書きされます。</string>
+ <string name="specify_file_to_decrypt_to">どのファイルを復号化するか決めてください。\n注意: 既存のファイルがあると上書きされます。</string>
+ <string name="specify_file_to_export_to">どのファイルをエクスポートするか決めてください。\n注意: 既存のファイルがあると上書きされます。</string>
+ <string name="specify_file_to_export_secret_keys_to">どのファイルをエクスポートするか決めてください。\n注意: 秘密鍵をエクスポートしています。\n注意: 既存のファイルがあると上書きされます。</string>
+ <string name="key_deletion_confirmation">鍵\'%s\'を本当に削除してもよいですか?\nこれは元に戻せません!</string>
+ <string name="key_deletion_confirmation_multi">選択したすべての鍵を本当に削除してよいですか?\nこれは元に戻せません。</string>
+ <string name="secret_key_deletion_confirmation">秘密鍵\'%s\'を本当に削除してもよいですか?\nこれは元に戻せません!</string>
+ <string name="ask_save_changed_key">あなたは鍵輪に変更を加えました、これを保存しますか?</string>
+ <string name="ask_empty_id_ok">あなたは空のユーザーIDを追加しました、このまま続けますか?</string>
+ <string name="public_key_deletetion_confirmation">公開鍵\'%s\'を本当に削除してもよいですか?\nこれは元に戻せません!</string>
+ <string name="secret_key_delete_text">秘密鍵を削除しますか?</string>
+ <string name="also_export_secret_keys">秘密鍵もエクスポートしますか?</string>
+ <plurals name="keys_added_and_updated_1">
+ <item quantity="other">%d の鍵を追加しました</item>
+ </plurals>
+ <plurals name="keys_added_and_updated_2">
+ <item quantity="other">そして %d の鍵をアップロードしました。</item>
+ </plurals>
+ <plurals name="keys_added">
+ <item quantity="other">%d の鍵を追加しました。</item>
+ </plurals>
+ <plurals name="keys_updated">
+ <item quantity="other">%d の鍵をアップロードしました。</item>
+ </plurals>
+ <string name="no_keys_added_or_updated">鍵の追加もしくは更新はありませんでした。</string>
+ <string name="key_exported">1つの鍵をエクスポートしました。</string>
+ <string name="keys_exported">%d の鍵をエクスポートしました。</string>
+ <string name="no_keys_exported">鍵をエクスポートしていません。</string>
+ <string name="key_creation_el_gamal_info">備考: 副鍵として ElGamalだけがサポートされ, ElGamal は鍵サイズとして1536, 2048, 3072, 4096, 8192 だけが使えます。</string>
+ <string name="key_creation_weak_rsa_info">付記: 長さ1024bitかそれ以下で生成されたRSA鍵は安全とはみなされず、新な鍵の生成は無効にされています。</string>
+ <string name="key_not_found">鍵 %08X は見付かりませんでした。</string>
+ <plurals name="keys_found">
+ <item quantity="other">%d の鍵を発見。</item>
+ </plurals>
+ <string name="unknown_signature">不明な署名、ボタンを押して見付からない鍵を検出してください。</string>
+ <plurals name="bad_keys_encountered">
+ <item quantity="other">%d の問題ある鍵を無視しました。 おそらく次のオプションでエクスポートしています\n --export-secret-subkeys\n代りに次のオプションでエクスポートしてください。\n --export-secret-keys</item>
+ </plurals>
+ <string name="key_send_success">鍵を鍵サーバにアップロードしました</string>
+ <string name="key_sign_success">鍵に署名しました。</string>
+ <string name="list_empty">このリストは空です!</string>
+ <string name="nfc_successfull">NFCビームで鍵を送信しました!</string>
+ <string name="key_copied_to_clipboard">鍵はクリプボードにコピーされました!</string>
+ <string name="key_has_already_been_signed">鍵はすでに署名されています!</string>
+ <string name="select_key_to_sign">署名に使う鍵を選択して下さい!</string>
+ <string name="key_too_big_for_sharing">この共有方法では鍵が大きすぎます!</string>
+ <!--errors
+ no punctuation, all lowercase,
+ they will be put after "error_message", e.g. "Error: file not found"-->
+ <string name="error_file_delete_failed">\'%s\' の削除に失敗</string>
+ <string name="error_file_not_found">ファイルが見付かりません</string>
+ <string name="error_no_secret_key_found">組になっている秘密鍵が見付かりません</string>
+ <string name="error_no_known_encryption_found">暗号化法が既知の種類内から見付かりません</string>
+ <string name="error_external_storage_not_ready">外部ストレージが準備できていません</string>
+ <string name="error_invalid_email">\'%s\' は不正なEメールアドレスです</string>
+ <string name="error_key_size_minimum512bit">鍵サイズは最低でも512bit必要です</string>
+ <string name="error_master_key_must_not_be_el_gamal">主鍵を ElGamal にすることはできません</string>
+ <string name="error_unknown_algorithm_choice">未知のアルゴリズムを選択しています</string>
+ <string name="error_user_id_needs_a_name">名前を特定する必要があります</string>
+ <string name="error_user_id_no_email">メールが見付かりません</string>
+ <string name="error_user_id_needs_an_email_address">Eメールアドレスを特定する必要があります</string>
+ <string name="error_key_needs_a_user_id">最低でも1つのユーザIDが必要です</string>
+ <string name="error_main_user_id_must_not_be_empty">主ユーザIDは空にすることはできません</string>
+ <string name="error_key_needs_master_key">主鍵が最低でも1つ必要です</string>
+ <string name="error_no_encryption_keys_or_passphrase">鍵が暗号化されていないかパスフレーズが与えられていません</string>
+ <string name="error_signature_failed">署名に失敗</string>
+ <string name="error_no_signature_passphrase">パスフレーズが与えられていません</string>
+ <string name="error_no_signature_key">署名鍵を与えられていません</string>
+ <string name="error_invalid_data">暗号化データが不正です</string>
+ <string name="error_corrupt_data">壊れたデータ</string>
+ <string name="error_integrity_check_failed">完全性チェックが失敗しました! データに変更があります!</string>
+ <string name="error_no_symmetric_encryption_packet">対称鍵暗号のパケットが見付かりませんでした</string>
+ <string name="error_wrong_passphrase">正しくないパスフレーズです</string>
+ <string name="error_saving_keys">鍵の保存エラー</string>
+ <string name="error_could_not_extract_private_key">秘密鍵を取り出すことができません</string>
+ <string name="error_only_files_are_supported">ファイルシステムに存在するファイルではないバイナリデータはサポートされません。 ACTION_ENCRYPT_STREAM_AND_RETURN でのみサポートされます。</string>
+ <string name="error_jelly_bean_needed">Android NFC Beam機能を使うにはAndroid 4.1 (Jelly Bean) が必要です!</string>
+ <string name="error_nfc_needed">あなたのデバイスにはNFCが存在しません!</string>
+ <string name="error_nothing_import">インポートするものがありません!</string>
+ <string name="error_expiry_must_come_after_creation">期限日時は生成日時より後である必要があります</string>
+ <string name="error_save_first">まず鍵輪を保存してください</string>
+ <string name="error_can_not_delete_contact">この連絡先はあなたなので削除できません。</string>
+ <string name="error_can_not_delete_contacts">この連絡先はあなたなので削除できません。:\n%s</string>
+ <string name="error_keyserver_insufficient_query">サーバへのクエリーが不足しています</string>
+ <string name="error_keyserver_query">鍵サーバへのクエリーが失敗</string>
+ <string name="error_keyserver_too_many_responses">レスポンスが多すぎます</string>
+ <string name="error_import_file_no_content">ファイルに内容がありません</string>
+ <string name="error_generic_report_bug">一般エラーが発生しました、この新しいバグの情報をOpenKeychainプロジェクトに送ってください</string>
+ <plurals name="error_can_not_delete_info">
+ <item quantity="other">\'自分の鍵\'画面から削除してください!</item>
+ </plurals>
+ <plurals name="error_import_non_pgp_part">
+ <item quantity="other">読み込んだファイルのOpenPGPオブジェクト部分は正しいですが、OpenPGPの鍵ではありません</item>
+ </plurals>
+ <string name="error_change_something_first">あなたは鍵輪を保存する前に変更を加えなくてはなりません</string>
+ <!--progress dialogs, usually ending in '…'-->
+ <string name="progress_done">完了。</string>
+ <string name="progress_cancel">キャンセル</string>
+ <string name="progress_saving">保存...</string>
+ <string name="progress_importing">インポート...</string>
+ <string name="progress_exporting">エクスポート...</string>
+ <string name="progress_building_key">鍵の構築中...</string>
+ <string name="progress_preparing_master_key">主鍵の準備中...</string>
+ <string name="progress_certifying_master_key">主鍵の検証中...</string>
+ <string name="progress_building_master_key">主鍵輪の構築中...</string>
+ <string name="progress_adding_sub_keys">副鍵の追加中...</string>
+ <string name="progress_saving_key_ring">鍵の保存...</string>
+ <plurals name="progress_exporting_key">
+ <item quantity="other">鍵のエクスポート...</item>
+ </plurals>
+ <plurals name="progress_generating">
+ <item quantity="other">鍵の生成中、最大3分ほどかかります...</item>
+ </plurals>
+ <string name="progress_extracting_signature_key">署名鍵の取り出し中...</string>
+ <string name="progress_extracting_key">鍵の取り出し中...</string>
+ <string name="progress_preparing_streams">ストリームの準備中...</string>
+ <string name="progress_encrypting">データの暗号化中...</string>
+ <string name="progress_decrypting">データの復号化中...</string>
+ <string name="progress_preparing_signature">署名の準備中...</string>
+ <string name="progress_generating_signature">署名の生成中...</string>
+ <string name="progress_processing_signature">署名処理中...</string>
+ <string name="progress_verifying_signature">署名の検証中...</string>
+ <string name="progress_signing">署名中...</string>
+ <string name="progress_reading_data">データ読み込み中...</string>
+ <string name="progress_finding_key">鍵検索中...</string>
+ <string name="progress_decompressing_data">データの展開中...</string>
+ <string name="progress_verifying_integrity">完全性の検証中...</string>
+ <string name="progress_deleting_securely">\'%s\' を完全に削除中…</string>
+ <string name="progress_querying">要求中...</string>
+ <!--action strings-->
+ <string name="hint_public_keys">公開鍵の検索</string>
+ <string name="hint_secret_keys">秘密鍵の検索</string>
+ <string name="action_share_key_with">...で鍵の共有</string>
+ <!--key bit length selections-->
+ <string name="key_size_512">512</string>
+ <string name="key_size_1024">1024</string>
+ <string name="key_size_2048">2048</string>
+ <string name="key_size_4096">4096</string>
+ <!--compression-->
+ <string name="compression_fast">早い</string>
+ <string name="compression_very_slow">とても遅い</string>
+ <!--Help-->
+ <string name="help_tab_start">開始</string>
+ <string name="help_tab_faq">FAQ</string>
+ <string name="help_tab_nfc_beam">NFC Beam</string>
+ <string name="help_tab_changelog">Changelog</string>
+ <string name="help_tab_about">これについて</string>
+ <string name="help_about_version">バージョン:</string>
+ <!--Import-->
+ <string name="import_import">選択した鍵のインポート</string>
+ <string name="import_sign_and_upload">選択した鍵のインポート、署名、そしてアップロード</string>
+ <string name="import_from_clipboard">クリップボードからインポート</string>
+ <plurals name="import_qr_code_missing">
+ <item quantity="other">ID %s のQRコードがありません</item>
+ </plurals>
+ <string name="import_qr_code_start_with_one">QRコードをID 1で始めてください</string>
+ <string name="import_qr_code_wrong">不適QRコード! もう一度!</string>
+ <string name="import_qr_code_finished">QRコードの読み取り完了!</string>
+ <string name="import_qr_code_too_short_fingerprint">指紋が短かすぎます (&lt; 16 文字)</string>
+ <string name="import_qr_scan_button">\'バーコードスキャナー\'でQRコードをスキャンする</string>
+ <string name="import_nfc_text">NFCで鍵を受信しました、デバイスのロックを解除する必要があります。</string>
+ <string name="import_nfc_help_button">ヘルプ</string>
+ <string name="import_clipboard_button">クリップボードから鍵を取得</string>
+ <!--Intent labels-->
+ <string name="intent_decrypt_file">OpenKeychainでファイルを復号化</string>
+ <string name="intent_import_key">OpenKeychainに鍵をインポート</string>
+ <string name="intent_send_encrypt">OpenKeychainで暗号化</string>
+ <string name="intent_send_decrypt">OpenKeychainで復号化</string>
+ <!--Remote API-->
+ <string name="api_no_apps">登録されていないアプリケーション!\n\nサードパーティアプリケーションはOpenKeychainにアクセスを要求できます。アクセスを与えた後、それらはここにリストされます。</string>
+ <string name="api_settings_show_info">詳細情報を表示</string>
+ <string name="api_settings_hide_info">詳細情報を非表示</string>
+ <string name="api_settings_show_advanced">拡張設定を表示</string>
+ <string name="api_settings_hide_advanced">拡張設定を隠す</string>
+ <string name="api_settings_no_key">鍵が選択されていない</string>
+ <string name="api_settings_select_key">鍵の選択</string>
+ <string name="api_settings_create_key">このアカウントで新しい鍵を生成</string>
+ <string name="api_settings_save">保存</string>
+ <string name="api_settings_cancel">キャンセル</string>
+ <string name="api_settings_revoke">破棄されたアクセス</string>
+ <string name="api_settings_delete_account">アカウントを削除</string>
+ <string name="api_settings_package_name">パッケージ名</string>
+ <string name="api_settings_package_signature">パッケージの署名 SHA-256</string>
+ <string name="api_settings_accounts">アカウント</string>
+ <string name="api_settings_accounts_empty">このアプリケーションに接続されてるアカウントはありません。</string>
+ <string name="api_create_account_text">このアプリケーションは新しいアカウントの生成を要求しています。すでにある秘密鍵を選択するか、新しく生成してください。\nここであなたが選択する鍵の使い道についてアプリケーションには制約があります!</string>
+ <string name="api_register_text">表示されているアプリケーションはOpenKeychainへのアクセスを要求しています。\nアクセスを許可しますか?\n\n注意: もしなぜスクリーンに表れたかわからないなら、アクセスを許可しないでください! あなたは\'登録済みアプリケーション\'スクリーンを使って、以降のアクセスを破棄するこもできます。</string>
+ <string name="api_register_allow">許可されたアクセス</string>
+ <string name="api_register_disallow">許可されないアクセス</string>
+ <string name="api_register_error_select_key">鍵を選択してください!</string>
+ <string name="api_select_pub_keys_missing_text">このユーザIDについて公開鍵が見付かりません:</string>
+ <string name="api_select_pub_keys_dublicates_text">このユーザIDについて1つ以上の公開鍵が存在します:</string>
+ <string name="api_select_pub_keys_text">受信者リストを確認してください!</string>
+ <string name="api_error_wrong_signature">署名チェックが失敗! 違うところからこのアプリをインストールしましたか? もし攻撃されてでなくそうであるなら、OpenKeychainにあるこのアプリの登録を破棄し、再度アプリを登録してください。</string>
+ <!--Share-->
+ <string name="share_qr_code_dialog_title">QRコードで共有</string>
+ <string name="share_qr_code_dialog_start">すべてのQRコードを見る場合、\'次\' を押して一つ一つスキャンしてください。</string>
+ <string name="share_qr_code_dialog_fingerprint_text">指紋:</string>
+ <string name="share_qr_code_dialog_progress">%2$d の ID %1$d のQRコード</string>
+ <string name="share_nfc_dialog">NFCで共有</string>
+ <!--Key list-->
+ <plurals name="key_list_selected_keys">
+ <item quantity="other">%d の鍵を選択。</item>
+ </plurals>
+ <string name="key_list_empty_text1">すでにその鍵は存在しません...</string>
+ <string name="key_list_empty_text2">で始める</string>
+ <string name="key_list_empty_text3">もしくは</string>
+ <string name="key_list_empty_button_create">あなた所有の鍵を作る</string>
+ <string name="key_list_empty_button_import">鍵のインポート。</string>
+ <!--Key view-->
+ <string name="key_view_action_edit">この鍵の編集</string>
+ <string name="key_view_action_encrypt">この連絡先を暗号化</string>
+ <string name="key_view_action_certify">この連絡先の鍵を検証</string>
+ <string name="key_view_tab_main">情報</string>
+ <string name="key_view_tab_certs">証明</string>
+ <!--Navigation Drawer-->
+ <string name="nav_contacts">鍵</string>
+ <string name="nav_encrypt">署名と暗号化</string>
+ <string name="nav_decrypt">復号化と検証</string>
+ <string name="nav_import">鍵のインポート</string>
+ <string name="nav_secret_keys">自分の鍵</string>
+ <string name="nav_apps">登録済みのアプリ</string>
+ <string name="drawer_open">ナビゲーションドロワーを開く</string>
+ <string name="drawer_close">ナビゲーションドロワーを閉める</string>
+ <string name="edit">編集</string>
+ <string name="my_keys">自分の鍵</string>
+ <string name="label_secret_key">秘密鍵</string>
+ <string name="secret_key_yes">存在する</string>
+ <string name="secret_key_no">存在しない</string>
+ <!--hints-->
+ <string name="encrypt_content_edit_text_hint">ここに書いたメッセージを暗号化/署名..</string>
+ <string name="decrypt_content_edit_text_hint">ここに入力された暗号化テキストを復号化/検証...</string>
+ <!--unsorted-->
+ <string name="section_uids_to_sign">署名に使うユーザーID</string>
+ <string name="progress_re_adding_certs">検証を再適用する</string>
+</resources>
diff --git a/OpenKeychain/src/main/res/values-large/dimens.xml b/OpenKeychain/src/main/res/values-large/dimens.xml
new file mode 100644
index 000000000..192a4bb99
--- /dev/null
+++ b/OpenKeychain/src/main/res/values-large/dimens.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <dimen name="drawer_content_padding">240dp</dimen>
+</resources>
diff --git a/OpenKeychain/src/main/res/values-nl-rNL/strings.xml b/OpenKeychain/src/main/res/values-nl-rNL/strings.xml
new file mode 100644
index 000000000..b1354393c
--- /dev/null
+++ b/OpenKeychain/src/main/res/values-nl-rNL/strings.xml
@@ -0,0 +1,229 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<resources>
+ <!--title-->
+ <string name="title_select_recipients">Publieke sleutel selecteren</string>
+ <string name="title_select_secret_key">Privésleutel selecteren</string>
+ <string name="title_encrypt">Versleutelen</string>
+ <string name="title_decrypt">Ontsleutelen</string>
+ <string name="title_authentication">Wachtwoord</string>
+ <string name="title_create_key">Sleutel aanmaken</string>
+ <string name="title_edit_key">Sleutel bewerken</string>
+ <string name="title_preferences">Instellingen</string>
+ <string name="title_api_registered_apps">Geregistreerde apps</string>
+ <string name="title_set_passphrase">Wachtwoord instellen</string>
+ <string name="title_send_email">E-mail verzenden...</string>
+ <string name="title_encrypt_to_file">Versleutelen naar bestand</string>
+ <string name="title_decrypt_to_file">Ontsleutelen naar bestand</string>
+ <string name="title_import_keys">Sleutels importeren</string>
+ <string name="title_export_key">Sleutels exporteren</string>
+ <string name="title_export_keys">Sleutels exporteren</string>
+ <string name="title_key_not_found">Sleutel niet gevonden</string>
+ <string name="title_unknown_signature_key">Onbekende handtekeningssleutel</string>
+ <string name="title_help">Help</string>
+ <!--section-->
+ <string name="section_user_ids">Gebruikers-id\'s</string>
+ <string name="section_keys">Sleutels</string>
+ <string name="section_general">Algemeen</string>
+ <string name="section_defaults">Standaard</string>
+ <string name="section_advanced">Geavanceerd</string>
+ <!--button-->
+ <string name="btn_sign">Ondertekenen</string>
+ <string name="btn_decrypt">Ontsleutelen</string>
+ <string name="btn_select_encrypt_keys">Ontvangers selecteren</string>
+ <string name="btn_encrypt_file">Bestand versleutelen</string>
+ <string name="btn_save">Opslaan</string>
+ <string name="btn_do_not_save">Annuleren</string>
+ <string name="btn_delete">Verwijderen</string>
+ <string name="btn_no_date">Geen</string>
+ <string name="btn_okay">OK</string>
+ <string name="btn_search">Zoeken</string>
+ <string name="btn_next">Volgende</string>
+ <string name="btn_back">Terug</string>
+ <!--menu-->
+ <string name="menu_preferences">Instellingen</string>
+ <string name="menu_import_from_file">Importeren uit bestand</string>
+ <string name="menu_import_from_qr_code">Importeren met QR-code</string>
+ <string name="menu_import_from_nfc">Importeren met NFC</string>
+ <string name="menu_export_key">Exporteren naar bestand</string>
+ <string name="menu_delete_key">Sleutel verwijderen</string>
+ <string name="menu_create_key">Sleutel aanmaken</string>
+ <string name="menu_create_key_expert">Sleutel aanmaken (expert)</string>
+ <string name="menu_search">Zoeken</string>
+ <string name="menu_sign_key">Sleutel ondertekenen</string>
+ <string name="menu_beam_preferences">Beam-instellingen</string>
+ <!--label-->
+ <string name="label_sign">Ondertekenen</string>
+ <string name="label_message">Bericht</string>
+ <string name="label_file">Bestand</string>
+ <string name="label_no_passphrase">Geen wachtwoord</string>
+ <string name="label_passphrase">Wachtwoord</string>
+ <string name="label_passphrase_again">Opnieuw</string>
+ <string name="label_algorithm">Algoritme</string>
+ <string name="label_ascii_armor">ASCII-armor</string>
+ <string name="label_delete_after_encryption">Verwijderen na versleuteling</string>
+ <string name="label_delete_after_decryption">Verwijderen na ontsleuteling</string>
+ <string name="label_encryption_algorithm">Versleutelingsalgoritme</string>
+ <string name="label_hash_algorithm">Verificatie-algoritme</string>
+ <string name="label_passphrase_cache_ttl">Wachtwoordcache</string>
+ <string name="label_message_compression">Berichtcompressie</string>
+ <string name="label_file_compression">Bestandscompressie</string>
+ <string name="label_key_id">Sleutel-id</string>
+ <string name="label_creation">Aanmaak</string>
+ <string name="label_expiry">Verlopen</string>
+ <string name="label_usage">Gebruik</string>
+ <string name="label_key_size">Sleutelgrootte</string>
+ <string name="label_main_user_id">Hoofdgebruikers-id</string>
+ <string name="label_name">Naam</string>
+ <string name="label_comment">Opmerking</string>
+ <string name="label_email">E-mailadres</string>
+ <string name="none">&lt;geen&gt;</string>
+ <string name="no_key">&lt;geen sleutel&gt;</string>
+ <string name="unknown_status"></string>
+ <string name="can_encrypt">versleutelbaar</string>
+ <string name="can_sign">ondertekenbaar</string>
+ <string name="expired">verlopen</string>
+ <string name="fingerprint">VIngerafdruk:</string>
+ <string name="secret_key">Privésleutel:</string>
+ <!--choice-->
+ <string name="choice_none">Geen</string>
+ <string name="choice_15secs">15 sec.</string>
+ <string name="choice_1min">1 min.</string>
+ <string name="choice_3mins">3 min.</string>
+ <string name="choice_5mins">5 min.</string>
+ <string name="choice_10mins">10 min.</string>
+ <string name="choice_20mins">20 min.</string>
+ <string name="choice_40mins">40 min.</string>
+ <string name="choice_1hour">1 uur</string>
+ <string name="choice_2hours">2 uur</string>
+ <string name="choice_4hours">4 uur</string>
+ <string name="choice_8hours">8 uur</string>
+ <string name="dsa">DSA</string>
+ <string name="elgamal">ElGamal</string>
+ <string name="rsa">RSA</string>
+ <string name="filemanager_title_open">Openen...</string>
+ <string name="warning">Waarschuwing</string>
+ <string name="error">Fout</string>
+ <string name="error_message">Fout: %s</string>
+ <!--key flags-->
+ <!--sentences-->
+ <string name="wrong_passphrase">Wachtwoord verkeerd.</string>
+ <string name="using_clipboard_content">Gebruikmaken van klembordinhoud.</string>
+ <string name="set_a_passphrase">Stel eerst een wachtwoord in.</string>
+ <string name="no_filemanager_installed">Geen compatibele bestandsbeheerder geïnstalleerd.</string>
+ <string name="passphrases_do_not_match">De wachtwoorden komen niet overeen.</string>
+ <string name="passphrase_for_symmetric_encryption">Symmetrische versleuteling.</string>
+ <string name="passphrase_for">Voer het wachtwoord in voor \'%s\'</string>
+ <string name="file_delete_confirmation">Weer u zeker dat u het volgende wilt verwijderen:\n%s?</string>
+ <string name="file_delete_successful">Succesvol verwijderd.</string>
+ <string name="no_file_selected">Selecteer eerst een bestand.</string>
+ <string name="enter_passphrase_twice">Voer het wachtwoord tweemaal in.</string>
+ <string name="select_encryption_key">Selecteer ten minste één versleutelingssleutel.</string>
+ <string name="select_encryption_or_signature_key">Selecter ten minste één versleutelings-/ondertekeningssleutel.</string>
+ <string name="key_deletion_confirmation">Weet u zeker dat u de sleutel \'%s\' wilt verwijderen?\nDit kan niet ongedaan worden gemaakt.</string>
+ <string name="secret_key_deletion_confirmation">Weet u zeker dat u de privésleutel \'%s\' wilt verwijderen?\nDit kan niet ongedaan worden gemaakt.</string>
+ <string name="no_keys_added_or_updated">Geen sleutels toegevoegd of bijgewerkt.</string>
+ <string name="key_exported">1 sleutel succesvol geëxporteerd.</string>
+ <string name="no_keys_exported">Geen sleutels geëxporteerd.</string>
+ <string name="key_creation_el_gamal_info">Opmerking: alleen sub-sleutels ondersteunen ElGamal, en voor ElGamal wordt de dichtstbijzijnde sleutelgrootte van 1536, 2048, 4096 of 8192 gebruikt.</string>
+ <string name="key_not_found">Kan de sleutel %08X niet vinden.</string>
+ <string name="key_sign_success">Sleutel succesvol ondertekend</string>
+ <string name="list_empty">Lijst is leeg</string>
+ <string name="nfc_successfull">Sleutel succesvol verzonden met Beam</string>
+ <!--errors
+ no punctuation, all lowercase,
+ they will be put after "error_message", e.g. "Error: file not found"-->
+ <string name="error_file_delete_failed">verwijderen \'%s\' mislukt</string>
+ <string name="error_file_not_found">bestand niet gevonden</string>
+ <string name="error_no_secret_key_found">geen geschikte privésleutel gevonden</string>
+ <string name="error_no_known_encryption_found">geen bekende versleuteling gevonden</string>
+ <string name="error_external_storage_not_ready">externe opslag niet gereed</string>
+ <string name="error_invalid_email">ongeldig e-mailadres \'%s\'</string>
+ <string name="error_key_size_minimum512bit">sleutelgrootte moet minstens 512-bits zijn</string>
+ <string name="error_master_key_must_not_be_el_gamal">de hoofdsleutel kan geen ElGamal-sleutel zijn</string>
+ <string name="error_unknown_algorithm_choice">onbekende algoritmekeuze</string>
+ <string name="error_user_id_needs_a_name">u moet een naam invoeren</string>
+ <string name="error_user_id_needs_an_email_address">u moet een e-mailadres invoeren</string>
+ <string name="error_key_needs_a_user_id">ten minste één gebruiksers-id vereist</string>
+ <string name="error_main_user_id_must_not_be_empty">hoofdgebruikers-id kan niet leeg zijn</string>
+ <string name="error_key_needs_master_key">ten minste een hoofdsleutel is vereist</string>
+ <string name="error_signature_failed">handtekening mislukt</string>
+ <string name="error_no_signature_passphrase">geen wachtwoord opgegeven</string>
+ <string name="error_no_signature_key">geen ondertekeningssleutel opgegeven</string>
+ <string name="error_invalid_data">geen geldige versleutelingsgegevens</string>
+ <string name="error_corrupt_data">gegevens beschadigd</string>
+ <string name="error_no_symmetric_encryption_packet">kan geen pakket vinden met symmetrische versleuteling</string>
+ <string name="error_wrong_passphrase">wachtwoord verekerd</string>
+ <string name="error_could_not_extract_private_key">kan privésleutel niet uitpakken</string>
+ <string name="error_only_files_are_supported">Ruwe invoer van binaire gegevens wordt niet ondersteund, alleen bij ACTION_ENCRYPT_STREAM_AND_RETURN.</string>
+ <string name="error_jelly_bean_needed">Android 4.1 Jelly Bean of hoger is vereist voor NFC Beam.</string>
+ <string name="error_nfc_needed">Uw apparaat biedt geen ondersteuning voor NFC</string>
+ <string name="error_nothing_import">Niets te importeren</string>
+ <!--progress dialogs, usually ending in '…'-->
+ <string name="progress_saving">opslaan...</string>
+ <string name="progress_importing">importeren...</string>
+ <string name="progress_exporting">exporteren...</string>
+ <string name="progress_building_key">sleutel maken...</string>
+ <string name="progress_preparing_master_key">hoofdsleutel voorbereiden...</string>
+ <string name="progress_certifying_master_key">hoofdsleutel certificeren...</string>
+ <string name="progress_building_master_key">hoofdsleutelbos maken...</string>
+ <string name="progress_adding_sub_keys">sub-sleutels toevoegen...</string>
+ <string name="progress_extracting_signature_key">ondertekeningssleutel uitpakken...</string>
+ <string name="progress_extracting_key">sleutel uitpakken...</string>
+ <string name="progress_preparing_streams">streams voorbereiden...</string>
+ <string name="progress_encrypting">gegevens versleutelen...</string>
+ <string name="progress_decrypting">gegevens ontsleutelen...</string>
+ <string name="progress_preparing_signature">handtekening voorbereiden...</string>
+ <string name="progress_generating_signature">handtekening genereren...</string>
+ <string name="progress_processing_signature">handtekening verwerken...</string>
+ <string name="progress_verifying_signature">handtekening verifiëren...</string>
+ <string name="progress_signing">ondertekenen...</string>
+ <string name="progress_reading_data">gegevens lezen...</string>
+ <string name="progress_finding_key">sleutel opzoeken...</string>
+ <string name="progress_decompressing_data">gegevens decomprimeren...</string>
+ <string name="progress_verifying_integrity">integriteit verifiëren...</string>
+ <string name="progress_deleting_securely">\'%s\' veilig verwijderen...</string>
+ <string name="progress_querying">opvragen...</string>
+ <!--action strings-->
+ <string name="hint_public_keys">Publieke sleutels zoeken</string>
+ <string name="hint_secret_keys">Privésleutels zoeken</string>
+ <string name="action_share_key_with">Sleutel delen met...</string>
+ <!--key bit length selections-->
+ <string name="key_size_512">512</string>
+ <string name="key_size_1024">1024</string>
+ <string name="key_size_2048">2048</string>
+ <string name="key_size_4096">4096</string>
+ <!--compression-->
+ <string name="compression_fast">snel</string>
+ <string name="compression_very_slow">zeer langzaam</string>
+ <!--Help-->
+ <string name="help_tab_start">Beginnen</string>
+ <string name="help_tab_nfc_beam">NFC Beam</string>
+ <string name="help_tab_changelog">Lijst van wijzigingen</string>
+ <string name="help_tab_about">Over</string>
+ <string name="help_about_version">Versie:</string>
+ <!--Import-->
+ <string name="import_import">Geselecteerde sleutels importeren</string>
+ <string name="import_sign_and_upload">Geselecteerde sleutels importeren, ondertekenen en uploaden</string>
+ <string name="import_qr_code_wrong">QR-code ongeldig. Probeer het opnieuw</string>
+ <string name="import_qr_code_finished">QR-code gescand</string>
+ <!--Intent labels-->
+ <!--Remote API-->
+ <string name="api_settings_no_key">Geen sleutel geselecteerd</string>
+ <string name="api_settings_select_key">Sleutel selecteren</string>
+ <string name="api_settings_save">Opslaan</string>
+ <string name="api_settings_cancel">Annuleren</string>
+ <string name="api_settings_revoke">Toegang herroepen</string>
+ <string name="api_register_allow">Toegang toestaan</string>
+ <string name="api_register_disallow">Toegang weigeren</string>
+ <string name="api_register_error_select_key">Selecteert u a.u.b. een sleutel</string>
+ <string name="api_select_pub_keys_missing_text">Geen publieke sleutels gevonden voor deze gebruiker-id\'s:</string>
+ <string name="api_select_pub_keys_dublicates_text">Meer dan een publieke sleutel gevonden voor deze gebruikers-id\'s:</string>
+ <string name="api_select_pub_keys_text">Bekijkt u a.u.b. de ontvangers</string>
+ <!--Share-->
+ <string name="share_qr_code_dialog_start">U gaat door alle QR-codes met \'Volgende\', en scant ze een voor een.</string>
+ <!--Key list-->
+ <!--Key view-->
+ <!--Navigation Drawer-->
+ <!--hints-->
+ <!--unsorted-->
+</resources>
diff --git a/OpenKeychain/src/main/res/values-pl/strings.xml b/OpenKeychain/src/main/res/values-pl/strings.xml
new file mode 100644
index 000000000..efdd61e3c
--- /dev/null
+++ b/OpenKeychain/src/main/res/values-pl/strings.xml
@@ -0,0 +1,466 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<resources>
+ <!--title-->
+ <string name="title_manage_public_keys">Kontakty</string>
+ <string name="title_manage_secret_keys">Klucze prywatne</string>
+ <string name="title_select_recipients">Wybierz Klucz Publiczny</string>
+ <string name="title_select_secret_key">Wybierz Klucz Prywatny</string>
+ <string name="title_encrypt">Zaszyfruj</string>
+ <string name="title_decrypt">Odszyfruj</string>
+ <string name="title_authentication">Hasło</string>
+ <string name="title_create_key">Utwórz Klucz</string>
+ <string name="title_edit_key">Edytuj Klucz</string>
+ <string name="title_preferences">Właściwości</string>
+ <string name="title_api_registered_apps">Zarejestrowane Aplikacje</string>
+ <string name="title_key_server_preference">Właściwości serwera kluczy</string>
+ <string name="title_change_passphrase">Zmień hasło</string>
+ <string name="title_set_passphrase">Ustaw hasło</string>
+ <string name="title_send_email">Wyślij maila...</string>
+ <string name="title_send_file">Wyślij plik...</string>
+ <string name="title_encrypt_to_file">Zaszyfruj do pliku</string>
+ <string name="title_decrypt_to_file">Odszyfruj do pliku</string>
+ <string name="title_import_keys">Importuj klucze</string>
+ <string name="title_export_key">Eksportuj klucz</string>
+ <string name="title_export_keys">Eksportuj klucze</string>
+ <string name="title_key_not_found">Nie znaleziono klucza</string>
+ <string name="title_key_server_query">Wyślij zapytanie do serwera kluczy</string>
+ <string name="title_send_key">Wyślij do serwera kluczy</string>
+ <string name="title_unknown_signature_key">Nieznany klucz podpisu</string>
+ <string name="title_certify_key">Certyfikuj klucz</string>
+ <string name="title_key_details">Szczegóły klucza</string>
+ <string name="title_help">Pomoc</string>
+ <!--section-->
+ <string name="section_user_ids">Identyfikator użytkownika</string>
+ <string name="section_keys">Klucze</string>
+ <string name="section_general">Ogólne</string>
+ <string name="section_defaults">Domyślne</string>
+ <string name="section_advanced">Zaawansowane</string>
+ <string name="section_master_key">Klucz główny</string>
+ <string name="section_master_user_id">Główny identyfikator użytkownika</string>
+ <string name="section_actions">Działania</string>
+ <string name="section_certification_key">Twój klucz użyty do certyfikacji</string>
+ <string name="section_upload_key">Wyślij klucz</string>
+ <string name="section_key_server">Serwer kluczy</string>
+ <string name="section_encrypt_and_or_sign">Zaszyfruj i/lub podpisz</string>
+ <string name="section_decrypt_verify">Deszyfruj i weryfikuj</string>
+ <!--button-->
+ <string name="btn_sign">Podpisz</string>
+ <string name="btn_certify">Certyfikuj</string>
+ <string name="btn_decrypt">Odszyfruj</string>
+ <string name="btn_decrypt_verify">Deszyfruj i weryfikuj</string>
+ <string name="btn_decrypt_verify_clipboard">Ze schowka</string>
+ <string name="btn_select_encrypt_keys">Wybierz odbiorców</string>
+ <string name="btn_encrypt_file">Zaszyfruj plik</string>
+ <string name="btn_save">Zapisz</string>
+ <string name="btn_do_not_save">Anuluj</string>
+ <string name="btn_delete">Usuń</string>
+ <string name="btn_no_date">Żaden</string>
+ <string name="btn_okay">Ok</string>
+ <string name="btn_change_passphrase">Zmień nowe hasło</string>
+ <string name="btn_set_passphrase">Ustaw nowe hasło</string>
+ <string name="btn_search">Wyszukaj</string>
+ <string name="btn_export_to_server">Wyślij do serwera kluczy</string>
+ <string name="btn_next">Dalej</string>
+ <string name="btn_back">Wstecz</string>
+ <string name="btn_clipboard">Schowek</string>
+ <string name="btn_share">Podziel się z...</string>
+ <string name="btn_lookup_key">Klucz wyszukiwania</string>
+ <string name="btn_encryption_advanced_settings_show">Pokaż zaawanowane ustawienia</string>
+ <string name="btn_encryption_advanced_settings_hide">Ukryj zaawansowane ustawienia</string>
+ <!--menu-->
+ <string name="menu_preferences">Ustawienia</string>
+ <string name="menu_help">Pomoc</string>
+ <string name="menu_import_from_file">Zaimportuj z pliku</string>
+ <string name="menu_import_from_qr_code">Zaimportuj z kodu QR</string>
+ <string name="menu_import">Import</string>
+ <string name="menu_import_from_nfc">Zaimportuj przy użyciu NFC</string>
+ <string name="menu_export_key">Eksportuj do pliku</string>
+ <string name="menu_delete_key">Usuń klucz</string>
+ <string name="menu_create_key">Stwórz klucz</string>
+ <string name="menu_create_key_expert">Stwórz klucz (tryb zaawansowany)</string>
+ <string name="menu_search">Znajdź</string>
+ <string name="menu_import_from_key_server">Serwer kluczy</string>
+ <string name="menu_key_server">Serwer kluczy...</string>
+ <string name="menu_update_key">Aktualizuj z serwera kluczy</string>
+ <string name="menu_export_key_to_server">Wyślij do serwera kluczy</string>
+ <string name="menu_share">Udostepnij...</string>
+ <string name="menu_share_title_fingerprint">Udostepnij odcisk...</string>
+ <string name="menu_share_title">Udostępnij cały klucz...</string>
+ <string name="menu_share_default_fingerprint">z...</string>
+ <string name="menu_share_default">z...</string>
+ <string name="menu_share_qr_code">za pomocą kodu QR</string>
+ <string name="menu_share_qr_code_fingerprint">za pomocą kodu QR</string>
+ <string name="menu_share_nfc">za pomocą NFC</string>
+ <string name="menu_copy_to_clipboard">Kopiuj do schowka</string>
+ <string name="menu_sign_key">Klucz podpisu</string>
+ <string name="menu_beam_preferences">Ustawienia Beam</string>
+ <string name="menu_key_edit_cancel">Anuluj</string>
+ <string name="menu_encrypt_to">Zaszyfruj do...</string>
+ <string name="menu_select_all">Wybierz wszystko</string>
+ <string name="menu_add_keys">Dodaj klucze</string>
+ <!--label-->
+ <string name="label_sign">Podpis</string>
+ <string name="label_message">Wiadomość</string>
+ <string name="label_file">Plik</string>
+ <string name="label_no_passphrase">Brak hasła</string>
+ <string name="label_passphrase">Hasło</string>
+ <string name="label_passphrase_again">Ponów</string>
+ <string name="label_algorithm">Algorytm</string>
+ <string name="label_ascii_armor">ASCII Armor</string>
+ <string name="label_select_public_keys">Odbiorcy</string>
+ <string name="label_delete_after_encryption">Usuń po zaszyfrowaniu</string>
+ <string name="label_delete_after_decryption">Usuń po odszyfrowaniu</string>
+ <string name="label_share_after_encryption">Udostępnij po zaszyfrowaniu</string>
+ <string name="label_encryption_algorithm">Algorytm szyfrujący</string>
+ <string name="label_hash_algorithm">Algorytm funkcji skrótu</string>
+ <string name="label_asymmetric">za pomocą klucza publicznego</string>
+ <string name="label_symmetric">za pomocą hasła</string>
+ <string name="label_passphrase_cache_ttl">Bufor haseł</string>
+ <string name="label_message_compression">Kompresja wiadomości</string>
+ <string name="label_file_compression">Kompresja plików</string>
+ <string name="label_force_v3_signature">Wymuś stare podpisy OpenPGPv3</string>
+ <string name="label_key_servers">Serwery kluczy</string>
+ <string name="label_key_id">Identyfikator klucza</string>
+ <string name="label_creation">Utworzenia</string>
+ <string name="label_expiry">Wygaśnięcia</string>
+ <string name="label_usage">Wykorzystanie</string>
+ <string name="label_key_size">Rozmiar klucza</string>
+ <string name="label_main_user_id">Identyfikator głównego użytkownika</string>
+ <string name="label_name">Imię</string>
+ <string name="label_comment">Komentarz</string>
+ <string name="label_email">Adres email</string>
+ <string name="label_send_key">Wyślij klucz do serwera kluczy po certyfikacji</string>
+ <string name="label_fingerprint">Odcisk</string>
+ <string name="select_keys_button_default">Wybierz</string>
+ <string name="expiry_date_dialog_title">Ustaw datę wygaśnięcia</string>
+ <plurals name="select_keys_button">
+ <item quantity="one">wybrano %d</item>
+ <item quantity="few">wybrano %d</item>
+ <item quantity="other">wybrano %d</item>
+ </plurals>
+ <string name="user_id_no_name">&lt;bez nazwy&gt;</string>
+ <string name="none">&lt;żaden&gt;</string>
+ <string name="no_key">&lt;brak klucza&gt;</string>
+ <string name="no_email">&lt;Brak adresu email&gt;</string>
+ <string name="unknown_status"></string>
+ <string name="can_encrypt">może szyfrować</string>
+ <string name="can_sign">może podpisywać</string>
+ <string name="expired">wygasły</string>
+ <string name="revoked">unieważniony</string>
+ <string name="user_id">Identyfikator użytkownika</string>
+ <plurals name="n_contacts">
+ <item quantity="one">1 kontakt</item>
+ <item quantity="few">%d kontakty</item>
+ <item quantity="other">%d kontaktów</item>
+ </plurals>
+ <plurals name="n_key_servers">
+ <item quantity="one">%d serwer kluczy</item>
+ <item quantity="few">%d serwerów kluczy</item>
+ <item quantity="other">%d serwerów kluczy</item>
+ </plurals>
+ <string name="fingerprint">Odcisk:</string>
+ <string name="secret_key">Klucz prywatny:</string>
+ <!--choice-->
+ <string name="choice_none">Brak</string>
+ <string name="choice_15secs">15 sekund</string>
+ <string name="choice_1min">1 minuta</string>
+ <string name="choice_3mins">3 minuty</string>
+ <string name="choice_5mins">5 minut</string>
+ <string name="choice_10mins">10 minut</string>
+ <string name="choice_20mins">20 minut</string>
+ <string name="choice_40mins">40 minut</string>
+ <string name="choice_1hour">1 godzina</string>
+ <string name="choice_2hours">2 godziny</string>
+ <string name="choice_4hours">4 godziny</string>
+ <string name="choice_8hours">8 godzin</string>
+ <string name="choice_forever">na zawsze</string>
+ <string name="dsa">DSA</string>
+ <string name="elgamal">ElGamal</string>
+ <string name="rsa">RSA</string>
+ <string name="filemanager_title_open">Otwórz...</string>
+ <string name="warning">Ostrzeżenie</string>
+ <string name="error">Błąd</string>
+ <string name="error_message">Błąd: %s</string>
+ <!--key flags-->
+ <string name="flag_certify">Certyfikuj</string>
+ <string name="flag_sign">Podpisz</string>
+ <string name="flag_encrypt">Zaszyfruj</string>
+ <string name="flag_authenticate">Autentykuj</string>
+ <!--sentences-->
+ <string name="wrong_passphrase">Nieprawidłowe hasło.</string>
+ <string name="using_clipboard_content">Użycie zawartości schowka.</string>
+ <string name="set_a_passphrase">Najpierw ustaw hasło.</string>
+ <string name="no_filemanager_installed">Nie zainstalowano żadnego kompatybilnego menadżera plików.</string>
+ <string name="passphrases_do_not_match">Hasła nie pasują do siebie</string>
+ <string name="passphrase_must_not_be_empty">Podaj hasło.</string>
+ <string name="passphrase_for_symmetric_encryption">Szyfrowanie symetryczne.</string>
+ <string name="passphrase_for">Podaj hasło dla \'%s\'</string>
+ <string name="file_delete_confirmation">Czy jesteś pewien że chcesz usunąć\n%s?</string>
+ <string name="file_delete_successful">Usunięto pomyślnie.</string>
+ <string name="no_file_selected">Najpierw wskaż plik.</string>
+ <string name="decryption_successful">Pomyślnie deszyfrowano i/lub zweryfikowano.</string>
+ <string name="encryption_successful">Pomyślnie podpisano i/lub zaszyfrowano.</string>
+ <string name="encryption_to_clipboard_successful">Pomyslnie podpisano i/lub zaszyfrowano do schowka.</string>
+ <string name="enter_passphrase_twice">Podaj hasło dwukrotnie.</string>
+ <string name="select_encryption_key">Wybierz co najmniej jeden klucz szyfrujący.</string>
+ <string name="select_encryption_or_signature_key">Wybierz co najmniej jeden klucz szyfrujący lub klucz podpisujący.</string>
+ <string name="specify_file_to_encrypt_to">Wskaż, do którego pliku zapisać zaszyfrowane dane.\nOSTRZEŻENIE: Plik zostanie nadpisany, jeżeli istnieje.</string>
+ <string name="specify_file_to_decrypt_to">Wskaż, do którego pliku zapisać odszyfrowane dane.\nOSTRZEŻENIE: Plik zostanie nadpisany, jeżeli istnieje.</string>
+ <string name="specify_file_to_export_to">Wskaż, do którego pliku wyeksportować dane.\nOSTRZEŻENIE: Plik zostanie nadpisany, jeżeli istnieje.</string>
+ <string name="specify_file_to_export_secret_keys_to">Wskaż, do którego pliku zapisać eksportowane dane.\nOSTRZEŻENIE: Masz zamiar zapisać klucze PRYWATNE (tajne)\nOSTRZEŻENIE: Plik zostanie nadpisany, jeżeli istnieje.</string>
+ <string name="key_deletion_confirmation">Czy na pewno chcesz usunąć klucz \'%s\'?\nNie można cofnąć tej operacji!</string>
+ <string name="key_deletion_confirmation_multi">Czy na pewno chcesz usunąć wszystkie zaznaczone klucze?\nTej operacji nie można cofnąć!</string>
+ <string name="secret_key_deletion_confirmation">Czy na pewno chcesz usunąć klucz prywatny \'%s\'?\nNie można cofnąć tej operacji!</string>
+ <string name="ask_save_changed_key">Zostały dokonane zmiany w pęku kluczy, czy chcesz je zachować?</string>
+ <string name="ask_empty_id_ok">Dodałeś pusty identyfikator użytkownika, czy na pewno chcesz kontynuować?</string>
+ <string name="public_key_deletetion_confirmation">Czy na pewno chcesz usunąć klucz publiczny \'%s\'?\nNie można cofnąć tej operacji!</string>
+ <string name="secret_key_delete_text">Usunąć klucze prywatne?</string>
+ <string name="also_export_secret_keys">Czy wyeksportować również klucze prywatne?</string>
+ <plurals name="keys_added_and_updated_1">
+ <item quantity="one">Pomyślnie dodano %d klucz</item>
+ <item quantity="few">Pomyślnie dodano %d kluczy</item>
+ <item quantity="other">Pomyślnie dodano %d kluczy</item>
+ </plurals>
+ <plurals name="keys_added_and_updated_2">
+ <item quantity="one">i zaktualizowano %d klucz.</item>
+ <item quantity="few">i zaktualizowano %d kluczy.</item>
+ <item quantity="other">i zaktualizowano %d kluczy.</item>
+ </plurals>
+ <plurals name="keys_added">
+ <item quantity="one">Pomyślnie dodano %d klucz.</item>
+ <item quantity="few">Pomyślnie dodano %d kluczy.</item>
+ <item quantity="other">Pomyślnie dodano %d kluczy.</item>
+ </plurals>
+ <plurals name="keys_updated">
+ <item quantity="one">Pomyślnie zaktualizowano %d klucz.</item>
+ <item quantity="few">Pomyślnie zaktualizowano %d kluczy.</item>
+ <item quantity="other">Pomyślnie zaktualizowano %d kluczy.</item>
+ </plurals>
+ <string name="no_keys_added_or_updated">Nie dodano ani zaktualizowano żadnych kluczy.</string>
+ <string name="key_exported">Pomyślnie wyeksportowano 1 klucz.</string>
+ <string name="keys_exported">Pomyślnie wyeksportowano %d kluczy.</string>
+ <string name="no_keys_exported">Nie wyeksportowano żadnych kluczy.</string>
+ <string name="key_creation_el_gamal_info">Uwaga: algorytm EnGamal jest obsługiwany tylko przez podklucze i użyty zostanie najbliższy rozmiar klucza z podanych: 1536, 2048, 3072, 4096, 8192.</string>
+ <string name="key_creation_weak_rsa_info">Uwaga: generowanie klucza RSA o długości 1024 bity i mniejszej jest uważane za niebezpieczne i wyłączone dla tworzenia nowych kluczy.</string>
+ <string name="key_not_found">Nie można znaleźć klucza %08X.</string>
+ <plurals name="keys_found">
+ <item quantity="one">Znaleziono %d klucz.</item>
+ <item quantity="few">Znaleziono %d kluczy.</item>
+ <item quantity="other">Znaleziono %d kluczy.</item>
+ </plurals>
+ <string name="unknown_signature">Nieznany podpis, naciśnij przycisk, aby wyszukać brakujący klucz.</string>
+ <plurals name="bad_keys_encountered">
+ <item quantity="one">Zignorowano %d niepoprawny klucz prywatny. Prawdopodobnie został wyeksportowany przy uzyciu opcji\n --export-secret-subkeys\nUpewnij się że eksportujesz go z opcją\n --export-secret-keys\nktóra jest poprawna.</item>
+ <item quantity="few">Zignorowano %d niepoprawnych kluczy prywatnych. Prawdopodobnie zostały wyeksportowane przy uzyciu opcji\n --export-secret-subkeys\nUpewnij się że eksportujesz je z opcją\n --export-secret-keys\nktóra jest poprawna.</item>
+ <item quantity="other">zignorowano %d niepoprawnych kluczy prywatnych. Prawdopodobnie zostały wyeksportowane przy uzyciu opcji\n --export-secret-subkeys\nUpewnij się że eksportujesz je z opcją\n --export-secret-keys\nktóra jest poprawna.</item>
+ </plurals>
+ <string name="key_send_success">Pomyślnie wysłano klucz na serwer</string>
+ <string name="key_sign_success">Pomyślnie podpisano klucz</string>
+ <string name="list_empty">Lista jest pusta!</string>
+ <string name="nfc_successfull">Pomyślnie wysłano klucz przez NFC!</string>
+ <string name="key_copied_to_clipboard">Klucz został skopiowany do schowka!</string>
+ <string name="key_has_already_been_signed">Klucz został już wcześniej podpisany!</string>
+ <string name="select_key_to_sign">Wybierz klucz, który zostanie użyty do podpisania!</string>
+ <string name="key_too_big_for_sharing">Klucz ma za duży rozmiar by być udostępniony w ten sposób!</string>
+ <!--errors
+ no punctuation, all lowercase,
+ they will be put after "error_message", e.g. "Error: file not found"-->
+ <string name="error_file_delete_failed">usuwanie \'%s\' zakończone niepowodzeniem</string>
+ <string name="error_file_not_found">plik nie znaleziony</string>
+ <string name="error_no_secret_key_found">nie znaleziono pasującego klucza prywatnego</string>
+ <string name="error_no_known_encryption_found">napotkano nieznany rodzaj szyfrowania</string>
+ <string name="error_external_storage_not_ready">zewnętrzne urządzenie jest niegotowe</string>
+ <string name="error_invalid_email">nieprawidłowy adres email \'%s\'</string>
+ <string name="error_key_size_minimum512bit">klucz musi mieć rozmiar co najmniej 512 bitów</string>
+ <string name="error_master_key_must_not_be_el_gamal">klucz EnGamal nie może być kluczem głównym</string>
+ <string name="error_unknown_algorithm_choice">wybrano nieznany algorytm</string>
+ <string name="error_user_id_needs_a_name">musisz wskazać imię</string>
+ <string name="error_user_id_no_email">nie znaleziono adresu email</string>
+ <string name="error_user_id_needs_an_email_address">musisz wskazać adres email</string>
+ <string name="error_key_needs_a_user_id">potrzeba co najmniej jednego identyfikatora użytkownika</string>
+ <string name="error_main_user_id_must_not_be_empty">główny identyfikator użytkownika nie może być pusty</string>
+ <string name="error_key_needs_master_key">potrzeba co najmniej klucza głównego</string>
+ <string name="error_no_encryption_keys_or_passphrase">nie podano hasła ani klucza szyfrującego</string>
+ <string name="error_signature_failed">podpisywanie nie powiodło się</string>
+ <string name="error_no_signature_passphrase">nie podano hasła</string>
+ <string name="error_no_signature_key">nie podano klucza podpisu</string>
+ <string name="error_invalid_data">nieprawidłowe dane</string>
+ <string name="error_corrupt_data">uszkodzone dane</string>
+ <string name="error_integrity_check_failed">Sprawdzanie spójności zakończone niepowodzeniem! Dane były modyfikowane!</string>
+ <string name="error_no_symmetric_encryption_packet">nie znaleziono pakietu z szyfrowaniem symatrycznym</string>
+ <string name="error_wrong_passphrase">nieprawidłowe hasło</string>
+ <string name="error_saving_keys">błąd przy zapisywaniu kluczy</string>
+ <string name="error_could_not_extract_private_key">nie można wyodrębnić klucza prywatnego</string>
+ <string name="error_only_files_are_supported">Dane binarne pozbawione pliku nie są obsługiwane. To jest wspierane tylko dla akcji ACTION_ENCRYPT_STREAM_AND_RETURN.</string>
+ <string name="error_jelly_bean_needed">Potrzebujesz Androida 4.1 Jelly Bean, aby korzystać z Android NFC Beam!</string>
+ <string name="error_nfc_needed">NCF jest niedostępne na twoim urządzeniu</string>
+ <string name="error_nothing_import">Nie ma nic do zaimportowania!</string>
+ <string name="error_expiry_must_come_after_creation">data wygaśnięcia musi być późniejsza niż data stworzenia</string>
+ <string name="error_save_first">zapisz najpierw pęk kluczy</string>
+ <string name="error_can_not_delete_contact">nie możesz usunąć tego kontaktu, ponieważ należy do ciebie.</string>
+ <string name="error_can_not_delete_contacts">nie możesz usunąć tych kontaktów, ponieważ należą do ciebie:\n%s</string>
+ <string name="error_keyserver_insufficient_query">Niewystarczające zapytanie do serwera</string>
+ <string name="error_keyserver_query">Odpytywanie serwera zakończone niepowodzeniem</string>
+ <string name="error_keyserver_too_many_responses">Za dużo odpowiedzi</string>
+ <string name="error_import_file_no_content">Plik jest pusty</string>
+ <string name="error_generic_report_bug">Wystąpił błąd ogólny, proszę zgłoś go autorom OpenKeychain.</string>
+ <plurals name="error_can_not_delete_info">
+ <item quantity="one">Usuń go z ekranu \'Moje klucze\'!</item>
+ <item quantity="few">Usuń je z ekranu \'Moje klucze\'!</item>
+ <item quantity="other">Usuń je z ekranu \'Moje klucze\'!</item>
+ </plurals>
+ <plurals name="error_import_non_pgp_part">
+ <item quantity="one">Część wczytanego pliku jest poprawnym obiektem OpenPGP, ale nie jest kluczem OpenPGP</item>
+ <item quantity="few">Część wczytanego pliku to poprawne obiekty OpenPGP, ale nie są kluczami OpenPGP</item>
+ <item quantity="other">Część wczytanego pliku to poprawne obiekty OpenPGP, ale nie są kluczami OpenPGP</item>
+ </plurals>
+ <string name="error_change_something_first">Musisz dokonać zmian w pęku kluczy zanim będziesz mógł go zachować</string>
+ <!--progress dialogs, usually ending in '…'-->
+ <string name="progress_done">Gotowe.</string>
+ <string name="progress_cancel">Anuluj</string>
+ <string name="progress_saving">zapisywanie...</string>
+ <string name="progress_importing">importowanie...</string>
+ <string name="progress_exporting">eksportowanie...</string>
+ <string name="progress_building_key">budowanie klucza...</string>
+ <string name="progress_preparing_master_key">przygotowywanie klucza glównego...</string>
+ <string name="progress_certifying_master_key">podpisywanie klucza głównego...</string>
+ <string name="progress_building_master_key">budowanie głównego zbioru kluczy...</string>
+ <string name="progress_adding_sub_keys">dodawanie podkluczy...</string>
+ <string name="progress_saving_key_ring">zapisywanie klucza...</string>
+ <plurals name="progress_exporting_key">
+ <item quantity="one">eksportowanie klucza...</item>
+ <item quantity="few">eksportowanie kluczy...</item>
+ <item quantity="other">eksportowanie kluczy...</item>
+ </plurals>
+ <plurals name="progress_generating">
+ <item quantity="one">generowanie klucza, może to potrwać do 3 minut...</item>
+ <item quantity="few">generowanie kluczy, może to potrwać do 3 minut...</item>
+ <item quantity="other">generowanie kluczy, może to potrwać do 3 minut...</item>
+ </plurals>
+ <string name="progress_extracting_signature_key">wyodrębnianie klucza podpisu...</string>
+ <string name="progress_extracting_key">wyodrębnianie klucza...</string>
+ <string name="progress_preparing_streams">przygotowywanie strumieni...</string>
+ <string name="progress_encrypting">szyfrowanie danych...</string>
+ <string name="progress_decrypting">deszyfrowywanie danych...</string>
+ <string name="progress_preparing_signature">przygotowywanie podpisu...</string>
+ <string name="progress_generating_signature">generowanie podpisu...</string>
+ <string name="progress_processing_signature">przetwarzanie podpisu...</string>
+ <string name="progress_verifying_signature">weryfikowanie podpisu...</string>
+ <string name="progress_signing">podpisywanie...</string>
+ <string name="progress_reading_data">czytanie danych...</string>
+ <string name="progress_finding_key">szukanie klucza...</string>
+ <string name="progress_decompressing_data">dekompresja danych...</string>
+ <string name="progress_verifying_integrity">weryfikacja spójności...</string>
+ <string name="progress_deleting_securely">usuwanie \'%s\' bezpiecznie…</string>
+ <string name="progress_querying">odpytywanie...</string>
+ <!--action strings-->
+ <string name="hint_public_keys">Wyszukaj klucze publiczne</string>
+ <string name="hint_secret_keys">Wyszukaj klucze prywatne</string>
+ <string name="action_share_key_with">Udostępnij klucz...</string>
+ <!--key bit length selections-->
+ <string name="key_size_512">512</string>
+ <string name="key_size_1024">1024</string>
+ <string name="key_size_2048">2048</string>
+ <string name="key_size_4096">4096</string>
+ <!--compression-->
+ <string name="compression_fast">szybka</string>
+ <string name="compression_very_slow">bardzo wolna</string>
+ <!--Help-->
+ <string name="help_tab_start">Początek</string>
+ <string name="help_tab_faq">FAQ</string>
+ <string name="help_tab_nfc_beam">NFC Beam</string>
+ <string name="help_tab_changelog">Dziennik zmian</string>
+ <string name="help_tab_about">O programie</string>
+ <string name="help_about_version">Wersja:</string>
+ <!--Import-->
+ <string name="import_import">Zaimportuj wybrane klucze</string>
+ <string name="import_sign_and_upload">Importuj, podpisz i wyślij wybrane klucze</string>
+ <string name="import_from_clipboard">Importuj ze schowka</string>
+ <plurals name="import_qr_code_missing">
+ <item quantity="one">Brakuje kodu QR o identyfikatorze %s</item>
+ <item quantity="few">Brakuje kodów QR o identyfikatorach %s</item>
+ <item quantity="other">Brakuje kodów QR o identyfikatorach %s</item>
+ </plurals>
+ <string name="import_qr_code_start_with_one">Zacznij od kodu QR o identyfikatorze 1</string>
+ <string name="import_qr_code_wrong">Kod QR zniekształcony! Spróbuj jeszcze raz!</string>
+ <string name="import_qr_code_finished">Skanowanie kodu QR zakończone!</string>
+ <string name="import_qr_code_too_short_fingerprint">Odcisk klucza jest za krótki (&lt; 16 znaków)</string>
+ <string name="import_qr_scan_button">Odczytaj kod QR przy pomocy \'Barcode Scanner\'</string>
+ <string name="import_nfc_text">Aby odbierać klucze przez NFC, urządzenie musi być odblokowane.</string>
+ <string name="import_nfc_help_button">Pomoc</string>
+ <string name="import_clipboard_button">Odczytaj klucz ze schowka</string>
+ <!--Intent labels-->
+ <string name="intent_decrypt_file">Deszyfruj plik korzystając z OpenKeychain</string>
+ <string name="intent_import_key">Importuj klucz korzystając z OpenKeychain</string>
+ <string name="intent_send_encrypt">Zaszyfruj korzystając z OpenKeychain</string>
+ <string name="intent_send_decrypt">Deszyfruj korzystając z OpenKeychain</string>
+ <!--Remote API-->
+ <string name="api_no_apps">Brak zarejestrowanych aplikacji!\n\nZewnętrzne aplikacje mogą żądać dostępu do OpenKeychain. Po przyznaniu dostępu, będa wyświetlone tutaj.</string>
+ <string name="api_settings_show_info">Pokaż zaawansowane informacje</string>
+ <string name="api_settings_hide_info">Ukryj zaawansowane informacje</string>
+ <string name="api_settings_show_advanced">Pokaż zaawanowane ustawienia</string>
+ <string name="api_settings_hide_advanced">Ukryj zaawansowane ustawienia</string>
+ <string name="api_settings_no_key">Nie wybrano klucza</string>
+ <string name="api_settings_select_key">Wybierz klucz</string>
+ <string name="api_settings_create_key">Utwórz nowy klucz dla tego konta</string>
+ <string name="api_settings_save">Zapisz</string>
+ <string name="api_settings_cancel">Anuluj</string>
+ <string name="api_settings_revoke">Odwołaj dostęp</string>
+ <string name="api_settings_delete_account">Usuń konto</string>
+ <string name="api_settings_package_name">Nazwa paczki</string>
+ <string name="api_settings_package_signature">Skrót SHA-256 podpisu paczki</string>
+ <string name="api_settings_accounts">Konta</string>
+ <string name="api_settings_accounts_empty">Nie przypisano żadnych kont do tej aplikacji</string>
+ <string name="api_create_account_text">Aplikacja prosi o zgodę na utworzenie nowego konta. Wskaż istniejący klucz prywatny lub wygeneruj nowy.\nAplikacje mogą używać wyłącznie klucze które tutaj wskażesz!</string>
+ <string name="api_register_text">Wyświetlona aplikacja prosi o dostęp do OpenKeychain.\nZezwolić?\n\nOSTRZEZENIE: Jeżeli nie wiesz, czemu wyświetlił się ten komunikat, nie zezwalaj na dostęp! Możesz to również zrobić później, korzystając z ekranu \'Zarejestrowane aplikacje\'.</string>
+ <string name="api_register_allow">Zezwól na dostęp</string>
+ <string name="api_register_disallow">Odmów dostępu</string>
+ <string name="api_register_error_select_key">Wybierz klucz!</string>
+ <string name="api_select_pub_keys_missing_text">Nie znaleziono kluczy publiczych dla tych identyfikatorów użytkownika:</string>
+ <string name="api_select_pub_keys_dublicates_text">Więcej niż jeden klucz publiczny istnieje dla tych identyfikatorów użytkownika:</string>
+ <string name="api_select_pub_keys_text">Proszę przejrzeć listę adresatów!</string>
+ <string name="api_error_wrong_signature">Sprawdzanie podpisu zakończone niepowodzeniem! Czy zainstalowałeś tę aplikację z innego źródła? Jeżeli jesteś pewien, że nie jest to atak, odwołaj rejestrację teg aplikacji w OpenKeychain, a następnie zarejestruj ją ponownie.</string>
+ <!--Share-->
+ <string name="share_qr_code_dialog_title">Udostępnij przez kod QR</string>
+ <string name="share_qr_code_dialog_start">Przejdź przez wszystkiego kody QR korzystając z przycisku \'Nastepny\' i skanuj je pojedynczo.</string>
+ <string name="share_qr_code_dialog_fingerprint_text">Odcisk:</string>
+ <string name="share_qr_code_dialog_progress">Kod QR o identyfikatorze %1$d z %2$d</string>
+ <string name="share_nfc_dialog">Udostępnij przez NFC</string>
+ <!--Key list-->
+ <plurals name="key_list_selected_keys">
+ <item quantity="one">1 klucz wybrany.</item>
+ <item quantity="few">%d kluczy wybranych.</item>
+ <item quantity="other">%d kluczy wybranych.</item>
+ </plurals>
+ <string name="key_list_empty_text1">Żadne klucze nie są jeszcze dostępne...</string>
+ <string name="key_list_empty_text2">Możesz zacząć od</string>
+ <string name="key_list_empty_text3">lub</string>
+ <string name="key_list_empty_button_create">tworzenie własnego klucza</string>
+ <string name="key_list_empty_button_import">importowanie kluczy.</string>
+ <!--Key view-->
+ <string name="key_view_action_edit">Edytuj ten klucz</string>
+ <string name="key_view_action_encrypt">Zaszyfruj do tego kontaktu</string>
+ <string name="key_view_action_certify">Certyfikuj klucz tego kontaktu</string>
+ <string name="key_view_tab_main">Informacje</string>
+ <string name="key_view_tab_certs">Certyfikaty</string>
+ <!--Navigation Drawer-->
+ <string name="nav_contacts">Klucze</string>
+ <string name="nav_encrypt">Podpisz i zaszyfruj</string>
+ <string name="nav_decrypt">Deszyfruj i weryfikuj</string>
+ <string name="nav_import">Importuj klucze</string>
+ <string name="nav_secret_keys">Moje klucze</string>
+ <string name="nav_apps">Zarejestrowane aplikacje</string>
+ <string name="drawer_open">Otwórz panel nawigacji</string>
+ <string name="drawer_close">Zamknij panel nawigacji</string>
+ <string name="edit">Edytuj</string>
+ <string name="my_keys">Moje klucze</string>
+ <string name="label_secret_key">Klucz prywatny</string>
+ <string name="secret_key_yes">dostępny</string>
+ <string name="secret_key_no">niedostepny</string>
+ <!--hints-->
+ <string name="encrypt_content_edit_text_hint">Wpisz tutaj wiadomość do zaszyfrowania i/lub podpisania...</string>
+ <string name="decrypt_content_edit_text_hint">Wpisz tutaj tekst do zaszyfrowania i/lub zweryfikowania...</string>
+ <!--unsorted-->
+ <string name="section_uids_to_sign">Identyfikator użytkownika do podpisu</string>
+ <string name="progress_re_adding_certs">Ponowne stosowanie certyfikatów</string>
+</resources>
diff --git a/OpenKeychain/src/main/res/values-pt-rBR/strings.xml b/OpenKeychain/src/main/res/values-pt-rBR/strings.xml
new file mode 100644
index 000000000..fc802092c
--- /dev/null
+++ b/OpenKeychain/src/main/res/values-pt-rBR/strings.xml
@@ -0,0 +1,29 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<resources>
+ <!--title-->
+ <!--section-->
+ <!--button-->
+ <!--menu-->
+ <!--label-->
+ <string name="unknown_status"></string>
+ <!--choice-->
+ <!--key flags-->
+ <!--sentences-->
+ <!--errors
+ no punctuation, all lowercase,
+ they will be put after "error_message", e.g. "Error: file not found"-->
+ <!--progress dialogs, usually ending in '…'-->
+ <!--action strings-->
+ <!--key bit length selections-->
+ <!--compression-->
+ <!--Help-->
+ <!--Import-->
+ <!--Intent labels-->
+ <!--Remote API-->
+ <!--Share-->
+ <!--Key list-->
+ <!--Key view-->
+ <!--Navigation Drawer-->
+ <!--hints-->
+ <!--unsorted-->
+</resources>
diff --git a/OpenKeychain/src/main/res/values-ru/strings.xml b/OpenKeychain/src/main/res/values-ru/strings.xml
new file mode 100644
index 000000000..5fe4dd587
--- /dev/null
+++ b/OpenKeychain/src/main/res/values-ru/strings.xml
@@ -0,0 +1,456 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<resources>
+ <!--title-->
+ <string name="title_manage_public_keys">Контакты</string>
+ <string name="title_manage_secret_keys">Секретные ключи</string>
+ <string name="title_select_recipients">Выбрать Публичный ключ</string>
+ <string name="title_select_secret_key">Выбрать Секретный ключ</string>
+ <string name="title_encrypt">Зашифровать</string>
+ <string name="title_decrypt">Расшифровать</string>
+ <string name="title_authentication">Пароль</string>
+ <string name="title_create_key">Создать ключ</string>
+ <string name="title_edit_key">Изменить ключ</string>
+ <string name="title_preferences">Настройки</string>
+ <string name="title_api_registered_apps">Связанные приложения</string>
+ <string name="title_key_server_preference">Настройки сервера ключей</string>
+ <string name="title_change_passphrase">Изменить пароль</string>
+ <string name="title_set_passphrase">Задать пароль</string>
+ <string name="title_send_email">Отправить...</string>
+ <string name="title_send_file">Отправить файл</string>
+ <string name="title_encrypt_to_file">Зашифровать в файл</string>
+ <string name="title_decrypt_to_file">Расшифровать в файл</string>
+ <string name="title_import_keys">Импорт ключей</string>
+ <string name="title_export_key">Экспортировать ключ</string>
+ <string name="title_export_keys">Экспорт ключей</string>
+ <string name="title_key_not_found">Ключ не найден</string>
+ <string name="title_key_server_query">Запросить сервер ключей</string>
+ <string name="title_send_key">Загрузить на сервер ключей</string>
+ <string name="title_unknown_signature_key">Неизвестная подпись</string>
+ <string name="title_certify_key">Сертифицировать ключ</string>
+ <string name="title_key_details">Сведения о ключе</string>
+ <string name="title_help">Помощь</string>
+ <!--section-->
+ <string name="section_user_ids">ID пользователя</string>
+ <string name="section_keys">Ключи</string>
+ <string name="section_general">Приложение</string>
+ <string name="section_defaults">Алгоритмы</string>
+ <string name="section_advanced">Дополнительно</string>
+ <string name="section_master_key">Основной ключ</string>
+ <string name="section_master_user_id">Владелец</string>
+ <string name="section_actions">Действия</string>
+ <string name="section_certification_key">Ваш ключ для сертификации</string>
+ <string name="section_upload_key">Загрузить ключ</string>
+ <string name="section_key_server">Сервер ключей</string>
+ <string name="section_encrypt_and_or_sign">Зашифровать и/или Подписать</string>
+ <string name="section_decrypt_verify">Расшифровать и проверить</string>
+ <!--button-->
+ <string name="btn_sign">Подписать</string>
+ <string name="btn_certify">Сертифицировать</string>
+ <string name="btn_decrypt">Расшифровать</string>
+ <string name="btn_decrypt_verify">Расшифровать и проверить</string>
+ <string name="btn_decrypt_verify_clipboard">Из буфера обмена</string>
+ <string name="btn_select_encrypt_keys">Выбрать получателей</string>
+ <string name="btn_encrypt_file">Зашифровать файл</string>
+ <string name="btn_save">Сохранить</string>
+ <string name="btn_do_not_save">Отмена</string>
+ <string name="btn_delete">Удалить</string>
+ <string name="btn_no_date">Нет</string>
+ <string name="btn_okay">Да</string>
+ <string name="btn_change_passphrase">Изменить новый пароль</string>
+ <string name="btn_set_passphrase">Задать новый пароль</string>
+ <string name="btn_search">Поиск</string>
+ <string name="btn_export_to_server">Загрузить на сервер ключей</string>
+ <string name="btn_next">Далее</string>
+ <string name="btn_back">Назад</string>
+ <string name="btn_clipboard">Буфер обмена</string>
+ <string name="btn_share">Поделиться...</string>
+ <string name="btn_lookup_key">Найти ключ</string>
+ <string name="btn_encryption_advanced_settings_show">Показать расширенные настройки</string>
+ <string name="btn_encryption_advanced_settings_hide">Скрыть расширенные настройки</string>
+ <!--menu-->
+ <string name="menu_preferences">Настройки</string>
+ <string name="menu_help">Помощь</string>
+ <string name="menu_import_from_file">Импорт из файла</string>
+ <string name="menu_import_from_qr_code">Импорт из QR кода</string>
+ <string name="menu_import">Импорт</string>
+ <string name="menu_import_from_nfc">Импорт из NFC</string>
+ <string name="menu_export_key">Экспорт в файл</string>
+ <string name="menu_delete_key">Удалить ключ</string>
+ <string name="menu_create_key">Создать ключ</string>
+ <string name="menu_create_key_expert">Создать ключ (эксперт)</string>
+ <string name="menu_search">Поиск</string>
+ <string name="menu_import_from_key_server">Сервер ключей</string>
+ <string name="menu_key_server">Сервер ключей...</string>
+ <string name="menu_update_key">Обновить с сервера ключей</string>
+ <string name="menu_export_key_to_server">Загрузить на сервер ключей</string>
+ <string name="menu_share">Отправить...</string>
+ <string name="menu_share_title_fingerprint">Отправить отпечаток...</string>
+ <string name="menu_share_title">Отправить ключ...</string>
+ <string name="menu_share_default_fingerprint">Отправить</string>
+ <string name="menu_share_default">Отправить</string>
+ <string name="menu_share_qr_code">QR код</string>
+ <string name="menu_share_qr_code_fingerprint">QR код</string>
+ <string name="menu_share_nfc">через NFC</string>
+ <string name="menu_copy_to_clipboard">Копировать в буфер</string>
+ <string name="menu_sign_key">Подписать ключ</string>
+ <string name="menu_beam_preferences">Настройки Beam</string>
+ <string name="menu_key_edit_cancel">Отмена</string>
+ <string name="menu_encrypt_to">Зашифровать....</string>
+ <string name="menu_select_all">Выбрать все</string>
+ <string name="menu_add_keys">Добавить ключи</string>
+ <!--label-->
+ <string name="label_sign">Подписать</string>
+ <string name="label_message">Сообщение</string>
+ <string name="label_file">Файл</string>
+ <string name="label_no_passphrase">Без пароля</string>
+ <string name="label_passphrase">Пароль</string>
+ <string name="label_passphrase_again">Еще раз</string>
+ <string name="label_algorithm">Алгоритм</string>
+ <string name="label_ascii_armor">ASCII формат</string>
+ <string name="label_select_public_keys">Получатели</string>
+ <string name="label_delete_after_encryption">Удалить после шифрования</string>
+ <string name="label_delete_after_decryption">Удалить после расшифровки</string>
+ <string name="label_share_after_encryption">Отправить после шифрования</string>
+ <string name="label_encryption_algorithm">Алгоритм шифрования</string>
+ <string name="label_hash_algorithm">Hash-алгоритм</string>
+ <string name="label_passphrase_cache_ttl">Помнить пароль</string>
+ <string name="label_message_compression">Сжатие сообщения</string>
+ <string name="label_file_compression">Сжатие файла</string>
+ <string name="label_force_v3_signature">Использовать старые OpenPGPV3 подписи</string>
+ <string name="label_key_servers">Серверы ключей</string>
+ <string name="label_key_id">ID ключа</string>
+ <string name="label_creation">Создан</string>
+ <string name="label_expiry">Годен до...</string>
+ <string name="label_usage">Применение</string>
+ <string name="label_key_size">Размер ключа</string>
+ <string name="label_main_user_id">Основной ID пользователя</string>
+ <string name="label_name">Имя</string>
+ <string name="label_comment">Комментарий</string>
+ <string name="label_email">Email</string>
+ <string name="label_send_key">После сертификации загрузить ключ на сервер</string>
+ <string name="label_fingerprint">Отпечаток</string>
+ <string name="select_keys_button_default">Выбрать</string>
+ <string name="expiry_date_dialog_title">Срок годности</string>
+ <plurals name="select_keys_button">
+ <item quantity="one">%d выбран</item>
+ <item quantity="few">%d выбрано</item>
+ <item quantity="other">%d выбрано</item>
+ </plurals>
+ <string name="user_id_no_name">&lt;нет имени&gt;</string>
+ <string name="none">&lt;нет&gt;</string>
+ <string name="no_key">&lt;нет ключа&gt;</string>
+ <string name="no_email">&lt;нет email&gt;</string>
+ <string name="unknown_status"></string>
+ <string name="can_encrypt">шифрование</string>
+ <string name="can_sign">подпись</string>
+ <string name="expired">просрочен</string>
+ <string name="revoked">отозван</string>
+ <string name="user_id">ID пользователя</string>
+ <plurals name="n_contacts">
+ <item quantity="one">1 контакт</item>
+ <item quantity="few">%d контактов</item>
+ <item quantity="other">%d контактов</item>
+ </plurals>
+ <plurals name="n_key_servers">
+ <item quantity="one">%d сервер ключей</item>
+ <item quantity="few">%d серверов ключей</item>
+ <item quantity="other">%d серверов ключей</item>
+ </plurals>
+ <string name="fingerprint">Отпечаток:</string>
+ <string name="secret_key">Секретный ключ:</string>
+ <!--choice-->
+ <string name="choice_none">Нет</string>
+ <string name="choice_15secs">15 секунд</string>
+ <string name="choice_1min">1 минуту</string>
+ <string name="choice_3mins">3 минуты</string>
+ <string name="choice_5mins">5 минут</string>
+ <string name="choice_10mins">10 минут</string>
+ <string name="choice_20mins">20 минут</string>
+ <string name="choice_40mins">40 минут</string>
+ <string name="choice_1hour">1 час</string>
+ <string name="choice_2hours">2 часа</string>
+ <string name="choice_4hours">4 часа</string>
+ <string name="choice_8hours">8 часов</string>
+ <string name="choice_forever">всегда</string>
+ <string name="dsa">DSA</string>
+ <string name="elgamal">ElGamal</string>
+ <string name="rsa">RSA</string>
+ <string name="filemanager_title_open">Открыть...</string>
+ <string name="warning">Внимание</string>
+ <string name="error">Ошибка</string>
+ <string name="error_message">Ошибка: %s</string>
+ <!--key flags-->
+ <string name="flag_certify">Сертифицировать</string>
+ <string name="flag_sign">Подписать</string>
+ <string name="flag_encrypt">Зашифровать</string>
+ <!--sentences-->
+ <string name="wrong_passphrase">Неправ. пароль</string>
+ <string name="using_clipboard_content">Следить за буфером обмена</string>
+ <string name="set_a_passphrase">Сначала задайте пароль</string>
+ <string name="no_filemanager_installed">Нет совместимого менеджера файлов.</string>
+ <string name="passphrases_do_not_match">Пароли не совпадают.</string>
+ <string name="passphrase_must_not_be_empty">Пожалуйста, введите пароль.</string>
+ <string name="passphrase_for_symmetric_encryption">Симметричное шифрование.</string>
+ <string name="passphrase_for">Введите пароль для\n\'%s\'</string>
+ <string name="file_delete_confirmation">Вы уверены, что хотите удалить\n%s ?</string>
+ <string name="file_delete_successful">Удалено.</string>
+ <string name="no_file_selected">Сначала выберите файл.</string>
+ <string name="decryption_successful">Расшифровано и/или проверено.</string>
+ <string name="encryption_successful">Подписано и/или зашифровано.</string>
+ <string name="encryption_to_clipboard_successful">Подписано и/или зашифровано в буфер обмена.</string>
+ <string name="enter_passphrase_twice">Дважды введите пароль.</string>
+ <string name="select_encryption_key">Укажите хотя бы один ключ.</string>
+ <string name="select_encryption_or_signature_key">Выберите хотя бы один ключ для шифрования или подписи.</string>
+ <string name="specify_file_to_encrypt_to">Пожалуйста, выберите файл для шифрования.\nВНИМАНИЕ! Если файл существует, он будет перезаписан.</string>
+ <string name="specify_file_to_decrypt_to">Пожалуйста, выберите файл для расшифровки.\nВНИМАНИЕ! Если файл существует, он будет перезаписан.</string>
+ <string name="specify_file_to_export_to">Пожалуйста, выберите файл для экспорта.\nВНИМАНИЕ! Если файл существует, он будет перезаписан.</string>
+ <string name="specify_file_to_export_secret_keys_to">Пожалуйста, выберите файл для экспорта.\nВНИМАНИЕ: Вы экспортируете СЕКРЕТНЫЙ ключ.\nВНИМАНИЕ: Если файл существует, он будет перезаписан.</string>
+ <string name="key_deletion_confirmation">Вы уверены, что ходите удалить ключ \'%s\'?\nЭто действие нельзя отменить!</string>
+ <string name="key_deletion_confirmation_multi">Вы уверены, что хотите удалить ВСЕ выбранные ключи?\nЭто действие нельзя отменить!</string>
+ <string name="secret_key_deletion_confirmation">Вы уверены, что ходите удалить СЕКРЕТНЫЙ ключ \'%s\'?\nЭто действие нельзя отменить!</string>
+ <string name="public_key_deletetion_confirmation">Вы правда хотите удалить ПУБЛИЧНЫЙ ключ \'%s\'?\nЭто нельзя отменить!</string>
+ <string name="secret_key_delete_text">Удалить секретные ключи?</string>
+ <string name="also_export_secret_keys">Экспортировать секретные ключи?</string>
+ <plurals name="keys_added_and_updated_1">
+ <item quantity="one">Успешно добавлено %d ключ</item>
+ <item quantity="few">Успешно добавлено %d ключей</item>
+ <item quantity="other">Успешно добавлено %d ключей</item>
+ </plurals>
+ <plurals name="keys_added_and_updated_2">
+ <item quantity="one">и обновлен %d ключ.</item>
+ <item quantity="few">и обновлено %d ключей.</item>
+ <item quantity="other">и обновлено %d ключей.</item>
+ </plurals>
+ <plurals name="keys_added">
+ <item quantity="one">Добавлен %d ключ</item>
+ <item quantity="few">Добавлено %d ключей</item>
+ <item quantity="other">Добавлено %d ключей</item>
+ </plurals>
+ <plurals name="keys_updated">
+ <item quantity="one">Обновлен %d ключ.</item>
+ <item quantity="few">Обновлено %d ключей.</item>
+ <item quantity="other">Обновлено %d ключей.</item>
+ </plurals>
+ <string name="no_keys_added_or_updated">Нет обновленных или добавленных ключей</string>
+ <string name="key_exported">Успешный экспорт 1 ключа.</string>
+ <string name="keys_exported">Экспортировано %d ключей.</string>
+ <string name="no_keys_exported">Ключи не были экспортированы.</string>
+ <string name="key_creation_el_gamal_info">Инфо: ElGamal подходит только для дополнительных ключей. При создании ключа будет использован ближайший из размеров: 1536, 2048, 3072, 4096, или 8192.</string>
+ <string name="key_creation_weak_rsa_info">Внимание: создание ключей RSA длиной 1024 бита и менее признано небезопасным. Данная возможность отключена.</string>
+ <string name="key_not_found">Не удается найти ключ %08X.</string>
+ <plurals name="keys_found">
+ <item quantity="one">Найден %d ключ.</item>
+ <item quantity="few">Найдено %d ключей.</item>
+ <item quantity="other">Найдено %d ключей.</item>
+ </plurals>
+ <string name="unknown_signature">Неизвестная подпись. Нажмите кнопку, что бы найти ключ.</string>
+ <plurals name="bad_keys_encountered">
+ <item quantity="one">%d плохой секретный ключ проигнорирован. Возможно, вы экспортируете с параметром\n--export-secret-subkeys\nВместо этого используйте\n--export-secret-keys</item>
+ <item quantity="few">%d плохих секретных ключей проигнорировано. Возможно, вы экспортируете с параметром\n--export-secret-subkeys\nВместо этого используйте\n--export-secret-keys\n</item>
+ <item quantity="other">%d плохих секретных ключей проигнорировано. Возможно, вы экспортируете с параметром\n--export-secret-subkeys\nВместо этого используйте\n--export-secret-keys\n</item>
+ </plurals>
+ <string name="key_send_success">Ключ успешно загружен на сервер</string>
+ <string name="key_sign_success">Ключ успешно подписан</string>
+ <string name="list_empty">Список пуст!</string>
+ <string name="nfc_successfull">Ключ успешно передан через NFC!</string>
+ <string name="key_copied_to_clipboard">Ключ скопирован в буфер обмена!</string>
+ <string name="key_has_already_been_signed">Ключ уже был подписан ранее!</string>
+ <string name="select_key_to_sign">Выберите ключ, используемый для подписи!</string>
+ <string name="key_too_big_for_sharing">Ключ слишком большой для этого способа передачи!</string>
+ <!--errors
+ no punctuation, all lowercase,
+ they will be put after "error_message", e.g. "Error: file not found"-->
+ <string name="error_file_delete_failed">ошибка удаления \'%s\'</string>
+ <string name="error_file_not_found">файл не найден</string>
+ <string name="error_no_secret_key_found">нет подходящего секретного ключа</string>
+ <string name="error_no_known_encryption_found">алгоритм шифрования не определен</string>
+ <string name="error_external_storage_not_ready">внешняя память не готова</string>
+ <string name="error_invalid_email">неправильный email \'%s\'</string>
+ <string name="error_key_size_minimum512bit">размер ключа должен быть не менее 512бит</string>
+ <string name="error_master_key_must_not_be_el_gamal">ключ ElGamal не может быть основным</string>
+ <string name="error_unknown_algorithm_choice">выбран неизвестный алгоритм</string>
+ <string name="error_user_id_needs_a_name">необходимо указать имя</string>
+ <string name="error_user_id_no_email">email не найден</string>
+ <string name="error_user_id_needs_an_email_address">необходимо указать email</string>
+ <string name="error_key_needs_a_user_id">необходим хотя бы один id пользователя</string>
+ <string name="error_main_user_id_must_not_be_empty">основная запись пользователя не может быть пустой</string>
+ <string name="error_key_needs_master_key">необходим основной ключ</string>
+ <string name="error_no_encryption_keys_or_passphrase">не задан ключ или пароль для шифрования</string>
+ <string name="error_signature_failed">ошибка подписи</string>
+ <string name="error_no_signature_passphrase">пароль не задан</string>
+ <string name="error_no_signature_key">ключ для подписи не задан</string>
+ <string name="error_invalid_data">некорректное шифрование</string>
+ <string name="error_corrupt_data">данные повреждены</string>
+ <string name="error_integrity_check_failed">Проверка целостности не удалась! Данные изменились!</string>
+ <string name="error_no_symmetric_encryption_packet">не найден блок симметричного шифрования</string>
+ <string name="error_wrong_passphrase">неправильный пароль</string>
+ <string name="error_saving_keys">ошибка сохранения ключей</string>
+ <string name="error_could_not_extract_private_key">не удалось извлечь личный ключ</string>
+ <string name="error_only_files_are_supported">Прямая передача данных без использования файла в памяти устройства не поддерживается. Это возможно только для ACTION_ENCRYPT_STREAM_AND_RETURN.</string>
+ <string name="error_jelly_bean_needed">Для использования NFC Beam требуется Android 4.1+ !</string>
+ <string name="error_nfc_needed">Ваше устройство не поддерживает NFC!</string>
+ <string name="error_nothing_import">Нет данных для импорта!</string>
+ <string name="error_expiry_must_come_after_creation">срок годности не может быть раньше даты создания</string>
+ <string name="error_can_not_delete_contact">нельзя удалить свой собственный контакт. Пожалуйста, удалите его в разделе \'Мои ключи\'!</string>
+ <string name="error_can_not_delete_contacts">это ваши собственные контакты, их нельзя удалить:\n%s</string>
+ <string name="error_keyserver_insufficient_query">Ограничение запроса сервера</string>
+ <string name="error_keyserver_query">Сбой запроса сервера ключей</string>
+ <string name="error_keyserver_too_many_responses">Слишком много ответов</string>
+ <string name="error_import_file_no_content">Файл пуст</string>
+ <string name="error_generic_report_bug">Выявлена ошибка. Пожалуйста, сообщите о ней разработчику.</string>
+ <plurals name="error_can_not_delete_info">
+ <item quantity="one">Пожалуйста, удалите его в разделе \'Мои ключи\'!</item>
+ <item quantity="few">Пожалуйста, удалите их в разделе \'Мои ключи\'!</item>
+ <item quantity="other">Пожалуйста, удалите их в разделе \'Мои ключи\'!</item>
+ </plurals>
+ <plurals name="error_import_non_pgp_part">
+ <item quantity="one">часть загруженного файла содержит данные OpenPGP, но это не ключ</item>
+ <item quantity="few">части загруженного файла содержат данные OpenPGP, но это не ключ</item>
+ <item quantity="other">части загруженного файла содержат данные OpenPGP, но это не ключ</item>
+ </plurals>
+ <!--progress dialogs, usually ending in '…'-->
+ <string name="progress_done">Готово.</string>
+ <string name="progress_cancel">Отмена</string>
+ <string name="progress_saving">сохранение...</string>
+ <string name="progress_importing">импорт...</string>
+ <string name="progress_exporting">экспорт...</string>
+ <string name="progress_building_key">создание ключа...</string>
+ <string name="progress_preparing_master_key">подготовка основного ключа...</string>
+ <string name="progress_certifying_master_key">сертификация основного ключа...</string>
+ <string name="progress_building_master_key">создание основной связки...</string>
+ <string name="progress_adding_sub_keys">добавление доп. ключей...</string>
+ <string name="progress_saving_key_ring">сохранение ключа...</string>
+ <plurals name="progress_exporting_key">
+ <item quantity="one">экспорт ключа...</item>
+ <item quantity="few">экспорт ключей...</item>
+ <item quantity="other">экспорт ключей...</item>
+ </plurals>
+ <plurals name="progress_generating">
+ <item quantity="one">создание ключа. это может занять до 3 минут...</item>
+ <item quantity="few">создание ключей. это может занять до 3 минут...</item>
+ <item quantity="other">создание ключей. это может занять до 3 минут...</item>
+ </plurals>
+ <string name="progress_extracting_signature_key">извлечение подписи ключа...</string>
+ <string name="progress_extracting_key">извлечение ключа...</string>
+ <string name="progress_preparing_streams">подготовка к передаче...</string>
+ <string name="progress_encrypting">шифрование данных...</string>
+ <string name="progress_decrypting">расшифровка данных...</string>
+ <string name="progress_preparing_signature">подготовка подписи...</string>
+ <string name="progress_generating_signature">формирование подписи...</string>
+ <string name="progress_processing_signature">обработка подписи...</string>
+ <string name="progress_verifying_signature">проверка подписи...</string>
+ <string name="progress_signing">подписание...</string>
+ <string name="progress_reading_data">чтение данных...</string>
+ <string name="progress_finding_key">поиск ключа...</string>
+ <string name="progress_decompressing_data">распаковка данных...</string>
+ <string name="progress_verifying_integrity">проверка целостности...</string>
+ <string name="progress_deleting_securely">безопасное удаление \'%s\'...</string>
+ <string name="progress_querying">запрос...</string>
+ <!--action strings-->
+ <string name="hint_public_keys">Найти публичные ключи</string>
+ <string name="hint_secret_keys">Найти секретные ключи</string>
+ <string name="action_share_key_with">Отправить...</string>
+ <!--key bit length selections-->
+ <string name="key_size_512">512</string>
+ <string name="key_size_1024">1024</string>
+ <string name="key_size_2048">2048</string>
+ <string name="key_size_4096">4096</string>
+ <!--compression-->
+ <string name="compression_fast">быстро</string>
+ <string name="compression_very_slow">очень медленно</string>
+ <!--Help-->
+ <string name="help_tab_start">Начать</string>
+ <string name="help_tab_faq">ЧаВо</string>
+ <string name="help_tab_nfc_beam">NFC Beam</string>
+ <string name="help_tab_changelog">Изменения</string>
+ <string name="help_tab_about">О программе</string>
+ <string name="help_about_version">Версия:</string>
+ <!--Import-->
+ <string name="import_import">Импорт выбранных ключей</string>
+ <string name="import_sign_and_upload">Импорт, подписание и загрузка ключей</string>
+ <string name="import_from_clipboard">Импорт из буфера обмена</string>
+ <plurals name="import_qr_code_missing">
+ <item quantity="one">Не найдены QR код с ID %s</item>
+ <item quantity="few">Не найдены QR коды с ID %s</item>
+ <item quantity="other">Не найдены QR коды с ID %s</item>
+ </plurals>
+ <string name="import_qr_code_start_with_one">Пожалуйста, начните с QR кода с id 1</string>
+ <string name="import_qr_code_wrong">Некорректный QR код. Попробуйте снова!</string>
+ <string name="import_qr_code_finished">Сканирование QR завершено!</string>
+ <string name="import_qr_scan_button">Сканировать QR код с \'Barcode Scanner\'</string>
+ <string name="import_nfc_text">Разблокируйте устройство, что бы получить ключ через NFC.</string>
+ <string name="import_nfc_help_button">Помощь</string>
+ <string name="import_clipboard_button">Получить ключ из буфера</string>
+ <!--Intent labels-->
+ <string name="intent_decrypt_file">OpenKeychain: Расшифровать файл</string>
+ <string name="intent_import_key">OpenKeychain: Импортировать ключ</string>
+ <string name="intent_send_encrypt">OpenKeychain: Зашифровать</string>
+ <string name="intent_send_decrypt">OpenKeychain: Расшифровать</string>
+ <!--Remote API-->
+ <string name="api_no_apps">Нет связанных программ!\n\nСторонние программы могут запросить доступ к OpenKeychain, после чего они будут отражаться здесь.</string>
+ <string name="api_settings_show_info">Показать подробную информацию</string>
+ <string name="api_settings_hide_info">Скрыть подробную информацию</string>
+ <string name="api_settings_show_advanced">Показать расширенные настройки</string>
+ <string name="api_settings_hide_advanced">Скрыть расширенные настройки</string>
+ <string name="api_settings_no_key">Ключ не выбран</string>
+ <string name="api_settings_select_key">Выбрать ключ</string>
+ <string name="api_settings_create_key">Создать новый ключ для этого аккаунта</string>
+ <string name="api_settings_save">Сохранить</string>
+ <string name="api_settings_cancel">Отмена</string>
+ <string name="api_settings_revoke">Отозвать доступ</string>
+ <string name="api_settings_delete_account">Удалить аккаунт</string>
+ <string name="api_settings_package_name">Наименование пакета</string>
+ <string name="api_settings_package_signature">SHA-256 подписи пакета</string>
+ <string name="api_settings_accounts">Аккаунты</string>
+ <string name="api_settings_accounts_empty">Нет аккаунтов, связанных с этим приложением.</string>
+ <string name="api_create_account_text">Приложение запрашивает создание нового аккаунта. Выберите секретный ключ или создайте новый.\nПриложения ограничены в доступе только к выбраным здесь ключам!</string>
+ <string name="api_register_text">Данное приложение запрашивает доступ к OpenKeychain.\nРазрешить доступ?\n\nВНИМАНИЕ: Если вы не знаете почему возник этот запрос, откажите в доступе!\nПозже вы можете отозвать право доступа в разделе \"Зарегистрированные программы\".</string>
+ <string name="api_register_allow">Разрешить доступ</string>
+ <string name="api_register_disallow">Запретить доступ</string>
+ <string name="api_register_error_select_key">Пожалуйста, выберите ключ!</string>
+ <string name="api_select_pub_keys_missing_text">Для этих id не найдены публичные ключи:</string>
+ <string name="api_select_pub_keys_dublicates_text">Для этих id найдено более одного ключа:</string>
+ <string name="api_select_pub_keys_text">Пожалуйста, проверьте получателей!</string>
+ <string name="api_error_wrong_signature">Проверка подписи пакета не удалась! Если вы установили программу из другого источника, отзовите для неё доступ к этой программе или обновите право доступа.</string>
+ <!--Share-->
+ <string name="share_qr_code_dialog_title">Отправить как QR код</string>
+ <string name="share_qr_code_dialog_start">Сканируйте несколько QR подряд, нажимая \'Далее\' после каждого кода.</string>
+ <string name="share_qr_code_dialog_fingerprint_text">Отпечаток:</string>
+ <string name="share_qr_code_dialog_progress">QR код с id %1$d из %2$d</string>
+ <string name="share_nfc_dialog">Отправить через NFC</string>
+ <!--Key list-->
+ <plurals name="key_list_selected_keys">
+ <item quantity="one">1 ключ выбран.</item>
+ <item quantity="few">%d ключей выбрано.</item>
+ <item quantity="other">%d ключей выбрано.</item>
+ </plurals>
+ <string name="key_list_empty_text1">У вас пока нет ключей...</string>
+ <string name="key_list_empty_text2">Но Вы можете</string>
+ <string name="key_list_empty_text3">или</string>
+ <string name="key_list_empty_button_create">создать свой ключ</string>
+ <string name="key_list_empty_button_import">Импортировать ключи</string>
+ <!--Key view-->
+ <string name="key_view_action_edit">Изменить ключ</string>
+ <string name="key_view_action_encrypt">Зашифровать для этого получателя</string>
+ <string name="key_view_action_certify">Сертифицировать ключ этого контакта</string>
+ <string name="key_view_tab_main">Информация</string>
+ <string name="key_view_tab_certs">Сертификация</string>
+ <!--Navigation Drawer-->
+ <string name="nav_contacts">Ключи</string>
+ <string name="nav_encrypt">Подписать и зашифровать</string>
+ <string name="nav_decrypt">Расшифровать и проверить</string>
+ <string name="nav_import">Импорт ключей</string>
+ <string name="nav_secret_keys">Мои ключи</string>
+ <string name="nav_apps">Связанные приложения</string>
+ <string name="drawer_open">Открыть панель навигации</string>
+ <string name="drawer_close">Закрыть панель навигации</string>
+ <string name="edit">Изменить</string>
+ <string name="my_keys">Мои ключи</string>
+ <string name="label_secret_key">Секретный ключ</string>
+ <string name="secret_key_yes">доступен</string>
+ <string name="secret_key_no">не доступен</string>
+ <!--hints-->
+ <string name="encrypt_content_edit_text_hint">Напишите сообщение здесь, что бы зашифровать и/или подписать...</string>
+ <!--unsorted-->
+ <string name="section_uids_to_sign">Подписываемые ID пользователя</string>
+</resources>
diff --git a/OpenKeychain/src/main/res/values-sl-rSI/strings.xml b/OpenKeychain/src/main/res/values-sl-rSI/strings.xml
new file mode 100644
index 000000000..fc802092c
--- /dev/null
+++ b/OpenKeychain/src/main/res/values-sl-rSI/strings.xml
@@ -0,0 +1,29 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<resources>
+ <!--title-->
+ <!--section-->
+ <!--button-->
+ <!--menu-->
+ <!--label-->
+ <string name="unknown_status"></string>
+ <!--choice-->
+ <!--key flags-->
+ <!--sentences-->
+ <!--errors
+ no punctuation, all lowercase,
+ they will be put after "error_message", e.g. "Error: file not found"-->
+ <!--progress dialogs, usually ending in '…'-->
+ <!--action strings-->
+ <!--key bit length selections-->
+ <!--compression-->
+ <!--Help-->
+ <!--Import-->
+ <!--Intent labels-->
+ <!--Remote API-->
+ <!--Share-->
+ <!--Key list-->
+ <!--Key view-->
+ <!--Navigation Drawer-->
+ <!--hints-->
+ <!--unsorted-->
+</resources>
diff --git a/OpenKeychain/src/main/res/values-tr/strings.xml b/OpenKeychain/src/main/res/values-tr/strings.xml
new file mode 100644
index 000000000..d7e4afd7c
--- /dev/null
+++ b/OpenKeychain/src/main/res/values-tr/strings.xml
@@ -0,0 +1,138 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<resources>
+ <!--title-->
+ <string name="title_select_recipients">Açık Anahtar Seç</string>
+ <string name="title_select_secret_key">Özel Anahtar Seç</string>
+ <string name="title_encrypt">Şifrele</string>
+ <string name="title_create_key">Anahtar oluştur</string>
+ <string name="title_edit_key">Anahtarı düzenle</string>
+ <string name="title_preferences">Seçenekler</string>
+ <string name="title_import_keys">Anahtarları Al</string>
+ <string name="title_export_key">Anahtarı Ver</string>
+ <string name="title_export_keys">Anahtarları Ver</string>
+ <string name="title_key_not_found">Anahtar Bulunamadı</string>
+ <string name="title_unknown_signature_key">Bilinmeyen İmza Anahtarı</string>
+ <string name="title_key_details">Anahtar Detayları</string>
+ <string name="title_help">Yardım</string>
+ <!--section-->
+ <string name="section_user_ids">Kullanıcı IDleri</string>
+ <string name="section_keys">Anahtarlar</string>
+ <string name="section_general">Genel</string>
+ <string name="section_defaults">Varsayılanlar</string>
+ <string name="section_advanced">Gelişmiş</string>
+ <string name="section_upload_key">Anahtar Yükle</string>
+ <!--button-->
+ <string name="btn_sign">İmzala</string>
+ <string name="btn_select_encrypt_keys">Alıcıları Seç</string>
+ <string name="btn_save">Kaydet</string>
+ <string name="btn_do_not_save">İptal</string>
+ <string name="btn_delete">Sil</string>
+ <string name="btn_okay">Tamam</string>
+ <string name="btn_search">Ara</string>
+ <string name="btn_next">İleri</string>
+ <string name="btn_back">Geri</string>
+ <!--menu-->
+ <string name="menu_preferences">Ayarlar</string>
+ <string name="menu_help">Yardım</string>
+ <string name="menu_import_from_file">Dosyadan al</string>
+ <string name="menu_import_from_qr_code">QR Kodundan al</string>
+ <string name="menu_import">Al</string>
+ <string name="menu_import_from_nfc">NFCden al</string>
+ <string name="menu_export_key">Dosyaya ver</string>
+ <string name="menu_delete_key">Anahtar sil</string>
+ <string name="menu_create_key">Anahtar oluştur</string>
+ <string name="menu_create_key_expert">Anahtar oluştur (uzman)</string>
+ <string name="menu_search">Ara</string>
+ <string name="menu_copy_to_clipboard">Panoya kopyala</string>
+ <string name="menu_sign_key">Anahtarı imzala</string>
+ <string name="menu_key_edit_cancel">İptal</string>
+ <!--label-->
+ <string name="label_sign">İmzala</string>
+ <string name="label_message">Mesaj</string>
+ <string name="label_file">Dosya</string>
+ <string name="label_passphrase_again">Tekrar</string>
+ <string name="label_algorithm">Algoritma</string>
+ <string name="label_creation">Oluşturma</string>
+ <string name="label_usage">Kullanım</string>
+ <string name="label_key_size">Anahtar Boyutu</string>
+ <string name="label_name">İsim</string>
+ <string name="label_comment">Yorum</string>
+ <string name="label_email">Eposta</string>
+ <string name="unknown_status"></string>
+ <string name="secret_key">Özel Anahtar:</string>
+ <!--choice-->
+ <string name="choice_15secs">15 saniye</string>
+ <string name="choice_1min">1 dakika</string>
+ <string name="choice_3mins">3 dakika</string>
+ <string name="choice_5mins">5 dakika</string>
+ <string name="choice_10mins">10 dakika</string>
+ <string name="choice_20mins">20 dakika</string>
+ <string name="choice_40mins">40 dakika</string>
+ <string name="choice_1hour">1 saat</string>
+ <string name="choice_2hours">2 saat</string>
+ <string name="choice_4hours">4 saat</string>
+ <string name="choice_8hours">8 saat</string>
+ <string name="dsa">DSA</string>
+ <string name="elgamal">ElGamal</string>
+ <string name="rsa">RSA</string>
+ <string name="filemanager_title_open">Aç...</string>
+ <string name="warning">Uyarı</string>
+ <string name="error">Hata</string>
+ <string name="error_message">Hata: %s</string>
+ <!--key flags-->
+ <!--sentences-->
+ <string name="file_delete_successful">Başarıyla silindi.</string>
+ <string name="no_file_selected">Önce bir dosya seçin.</string>
+ <string name="key_not_found">Anahtar %08X bulunamadı.</string>
+ <string name="key_sign_success">Anahtar başarıyla imzalandı</string>
+ <string name="list_empty">Liste boş!</string>
+ <!--errors
+ no punctuation, all lowercase,
+ they will be put after "error_message", e.g. "Error: file not found"-->
+ <string name="error_file_not_found">dosya bulunamadı</string>
+ <string name="error_invalid_email">geçersiz eposta \'%s\'</string>
+ <string name="error_key_size_minimum512bit">anahtar uzunluğu en az 512bit olmalı</string>
+ <string name="error_corrupt_data">bozuk veri</string>
+ <!--progress dialogs, usually ending in '…'-->
+ <string name="progress_saving">kaydediliyor...</string>
+ <string name="progress_importing">alıyor...</string>
+ <string name="progress_exporting">veriyor...</string>
+ <string name="progress_building_key">anahtar oluşturuluyor...</string>
+ <string name="progress_preparing_signature">imza hazırlanıyor...</string>
+ <string name="progress_generating_signature">imza oluşturuluyor...</string>
+ <string name="progress_processing_signature">imza işleniyor...</string>
+ <string name="progress_verifying_signature">imza doğrulanıyor...</string>
+ <string name="progress_signing">imzalanıyor...</string>
+ <string name="progress_reading_data">veri okunuyor...</string>
+ <string name="progress_finding_key">anahtar bulunuyor...</string>
+ <!--action strings-->
+ <!--key bit length selections-->
+ <string name="key_size_512">512</string>
+ <string name="key_size_1024">1024</string>
+ <string name="key_size_2048">2048</string>
+ <string name="key_size_4096">4096</string>
+ <!--compression-->
+ <string name="compression_fast">hızlı</string>
+ <string name="compression_very_slow">çok yavaş</string>
+ <!--Help-->
+ <string name="help_tab_about">Hakkında</string>
+ <string name="help_about_version">Sürüm:</string>
+ <!--Import-->
+ <string name="import_import">Seçili anahtarları al</string>
+ <string name="import_nfc_help_button">Yardım</string>
+ <!--Intent labels-->
+ <!--Remote API-->
+ <string name="api_settings_no_key">Anahtar seçilmedi</string>
+ <string name="api_settings_select_key">Anahtar seç</string>
+ <string name="api_settings_save">Kaydet</string>
+ <string name="api_settings_cancel">İptal</string>
+ <string name="api_register_allow">Erişime izin ver</string>
+ <string name="api_register_disallow">Erişime izin verme</string>
+ <string name="api_register_error_select_key">Lütfen bir anahtar seçin!</string>
+ <!--Share-->
+ <!--Key list-->
+ <!--Key view-->
+ <!--Navigation Drawer-->
+ <!--hints-->
+ <!--unsorted-->
+</resources>
diff --git a/OpenKeychain/src/main/res/values-uk/strings.xml b/OpenKeychain/src/main/res/values-uk/strings.xml
new file mode 100644
index 000000000..01551fcd9
--- /dev/null
+++ b/OpenKeychain/src/main/res/values-uk/strings.xml
@@ -0,0 +1,467 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<resources>
+ <!--title-->
+ <string name="title_manage_public_keys">Контакти</string>
+ <string name="title_manage_secret_keys">Секретні ключі</string>
+ <string name="title_select_recipients">Виберіть публічний ключ</string>
+ <string name="title_select_secret_key">Виберіть секретний ключ</string>
+ <string name="title_encrypt">Зашифрувати</string>
+ <string name="title_decrypt">Розшифрувати</string>
+ <string name="title_authentication">Парольна фраза</string>
+ <string name="title_create_key">Створити ключ</string>
+ <string name="title_edit_key">Редагувати ключ</string>
+ <string name="title_preferences">Налаштування</string>
+ <string name="title_api_registered_apps">Зареєстровані програми</string>
+ <string name="title_key_server_preference">Налаштування сервера ключів</string>
+ <string name="title_change_passphrase">Змінити парольну фразу</string>
+ <string name="title_set_passphrase">Задати парольну фразу</string>
+ <string name="title_send_email">Надіслати листа…</string>
+ <string name="title_send_file">Надіслати файл…</string>
+ <string name="title_encrypt_to_file">Зашифрувати до файлу</string>
+ <string name="title_decrypt_to_file">Розшифрувати до файлу</string>
+ <string name="title_import_keys">Імпортувати ключі</string>
+ <string name="title_export_key">Експортувати ключ</string>
+ <string name="title_export_keys">Експортувати ключі</string>
+ <string name="title_key_not_found">Ключ не знайдено</string>
+ <string name="title_key_server_query">Сервер ключа запиту</string>
+ <string name="title_send_key">Завантажити на сервер ключів</string>
+ <string name="title_unknown_signature_key">Невідомий підпис ключа</string>
+ <string name="title_certify_key">Сертифікувати ключ</string>
+ <string name="title_key_details">Подробиці про ключ</string>
+ <string name="title_help">Довідка</string>
+ <!--section-->
+ <string name="section_user_ids">ІД користувача</string>
+ <string name="section_keys">Ключі</string>
+ <string name="section_general">Загальне</string>
+ <string name="section_defaults">Типове</string>
+ <string name="section_advanced">Додаткове</string>
+ <string name="section_master_key">Основний ключ</string>
+ <string name="section_master_user_id">ІД основного ключа</string>
+ <string name="section_actions">Дії</string>
+ <string name="section_certification_key">Ваш ключ використаний для сертифікації</string>
+ <string name="section_upload_key">Завантажити ключ</string>
+ <string name="section_key_server">Сервер ключів</string>
+ <string name="section_encrypt_and_or_sign">Шифрувати і/або підписати</string>
+ <string name="section_decrypt_verify">Розшифрувати і Перевірити</string>
+ <!--button-->
+ <string name="btn_sign">Підписати</string>
+ <string name="btn_certify">Сертифікувати</string>
+ <string name="btn_decrypt">Розшифрувати</string>
+ <string name="btn_decrypt_verify">Розшифрувати і Перевірити</string>
+ <string name="btn_decrypt_verify_clipboard">З буфера обміну</string>
+ <string name="btn_select_encrypt_keys">Вибрати одержувачів</string>
+ <string name="btn_encrypt_file">Шифрувати файл</string>
+ <string name="btn_save">Зберегти</string>
+ <string name="btn_do_not_save">Скасувати</string>
+ <string name="btn_delete">Вилучити</string>
+ <string name="btn_no_date">Жоден</string>
+ <string name="btn_okay">Гаразд</string>
+ <string name="btn_change_passphrase">Змінити нову парольну фразу</string>
+ <string name="btn_set_passphrase">Задати нову парольну фразу</string>
+ <string name="btn_search">Пошук</string>
+ <string name="btn_export_to_server">Завантажити на сервер ключів</string>
+ <string name="btn_next">Далі</string>
+ <string name="btn_back">Назад</string>
+ <string name="btn_clipboard">Буфер обміну</string>
+ <string name="btn_share">Поділитися через…</string>
+ <string name="btn_lookup_key">Шукати ключ</string>
+ <string name="btn_encryption_advanced_settings_show">Показати додаткові налаштування</string>
+ <string name="btn_encryption_advanced_settings_hide">Приховати додаткові налаштування</string>
+ <!--menu-->
+ <string name="menu_preferences">Параметри</string>
+ <string name="menu_help">Довідка</string>
+ <string name="menu_import_from_file">Імпорт з файлу</string>
+ <string name="menu_import_from_qr_code">Імпорт з штрих-коду</string>
+ <string name="menu_import">Імпорт</string>
+ <string name="menu_import_from_nfc">Імпорт з NFC</string>
+ <string name="menu_export_key">Експорт до файлу</string>
+ <string name="menu_delete_key">Вилучити ключ</string>
+ <string name="menu_create_key">Створити ключ</string>
+ <string name="menu_create_key_expert">Створити ключ (експерт)</string>
+ <string name="menu_search">Пошук</string>
+ <string name="menu_import_from_key_server">Сервер ключів</string>
+ <string name="menu_key_server">Сервер ключів…</string>
+ <string name="menu_update_key">Оновити з сервера ключів</string>
+ <string name="menu_export_key_to_server">Завантажити на сервер ключів</string>
+ <string name="menu_share">Поділитися…</string>
+ <string name="menu_share_title_fingerprint">Поділитися відбитком…</string>
+ <string name="menu_share_title">Поділитися цілим ключем…</string>
+ <string name="menu_share_default_fingerprint">з…</string>
+ <string name="menu_share_default">з…</string>
+ <string name="menu_share_qr_code">з QR кодом</string>
+ <string name="menu_share_qr_code_fingerprint">з QR кодом</string>
+ <string name="menu_share_nfc">з NFC</string>
+ <string name="menu_copy_to_clipboard">Копіювати у буфер обміну</string>
+ <string name="menu_sign_key">Підписати ключ</string>
+ <string name="menu_beam_preferences">Налаштування променя</string>
+ <string name="menu_key_edit_cancel">Скасувати</string>
+ <string name="menu_encrypt_to">Зашифрувати…</string>
+ <string name="menu_select_all">Вибрати усе</string>
+ <string name="menu_add_keys">Додати ключі</string>
+ <string name="menu_export_all_keys">Експортувати усі ключі</string>
+ <!--label-->
+ <string name="label_sign">Підпис</string>
+ <string name="label_message">Повідомлення</string>
+ <string name="label_file">Файл</string>
+ <string name="label_no_passphrase">Без парольної фрази</string>
+ <string name="label_passphrase">Парольна фраза</string>
+ <string name="label_passphrase_again">Знову</string>
+ <string name="label_algorithm">Алгоритм</string>
+ <string name="label_ascii_armor">ASCII Броня</string>
+ <string name="label_select_public_keys">Отримувачі</string>
+ <string name="label_delete_after_encryption">Вилучити після шифрування</string>
+ <string name="label_delete_after_decryption">Вилучити після розшифрування</string>
+ <string name="label_share_after_encryption">Поширити після шифрування</string>
+ <string name="label_encryption_algorithm">Алгоритм шифрування</string>
+ <string name="label_hash_algorithm">Хеш алгоритм</string>
+ <string name="label_asymmetric">з публічним ключем</string>
+ <string name="label_symmetric">з парольною фразою</string>
+ <string name="label_passphrase_cache_ttl">Кеш парольної фрази</string>
+ <string name="label_message_compression">Стиснення повідомлення</string>
+ <string name="label_file_compression">Стиснення файлу</string>
+ <string name="label_force_v3_signature">Примусово старі підписи OpenPGPv3</string>
+ <string name="label_key_servers">Сервери ключів</string>
+ <string name="label_key_id">ІД ключа</string>
+ <string name="label_creation">Створення</string>
+ <string name="label_expiry">Закінчення</string>
+ <string name="label_usage">Використання</string>
+ <string name="label_key_size">Розмір ключа</string>
+ <string name="label_main_user_id">ІД основного користувача</string>
+ <string name="label_name">Назва</string>
+ <string name="label_comment">Коментар</string>
+ <string name="label_email">Ел. пошта</string>
+ <string name="label_send_key">Завантажити ключ до вибраного сервера ключів після сертифікації</string>
+ <string name="label_fingerprint">Відбиток</string>
+ <string name="select_keys_button_default">Вибрати</string>
+ <string name="expiry_date_dialog_title">Задати термін дії</string>
+ <plurals name="select_keys_button">
+ <item quantity="one">%d вибраний</item>
+ <item quantity="few">%d вибрані</item>
+ <item quantity="other">%d вибраних</item>
+ </plurals>
+ <string name="user_id_no_name">&lt;без імені&gt;</string>
+ <string name="none">&lt;жоден&gt;</string>
+ <string name="no_key">&lt;без ключа&gt;</string>
+ <string name="no_email">&lt;Немає ел. пошти&gt;</string>
+ <string name="unknown_status"></string>
+ <string name="can_encrypt">можна зашифрувати</string>
+ <string name="can_sign">можна підписати</string>
+ <string name="expired">закінчився</string>
+ <string name="revoked">скасовано</string>
+ <string name="user_id">ІД користувача</string>
+ <plurals name="n_contacts">
+ <item quantity="one">1 контакт</item>
+ <item quantity="few">%d контакти</item>
+ <item quantity="other">%d контактів</item>
+ </plurals>
+ <plurals name="n_key_servers">
+ <item quantity="one">%d сервер ключів</item>
+ <item quantity="few">%d сервери ключів</item>
+ <item quantity="other">%d серверів ключів</item>
+ </plurals>
+ <string name="fingerprint">Відбиток:</string>
+ <string name="secret_key">Секретний ключ:</string>
+ <!--choice-->
+ <string name="choice_none">Жоден</string>
+ <string name="choice_15secs">15 секунд</string>
+ <string name="choice_1min">1 хв</string>
+ <string name="choice_3mins">3 хв</string>
+ <string name="choice_5mins">5 хв</string>
+ <string name="choice_10mins">10 хв</string>
+ <string name="choice_20mins">20 хв</string>
+ <string name="choice_40mins">40 хв</string>
+ <string name="choice_1hour">1 година</string>
+ <string name="choice_2hours">2 години</string>
+ <string name="choice_4hours">4 години</string>
+ <string name="choice_8hours">8 годин</string>
+ <string name="choice_forever">назавжди</string>
+ <string name="dsa">DSA</string>
+ <string name="elgamal">ElGamal</string>
+ <string name="rsa">RSA</string>
+ <string name="filemanager_title_open">Відкрити…</string>
+ <string name="warning">Попередження</string>
+ <string name="error">Помилка</string>
+ <string name="error_message">Помилка: %s</string>
+ <!--key flags-->
+ <string name="flag_certify">Сертифікувати</string>
+ <string name="flag_sign">Підписати</string>
+ <string name="flag_encrypt">Зашифрувати</string>
+ <string name="flag_authenticate">Перевірити справжність</string>
+ <!--sentences-->
+ <string name="wrong_passphrase">Невірна парольна фраза.</string>
+ <string name="using_clipboard_content">Використання вмісту буфера обміну.</string>
+ <string name="set_a_passphrase">Спершу задайте парольну фразу.</string>
+ <string name="no_filemanager_installed">Нема встановленого сумісного менеджера файлів.</string>
+ <string name="passphrases_do_not_match">Парольні фрази не збігаються.</string>
+ <string name="passphrase_must_not_be_empty">Будь ласка, введіть парольну фразу.</string>
+ <string name="passphrase_for_symmetric_encryption">Симетричне шифрування.</string>
+ <string name="passphrase_for">Введіть парольну фразу для \'%s\'</string>
+ <string name="file_delete_confirmation">Ви справді хочете вилучити\n%s?</string>
+ <string name="file_delete_successful">Успішно вилучено.</string>
+ <string name="no_file_selected">Виберіть спершу файл.</string>
+ <string name="decryption_successful">Успішно розшифровано та/або перевірено.</string>
+ <string name="encryption_successful">Успішно підписано та/або перевірено.</string>
+ <string name="encryption_to_clipboard_successful">Успішно підписано та/або зашифровано до буфера обміну.</string>
+ <string name="enter_passphrase_twice">Введіть двічі парольну фразу.</string>
+ <string name="select_encryption_key">Виберіть принаймні один ключ шифрування.</string>
+ <string name="select_encryption_or_signature_key">Виберіть принаймні один ключ шифрування або ключ підпису.</string>
+ <string name="specify_file_to_encrypt_to">Будь ласка, виберіть файл для шифрування.\nУВАГА! Якщо файл існує, то він буде переписаний.</string>
+ <string name="specify_file_to_decrypt_to">Будь ласка, виберіть файл для розшифрування.\nУВАГА! Якщо файл існує, то він буде переписаний.</string>
+ <string name="specify_file_to_export_to">Будь ласка, виберіть файл для експорту.\nУВАГА! Якщо файл існує, то він буде переписаний.</string>
+ <string name="specify_file_to_export_secret_keys_to">Будь ласка, виберіть файл для експорту.\nУВАГА: Ви експортуєте СЕКРЕТНИЙ ключ.\nУВАГА: Якщо файл існує, то він буде переписаний.</string>
+ <string name="key_deletion_confirmation">Ви справді хочете вилучити ключ \'%s\'?\nВи не зможете це відмінити!</string>
+ <string name="key_deletion_confirmation_multi">Ви справді хочете вилучити усі вибрані ключі?\nВи не зможете це відмінити!</string>
+ <string name="secret_key_deletion_confirmation">Ви справді хочете вилучити секретний ключ \'%s\'?\nВи не зможете це відмінити!</string>
+ <string name="ask_save_changed_key">Ви внесли зміни до в\'язки ключів, ви б хотіли. Волієте їх зберегти?</string>
+ <string name="ask_empty_id_ok">Ви вже додали порожній ідентифікатор користувача. Справді хочете продовжити?</string>
+ <string name="public_key_deletetion_confirmation">Справді волієте вилучити ВІДКРИТИЙ ключ \'%s\'?\nВи е зможете відмінити цю дію!</string>
+ <string name="secret_key_delete_text">Видалити секретні ключі?</string>
+ <string name="also_export_secret_keys">Також експортувати секретні ключі?</string>
+ <plurals name="keys_added_and_updated_1">
+ <item quantity="one">Успішно додано %d ключ</item>
+ <item quantity="few">Успішно додано %d ключі</item>
+ <item quantity="other">Успішно додано %d ключів</item>
+ </plurals>
+ <plurals name="keys_added_and_updated_2">
+ <item quantity="one">і оновлено %d ключ.</item>
+ <item quantity="few">і оновлено %d ключі.</item>
+ <item quantity="other">і оновлено %d ключів.</item>
+ </plurals>
+ <plurals name="keys_added">
+ <item quantity="one">Успішно додано %d ключ.</item>
+ <item quantity="few">Успішно додано %d ключі.</item>
+ <item quantity="other">Успішно додано %d ключів.</item>
+ </plurals>
+ <plurals name="keys_updated">
+ <item quantity="one">Успішно оновлено %d ключ.</item>
+ <item quantity="few">Успішно оновлено %d ключі.</item>
+ <item quantity="other">Успішно оновлено %d ключів.</item>
+ </plurals>
+ <string name="no_keys_added_or_updated">Жодного ключа не додано та не оновлено.</string>
+ <string name="key_exported">Успішно експортовано 1 ключ.</string>
+ <string name="keys_exported">Успішно експортовано %d ключів.</string>
+ <string name="no_keys_exported">Жодного ключа не експортовано.</string>
+ <string name="key_creation_el_gamal_info">Примітка: тільки підключі підтримують ElGamal, а для ElGamal буде використаний найближчий розмір ключа з 1536, 2048, 3072, 4096, або 8192.</string>
+ <string name="key_creation_weak_rsa_info">Примітка: генерація ключа RSA з довжиною 1024 біти і менше вважається небезпечною і вона вимкнена для генерації нових ключів.</string>
+ <string name="key_not_found">Не можливо знайти ключ %08X.</string>
+ <plurals name="keys_found">
+ <item quantity="one">Знайдено %d ключ.</item>
+ <item quantity="few">Знайдено %d ключі.</item>
+ <item quantity="other">Знайдено %d ключів.</item>
+ </plurals>
+ <string name="unknown_signature">Невідомий підпис, натисніть кнопку для пошуку втраченого ключа.</string>
+ <plurals name="bad_keys_encountered">
+ <item quantity="one">%d поганий секретний ключ проігнорований. Можливо ви експортували з параметром\n --export-secret-subkeys\nЗробіть ваш експорт з \n --export-secret-keys\nнатомість.</item>
+ <item quantity="few">%d погані секретні ключі проігноровані. Можливо ви експортували з параметром\n --export-secret-subkeys\nЗробіть ваш експорт з \n --export-secret-keys\nнатомість.</item>
+ <item quantity="other">%d поганих секретних ключів проігноровано. Можливо ви експортували з параметром\n --export-secret-subkeys\nЗробіть ваш експорт з \n --export-secret-keys\nнатомість.</item>
+ </plurals>
+ <string name="key_send_success">Успішно завантажено ключ на сервер</string>
+ <string name="key_sign_success">Успішно підписаний ключ</string>
+ <string name="list_empty">Цей список - порожній!</string>
+ <string name="nfc_successfull">Успішно відправлений ключ з NFC променем!</string>
+ <string name="key_copied_to_clipboard">Ключ вже скопійовано у буфер обміну!</string>
+ <string name="key_has_already_been_signed">Ключ уже був підписаний раніше!</string>
+ <string name="select_key_to_sign">Будь ласка, виберіть ключ, використаний для підписування!</string>
+ <string name="key_too_big_for_sharing">Ключ надто великий для цього способу поширення!</string>
+ <!--errors
+ no punctuation, all lowercase,
+ they will be put after "error_message", e.g. "Error: file not found"-->
+ <string name="error_file_delete_failed">вилучення \'%s\' провалилося</string>
+ <string name="error_file_not_found">файл не знайдено</string>
+ <string name="error_no_secret_key_found">відповідного секретного ключа не знайдено</string>
+ <string name="error_no_known_encryption_found">не знайдено відомого виду шифрування</string>
+ <string name="error_external_storage_not_ready">зовнішній носій не готовий</string>
+ <string name="error_invalid_email">невірна електронна пошта \'%s\'</string>
+ <string name="error_key_size_minimum512bit">ключ має мати хоча б 512 біт</string>
+ <string name="error_master_key_must_not_be_el_gamal">основний ключ не може бути ключем ElGamal</string>
+ <string name="error_unknown_algorithm_choice">вибір невідомого алгоритму</string>
+ <string name="error_user_id_needs_a_name">вам потрібно вказати назву</string>
+ <string name="error_user_id_no_email">жодного листа не знайдено</string>
+ <string name="error_user_id_needs_an_email_address">вам потрібно вказати електронну адресу</string>
+ <string name="error_key_needs_a_user_id">потрібний хоча б один ІД користувача</string>
+ <string name="error_main_user_id_must_not_be_empty">ІД основного користувача не має бути порожнім</string>
+ <string name="error_key_needs_master_key">потрібний хоча б один основний ключ</string>
+ <string name="error_no_encryption_keys_or_passphrase">не вказано ключі шифрування або парольну фразу</string>
+ <string name="error_signature_failed">підпис не вдався</string>
+ <string name="error_no_signature_passphrase">не подано парольної фрази</string>
+ <string name="error_no_signature_key">не подано ключ підпису</string>
+ <string name="error_invalid_data">недійсні дані шифрування</string>
+ <string name="error_corrupt_data">пошкодити дані</string>
+ <string name="error_integrity_check_failed">Невдала перевірка цілісності! Дані вже змінено!</string>
+ <string name="error_no_symmetric_encryption_packet">не знайдено пакунок з симетричним шифруванням</string>
+ <string name="error_wrong_passphrase">помилкова парольна фраза</string>
+ <string name="error_saving_keys">помилка збереження деяких ключів</string>
+ <string name="error_could_not_extract_private_key">не можна витягти секретний ключ</string>
+ <string name="error_only_files_are_supported">Прямі двійкові дані без справжнього файлу у файловій системі не підтримуються. Підтримується лише ACTION_ENCRYPT_STREAM_AND_RETURN.</string>
+ <string name="error_jelly_bean_needed">Вам потрібний Android 4.1 Jelly Bean для використання функції Androids NFC промінь!</string>
+ <string name="error_nfc_needed">NFC недоступний на вашому пристрої!</string>
+ <string name="error_nothing_import">Нема що імпортувати!</string>
+ <string name="error_expiry_must_come_after_creation">дата завершення дії має йти після дати створення</string>
+ <string name="error_save_first">спершу збережіть в\'язку ключів</string>
+ <string name="error_can_not_delete_contact">ви не можете вилучити цей контакт, тому що він ваш власний.</string>
+ <string name="error_can_not_delete_contacts">ви не можете вилучити наступні контакти, тому що вони - ваші власні:\n%s</string>
+ <string name="error_keyserver_insufficient_query">Запит обмеженого сервера</string>
+ <string name="error_keyserver_query">Збій сервера ключа запиту</string>
+ <string name="error_keyserver_too_many_responses">Забагато відповідей</string>
+ <string name="error_import_file_no_content">Файл не має вмісту</string>
+ <string name="error_generic_report_bug">Трапилася загальна помилка, будь ласка, створіть новий звіт про помилку для OpenKeychain.</string>
+ <plurals name="error_can_not_delete_info">
+ <item quantity="one">Будь ласка, вилучіть його з екрану „Мої ключі“!</item>
+ <item quantity="few">Будь ласка, вилучіть їх з екрану „Мої ключі“!</item>
+ <item quantity="other">Будь ласка, вилучіть їх з екрану „Мої ключі“!</item>
+ </plurals>
+ <plurals name="error_import_non_pgp_part">
+ <item quantity="one">частина завантаженого файлу є вірним об\'єктом OpenPGP, але не ключем OpenPGP</item>
+ <item quantity="few">частини завантаженого файлу є вірним об\'єктом OpenPGP, але не ключем OpenPGP</item>
+ <item quantity="other">частин завантаженого файлу є вірним об\'єктом OpenPGP, але не ключем OpenPGP</item>
+ </plurals>
+ <string name="error_change_something_first">Вам потрібно внести зміни до в\'язки ключів перед тим, як зможете їх зберегти.</string>
+ <!--progress dialogs, usually ending in '…'-->
+ <string name="progress_done">Готово.</string>
+ <string name="progress_cancel">Скасувати</string>
+ <string name="progress_saving">збереження…</string>
+ <string name="progress_importing">імпортується…</string>
+ <string name="progress_exporting">експортується…</string>
+ <string name="progress_building_key">будується ключ…</string>
+ <string name="progress_preparing_master_key">підготовка основного ключа…</string>
+ <string name="progress_certifying_master_key">сертифікація основного ключа…</string>
+ <string name="progress_building_master_key">побудова основного кільця…</string>
+ <string name="progress_adding_sub_keys">додавання підключів…</string>
+ <string name="progress_saving_key_ring">зберігається ключ…</string>
+ <plurals name="progress_exporting_key">
+ <item quantity="one">експортується ключ…</item>
+ <item quantity="few">експортуються ключі…</item>
+ <item quantity="other">експортуються ключі…</item>
+ </plurals>
+ <plurals name="progress_generating">
+ <item quantity="one">генерується ключ, це може тривати до 3 хвилини</item>
+ <item quantity="few">генеруються ключі, це може тривати до 3 хвилини</item>
+ <item quantity="other">генеруються ключі, це може тривати до 3 хвилини</item>
+ </plurals>
+ <string name="progress_extracting_signature_key">видобування ключа підпису…</string>
+ <string name="progress_extracting_key">видобувається ключа…</string>
+ <string name="progress_preparing_streams">підготовка потоків…</string>
+ <string name="progress_encrypting">шифруються дані…</string>
+ <string name="progress_decrypting">розшифровуються дані…</string>
+ <string name="progress_preparing_signature">підготовка підпису…</string>
+ <string name="progress_generating_signature">генерується підпис…</string>
+ <string name="progress_processing_signature">обробляється підпис…</string>
+ <string name="progress_verifying_signature">перевірка підпису…</string>
+ <string name="progress_signing">підписання…</string>
+ <string name="progress_reading_data">читання даних…</string>
+ <string name="progress_finding_key">пошук ключа…</string>
+ <string name="progress_decompressing_data">розпакування даних…</string>
+ <string name="progress_verifying_integrity">перевірка цілісності…</string>
+ <string name="progress_deleting_securely">вилучення безпечно \'%s\'…</string>
+ <string name="progress_querying">запит…</string>
+ <!--action strings-->
+ <string name="hint_public_keys">Пошук публічних ключів</string>
+ <string name="hint_secret_keys">Пошук секретних ключів</string>
+ <string name="action_share_key_with">Поділитися ключем з…</string>
+ <!--key bit length selections-->
+ <string name="key_size_512">512</string>
+ <string name="key_size_1024">1024</string>
+ <string name="key_size_2048">2048</string>
+ <string name="key_size_4096">4096</string>
+ <!--compression-->
+ <string name="compression_fast">швидке</string>
+ <string name="compression_very_slow">дуже повільне</string>
+ <!--Help-->
+ <string name="help_tab_start">Початок</string>
+ <string name="help_tab_faq">ЧАП</string>
+ <string name="help_tab_nfc_beam">NFC промінь</string>
+ <string name="help_tab_changelog">Журнал змін</string>
+ <string name="help_tab_about">Про</string>
+ <string name="help_about_version">Версія:</string>
+ <!--Import-->
+ <string name="import_import">Імпортувати вибрані ключі</string>
+ <string name="import_sign_and_upload">Імпорт, підпис та завантаження вибраних ключів</string>
+ <string name="import_from_clipboard">Імпорт з буфера обміну</string>
+ <plurals name="import_qr_code_missing">
+ <item quantity="one">Пропущений QR код із ID %s</item>
+ <item quantity="few">Пропущені QR коди із ID %s</item>
+ <item quantity="other">Пропущені QR коди із ID %s</item>
+ </plurals>
+ <string name="import_qr_code_start_with_one">Будь ласка, почніть з QR коду із ІД 1</string>
+ <string name="import_qr_code_wrong">Невірний штрих-код! Спробуйте знову!</string>
+ <string name="import_qr_code_finished">Сканування штрих-коду завершено!</string>
+ <string name="import_qr_code_too_short_fingerprint">Відбиток надто короткий (&lt; 16 символів)</string>
+ <string name="import_qr_scan_button">Сканувати QR код з \'Barcode Scanner\'</string>
+ <string name="import_nfc_text">Розблокуйте пристрій, щоб отримати ключ через NFC.</string>
+ <string name="import_nfc_help_button">Довідка</string>
+ <string name="import_clipboard_button">Отримати ключ з буфера обміну</string>
+ <!--Intent labels-->
+ <string name="intent_decrypt_file">Розшифрувати файл з OpenKeychain</string>
+ <string name="intent_import_key">Імпортувати ключ з OpenKeychain</string>
+ <string name="intent_send_encrypt">Зашифрувати з OpenKeychain</string>
+ <string name="intent_send_decrypt">Розшифрувати з OpenKeychain</string>
+ <!--Remote API-->
+ <string name="api_no_apps">Нема зареєстрованих програм!\n\nСтороні програми можуть вимагати доступ до OpenPGP Keychain. Після надання доступу вони будуть наведені тут.</string>
+ <string name="api_settings_show_info">Показати додаткову інформацію</string>
+ <string name="api_settings_hide_info">Приховати додаткову інформацію</string>
+ <string name="api_settings_show_advanced">Показати додаткові налаштування</string>
+ <string name="api_settings_hide_advanced">Приховати додаткові налаштування</string>
+ <string name="api_settings_no_key">Не вибрано ключа</string>
+ <string name="api_settings_select_key">Вибрати ключ</string>
+ <string name="api_settings_create_key">Створити новий ключ для цього профілю</string>
+ <string name="api_settings_save">Зберегти</string>
+ <string name="api_settings_cancel">Скасувати</string>
+ <string name="api_settings_revoke">Відкликати доступ</string>
+ <string name="api_settings_delete_account">Видалити профіль</string>
+ <string name="api_settings_package_name">Назва пакунку</string>
+ <string name="api_settings_package_signature">SHA-256 підписку пакунку</string>
+ <string name="api_settings_accounts">Облікові записи</string>
+ <string name="api_settings_accounts_empty">Немає облікового запису приєднаного до цієї програми.</string>
+ <string name="api_create_account_text">Ця програма вимагає створення нового профілю. Будь ласка, виберіть наявний приватний ключ або створіть інший.\nПрограми обмежені використання ключів, які ви тут оберете!</string>
+ <string name="api_register_text">Показана програма запитує доступ до OpenPGP Keychain.\nДозволити доступ?\n\nУВАГА: якщо ви не знаєте, чому цей екран появився, не дозволяйте доступ! Ви можете відкликати доступ пізніше, використовуючи екран \'Зареєстровані програми\'.</string>
+ <string name="api_register_allow">Дозволити доступ</string>
+ <string name="api_register_disallow">Не дозволити доступ</string>
+ <string name="api_register_error_select_key">Будь ласка, виберіть ключ!</string>
+ <string name="api_select_pub_keys_missing_text">Не знайдено публічних ключів для цих ІД користувачів:</string>
+ <string name="api_select_pub_keys_dublicates_text">Більше ніж один публічний ключ існує для цих ІД користувачів:</string>
+ <string name="api_select_pub_keys_text">Будь ласка, перевірте список одержувачів!</string>
+ <string name="api_error_wrong_signature">Перевірка підпису пакету не вдалася! Може ви встановили програму з іншого джерела? Якщо ви впевнені, що це не атака, то відкличте реєстрацію програми у OpenKeychain та знову зареєструйте її.</string>
+ <!--Share-->
+ <string name="share_qr_code_dialog_title">Відправити як штрих-код</string>
+ <string name="share_qr_code_dialog_start">Пройдіть через усі штрих-коди за допомогою \"Далі\", а також проскануйте їх по одному.</string>
+ <string name="share_qr_code_dialog_fingerprint_text">Відбиток:</string>
+ <string name="share_qr_code_dialog_progress">Штрих-код з ID %1$d із %2$d</string>
+ <string name="share_nfc_dialog">Поділитися з NFC</string>
+ <!--Key list-->
+ <plurals name="key_list_selected_keys">
+ <item quantity="one">1 ключ вибрано.</item>
+ <item quantity="few">%d ключі вибрано.</item>
+ <item quantity="other">%d ключів вибрано.</item>
+ </plurals>
+ <string name="key_list_empty_text1">Ще немає доступних ключів…</string>
+ <string name="key_list_empty_text2">Ви можете розпочати за</string>
+ <string name="key_list_empty_text3">чи</string>
+ <string name="key_list_empty_button_create">створюється ваш власний ключ</string>
+ <string name="key_list_empty_button_import">імпортуюся ключі.</string>
+ <!--Key view-->
+ <string name="key_view_action_edit">Редагувати цей ключ</string>
+ <string name="key_view_action_encrypt">Зашифрувати у цей контакт</string>
+ <string name="key_view_action_certify">Сертифікувати ключ цього контакту</string>
+ <string name="key_view_tab_main">Інформація</string>
+ <string name="key_view_tab_certs">Сертифікати</string>
+ <!--Navigation Drawer-->
+ <string name="nav_contacts">Ключі</string>
+ <string name="nav_encrypt">Підписати і зашифрувати</string>
+ <string name="nav_decrypt">Розшифрувати і Перевірити</string>
+ <string name="nav_import">Імпортувати ключі</string>
+ <string name="nav_secret_keys">Мої ключі</string>
+ <string name="nav_apps">Зареєстровані програми</string>
+ <string name="drawer_open">Відкрити панель навігації</string>
+ <string name="drawer_close">Закрити панель навігації</string>
+ <string name="edit">Редагувати</string>
+ <string name="my_keys">Мої ключі</string>
+ <string name="label_secret_key">Секретний ключ</string>
+ <string name="secret_key_yes">доступний</string>
+ <string name="secret_key_no">недоступний</string>
+ <!--hints-->
+ <string name="encrypt_content_edit_text_hint">Напишіть повідомлення для шифрування та/або підпису…</string>
+ <string name="decrypt_content_edit_text_hint">Уведіть зашифрований текст тут для його розшифрування та/або перевірки…</string>
+ <!--unsorted-->
+ <string name="section_uids_to_sign">ІД користувача для реєстрації</string>
+ <string name="progress_re_adding_certs">Перезастосування сертифікатів</string>
+</resources>
diff --git a/OpenKeychain/src/main/res/values-v14/styles.xml b/OpenKeychain/src/main/res/values-v14/styles.xml
new file mode 100644
index 000000000..4f8c45117
--- /dev/null
+++ b/OpenKeychain/src/main/res/values-v14/styles.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Used in Android >= 4 -->
+
+ <style name="KeychainTheme" parent="@style/Theme.AppCompat.Light">
+ </style>
+
+ <style name="SectionHeader">
+ <item name="android:drawableBottom">@drawable/section_header</item>
+ <item name="android:drawablePadding">4dp</item>
+ <item name="android:layout_marginTop">8dp</item>
+ <item name="android:paddingLeft">4dp</item>
+ <item name="android:textAllCaps">true</item>
+ <item name="android:textColor">@color/emphasis</item>
+ <item name="android:textSize">14sp</item>
+ </style>
+
+</resources> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/values-zh-rTW/strings.xml b/OpenKeychain/src/main/res/values-zh-rTW/strings.xml
new file mode 100644
index 000000000..fc802092c
--- /dev/null
+++ b/OpenKeychain/src/main/res/values-zh-rTW/strings.xml
@@ -0,0 +1,29 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<resources>
+ <!--title-->
+ <!--section-->
+ <!--button-->
+ <!--menu-->
+ <!--label-->
+ <string name="unknown_status"></string>
+ <!--choice-->
+ <!--key flags-->
+ <!--sentences-->
+ <!--errors
+ no punctuation, all lowercase,
+ they will be put after "error_message", e.g. "Error: file not found"-->
+ <!--progress dialogs, usually ending in '…'-->
+ <!--action strings-->
+ <!--key bit length selections-->
+ <!--compression-->
+ <!--Help-->
+ <!--Import-->
+ <!--Intent labels-->
+ <!--Remote API-->
+ <!--Share-->
+ <!--Key list-->
+ <!--Key view-->
+ <!--Navigation Drawer-->
+ <!--hints-->
+ <!--unsorted-->
+</resources>
diff --git a/OpenKeychain/src/main/res/values-zh/strings.xml b/OpenKeychain/src/main/res/values-zh/strings.xml
new file mode 100644
index 000000000..fa21e211b
--- /dev/null
+++ b/OpenKeychain/src/main/res/values-zh/strings.xml
@@ -0,0 +1,181 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<resources>
+ <!--title-->
+ <string name="title_manage_public_keys">聯絡人</string>
+ <string name="title_select_recipients">选择公钥</string>
+ <string name="title_select_secret_key">选择私钥</string>
+ <string name="title_encrypt">加密</string>
+ <string name="title_decrypt">解密</string>
+ <string name="title_authentication">密码短语</string>
+ <string name="title_create_key">创建密钥</string>
+ <string name="title_edit_key">编辑密钥</string>
+ <string name="title_preferences">参数</string>
+ <string name="title_api_registered_apps">已注册应用</string>
+ <string name="title_key_server_preference">密钥服务器偏好</string>
+ <string name="title_set_passphrase">设置密码短语</string>
+ <string name="title_send_email">发送邮件</string>
+ <string name="title_encrypt_to_file">加密至文件</string>
+ <string name="title_decrypt_to_file">解密至文件</string>
+ <string name="title_import_keys">导入密钥</string>
+ <string name="title_export_key">导出密钥</string>
+ <string name="title_export_keys">导出密钥</string>
+ <string name="title_key_not_found">无法找到密钥</string>
+ <string name="title_key_server_query">查询密钥服务器</string>
+ <string name="title_send_key">上传到密钥服务器</string>
+ <string name="title_unknown_signature_key">未知签名密钥</string>
+ <string name="title_help">帮助</string>
+ <!--section-->
+ <string name="section_user_ids">用户ID</string>
+ <string name="section_keys">密钥</string>
+ <string name="section_general">常规</string>
+ <string name="section_defaults">缺省</string>
+ <string name="section_advanced">高级</string>
+ <string name="section_master_key">主密钥</string>
+ <string name="section_key_server">密钥服务器</string>
+ <string name="section_decrypt_verify">解密并验证</string>
+ <!--button-->
+ <string name="btn_sign">签名</string>
+ <string name="btn_decrypt">解密</string>
+ <string name="btn_decrypt_verify">解密并验证</string>
+ <string name="btn_select_encrypt_keys">选择收件人</string>
+ <string name="btn_encrypt_file">加密文件</string>
+ <string name="btn_save">保存</string>
+ <string name="btn_do_not_save">取消</string>
+ <string name="btn_delete">删除</string>
+ <string name="btn_no_date">无</string>
+ <string name="btn_clipboard">剪贴板</string>
+ <!--menu-->
+ <string name="menu_help">帮助</string>
+ <string name="menu_import">导入</string>
+ <string name="menu_delete_key">删除密钥</string>
+ <string name="menu_create_key">创建密钥</string>
+ <string name="menu_create_key_expert">创建密钥(专家)</string>
+ <string name="menu_search">搜索</string>
+ <string name="menu_copy_to_clipboard">复制到剪贴板</string>
+ <string name="menu_sign_key">签署密钥</string>
+ <string name="menu_key_edit_cancel">取消</string>
+ <string name="menu_encrypt_to">加密到...</string>
+ <!--label-->
+ <string name="label_sign">签署</string>
+ <string name="label_message">讯息</string>
+ <string name="label_file">文件</string>
+ <string name="label_no_passphrase">没有密语</string>
+ <string name="label_passphrase">密语</string>
+ <string name="label_algorithm">算法</string>
+ <string name="label_select_public_keys">收件人</string>
+ <string name="label_delete_after_encryption">加密后删除</string>
+ <string name="label_delete_after_decryption">解密后删除</string>
+ <string name="label_encryption_algorithm">加密算法</string>
+ <string name="label_hash_algorithm">哈希算法</string>
+ <string name="label_passphrase_cache_ttl">密语缓存</string>
+ <string name="label_comment">注解</string>
+ <string name="label_email">电子邮件</string>
+ <string name="label_fingerprint">指纹</string>
+ <string name="unknown_status"></string>
+ <string name="can_encrypt">可以加密</string>
+ <string name="can_sign">可以签署</string>
+ <string name="expired">过期了</string>
+ <!--choice-->
+ <string name="choice_none">没有</string>
+ <string name="choice_15secs">15秒</string>
+ <string name="choice_1min">1分钟</string>
+ <string name="choice_3mins">3分钟</string>
+ <string name="choice_5mins">5分钟</string>
+ <string name="choice_10mins">10分钟</string>
+ <string name="choice_20mins">20分钟</string>
+ <string name="choice_40mins">40分钟</string>
+ <string name="choice_1hour">1小时</string>
+ <string name="choice_2hours">2小时</string>
+ <string name="choice_4hours">4小时</string>
+ <string name="choice_8hours">8小时</string>
+ <string name="choice_forever">永远</string>
+ <string name="filemanager_title_open">打开...</string>
+ <string name="warning">警告</string>
+ <string name="error">错误</string>
+ <!--key flags-->
+ <!--sentences-->
+ <string name="set_a_passphrase">先设置密钥</string>
+ <string name="no_filemanager_installed">安装了不匹配的文件管理器</string>
+ <string name="passphrases_do_not_match">密钥不匹配</string>
+ <string name="passphrase_for_symmetric_encryption">对称加密</string>
+ <string name="file_delete_successful">删除成功</string>
+ <string name="no_file_selected">先选择一个文件</string>
+ <string name="enter_passphrase_twice">输入两次密钥</string>
+ <string name="select_encryption_key">选择至少一个加密密钥</string>
+ <string name="select_encryption_or_signature_key">选择至少一个加密密钥或者签名密钥</string>
+ <string name="key_exported">成功地导出了1个密钥</string>
+ <string name="no_keys_exported">没有密钥被导出</string>
+ <string name="key_send_success">成功地上传了密钥到服务器</string>
+ <string name="list_empty">这个列表是空的!</string>
+ <!--errors
+ no punctuation, all lowercase,
+ they will be put after "error_message", e.g. "Error: file not found"-->
+ <string name="error_file_not_found">没有找到文件</string>
+ <string name="error_external_storage_not_ready">外置存储没有准备好</string>
+ <string name="error_invalid_email">无效的email \'%s\'</string>
+ <string name="error_key_size_minimum512bit">密钥的大小必须至少512位</string>
+ <string name="error_unknown_algorithm_choice">位置的算法选择</string>
+ <string name="error_user_id_needs_a_name">你需要指定一个名字</string>
+ <string name="error_user_id_needs_an_email_address">你需要指定一个电子邮件地址</string>
+ <string name="error_key_needs_a_user_id">需要至少一个用户id</string>
+ <string name="error_main_user_id_must_not_be_empty">主用户id不能是空的</string>
+ <string name="error_key_needs_master_key">需要至少一个主密钥</string>
+ <string name="error_signature_failed">签名失败</string>
+ <string name="error_no_signature_passphrase">没有提供密语</string>
+ <string name="error_no_signature_key">没有提供密钥</string>
+ <string name="error_invalid_data">不是有效的加密数据</string>
+ <string name="error_corrupt_data">损坏的数据</string>
+ <string name="error_wrong_passphrase">错误的密语</string>
+ <!--progress dialogs, usually ending in '…'-->
+ <string name="progress_saving">保存...</string>
+ <string name="progress_importing">导入中...</string>
+ <string name="progress_exporting">导出中...</string>
+ <string name="progress_building_key">建立密钥</string>
+ <string name="progress_preparing_master_key">正在准备主密钥</string>
+ <string name="progress_verifying_signature">正在验证签名...</string>
+ <string name="progress_signing">正在签名...</string>
+ <string name="progress_reading_data">正在读取数据</string>
+ <string name="progress_finding_key">正在查找密钥</string>
+ <string name="progress_querying">正在查询</string>
+ <!--action strings-->
+ <!--key bit length selections-->
+ <string name="key_size_512">512</string>
+ <string name="key_size_1024">1024</string>
+ <string name="key_size_2048">2048</string>
+ <string name="key_size_4096">4096</string>
+ <!--compression-->
+ <string name="compression_fast">快</string>
+ <string name="compression_very_slow">非常慢</string>
+ <!--Help-->
+ <string name="help_tab_start">开始</string>
+ <string name="help_tab_changelog">更新日志</string>
+ <string name="help_tab_about">关于</string>
+ <string name="help_about_version">版本:</string>
+ <!--Import-->
+ <string name="import_from_clipboard">从剪贴板导入</string>
+ <string name="import_qr_code_finished">二维码扫描完成!</string>
+ <string name="import_nfc_help_button">帮助</string>
+ <!--Intent labels-->
+ <!--Remote API-->
+ <string name="api_settings_show_advanced">显示高级设置</string>
+ <string name="api_settings_hide_advanced">隐藏高级设置</string>
+ <string name="api_settings_select_key">选择密钥</string>
+ <string name="api_settings_save">保存</string>
+ <string name="api_settings_cancel">取消</string>
+ <string name="api_settings_revoke">撤销访问</string>
+ <string name="api_register_allow">允许访问</string>
+ <string name="api_register_disallow">不允许访问</string>
+ <string name="api_register_error_select_key">请选择一个密钥</string>
+ <string name="api_select_pub_keys_text">请重审收件人列表</string>
+ <!--Share-->
+ <string name="share_qr_code_dialog_fingerprint_text">指纹:</string>
+ <string name="share_nfc_dialog">使用NFC分享</string>
+ <!--Key list-->
+ <string name="key_list_empty_text3">或者</string>
+ <!--Key view-->
+ <!--Navigation Drawer-->
+ <string name="nav_import">导入密钥</string>
+ <string name="nav_secret_keys">我的密钥</string>
+ <!--hints-->
+ <!--unsorted-->
+</resources>
diff --git a/OpenKeychain/src/main/res/values/arrays.xml b/OpenKeychain/src/main/res/values/arrays.xml
new file mode 100644
index 000000000..c84c2648d
--- /dev/null
+++ b/OpenKeychain/src/main/res/values/arrays.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <string-array name="passphrase_cache_ttl_entries" translatable="false">
+ <item>@string/choice_15secs</item>
+ <item>@string/choice_1min</item>
+ <item>@string/choice_3mins</item>
+ <item>@string/choice_5mins</item>
+ <item>@string/choice_10mins</item>
+ <item>@string/choice_20mins</item>
+ <item>@string/choice_40mins</item>
+ <item>@string/choice_1hour</item>
+ <item>@string/choice_2hours</item>
+ <item>@string/choice_4hours</item>
+ <item>@string/choice_8hours</item>
+ <item>@string/choice_forever</item>
+ </string-array>
+ <string-array name="passphrase_cache_ttl_values" translatable="false">
+ <item>15</item>
+ <item>60</item>
+ <item>180</item>
+ <item>300</item>
+ <item>600</item>
+ <item>1200</item>
+ <item>2400</item>
+ <item>3600</item>
+ <item>7200</item>
+ <item>14400</item>
+ <item>28800</item>
+ <item>-1</item>
+ </string-array>
+ <string-array name="key_size_spinner_values" translatable="false">
+ <item>@string/key_size_512</item>
+ <item>@string/key_size_1024</item>
+ <item>@string/key_size_2048</item>
+ <item>@string/key_size_4096</item>
+ </string-array>
+ <string-array name="import_action_list" translatable="false">
+ <item>@string/menu_import_from_key_server</item>
+ <item>@string/menu_import_from_file</item>
+ <item>@string/menu_import_from_qr_code</item>
+ <item>@string/import_from_clipboard</item>
+ <item>@string/menu_import_from_nfc</item>
+ </string-array>
+
+</resources>
diff --git a/OpenKeychain/src/main/res/values/attr.xml b/OpenKeychain/src/main/res/values/attr.xml
new file mode 100644
index 000000000..86622b3e0
--- /dev/null
+++ b/OpenKeychain/src/main/res/values/attr.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <declare-styleable name="FoldableLinearLayout">
+ <attr name="foldedLabel" format="string" />
+ <attr name="unFoldedLabel" format="string" />
+ <attr name="foldedIcon" format="string" />
+ <attr name="unFoldedIcon" format="string" />
+ </declare-styleable>
+
+</resources> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/values/colors.xml b/OpenKeychain/src/main/res/values/colors.xml
new file mode 100644
index 000000000..d1dc6c1f4
--- /dev/null
+++ b/OpenKeychain/src/main/res/values/colors.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <color name="emphasis">#31b6e7</color>
+ <color name="bg_gray">#cecbce</color>
+
+</resources> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/values/dimens.xml b/OpenKeychain/src/main/res/values/dimens.xml
new file mode 100644
index 000000000..e1a7749f0
--- /dev/null
+++ b/OpenKeychain/src/main/res/values/dimens.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <dimen name="drawer_size">240dp</dimen>
+ <dimen name="drawer_content_padding">0dp</dimen>
+</resources> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/values/ids.xml b/OpenKeychain/src/main/res/values/ids.xml
new file mode 100644
index 000000000..2004c0397
--- /dev/null
+++ b/OpenKeychain/src/main/res/values/ids.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <item name="tag_mki" type="id" />
+ <item name="tag_rank" type="id" />
+ <item name="tag_certifierId" type="id" />
+</resources> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/values/static_strings.xml b/OpenKeychain/src/main/res/values/static_strings.xml
new file mode 100644
index 000000000..faf1e687a
--- /dev/null
+++ b/OpenKeychain/src/main/res/values/static_strings.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <string name="app_name" translatable="false">OpenKeychain</string>
+
+</resources> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml
new file mode 100644
index 000000000..6c6d05103
--- /dev/null
+++ b/OpenKeychain/src/main/res/values/strings.xml
@@ -0,0 +1,511 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <!-- title -->
+ <string name="title_manage_public_keys">Contacts</string>
+ <string name="title_manage_secret_keys">Secret Keys</string>
+ <string name="title_select_recipients">Select Public Key</string>
+ <string name="title_select_secret_key">Select Secret Key</string>
+ <string name="title_encrypt">Encrypt</string>
+ <string name="title_decrypt">Decrypt</string>
+ <string name="title_authentication">Passphrase</string>
+ <string name="title_create_key">Create Key</string>
+ <string name="title_edit_key">Edit Key</string>
+ <string name="title_preferences">Preferences</string>
+ <string name="title_api_registered_apps">Registered Applications</string>
+ <string name="title_key_server_preference">Keyserver Preference</string>
+ <string name="title_change_passphrase">Change Passphrase</string>
+ <string name="title_set_passphrase">Set Passphrase</string>
+ <string name="title_send_email">"Send Mail…"</string>
+ <string name="title_send_file">"Send File…"</string>
+ <string name="title_encrypt_to_file">Encrypt To File</string>
+ <string name="title_decrypt_to_file">Decrypt To File</string>
+ <string name="title_import_keys">Import Keys</string>
+ <string name="title_export_key">Export Key</string>
+ <string name="title_export_keys">Export Keys</string>
+ <string name="title_key_not_found">Key Not Found</string>
+ <string name="title_key_server_query">Query Keyserver</string>
+ <string name="title_send_key">Upload to Keyserver</string>
+ <string name="title_unknown_signature_key">Unknown Signature Key</string>
+ <string name="title_certify_key">Certify Key</string>
+ <string name="title_key_details">Key Details</string>
+ <string name="title_help">Help</string>
+
+ <!-- section -->
+ <string name="section_user_ids">User IDs</string>
+ <string name="section_keys">Keys</string>
+ <string name="section_general">General</string>
+ <string name="section_defaults">Defaults</string>
+ <string name="section_advanced">Advanced</string>
+ <string name="section_master_key">Master Key</string>
+ <string name="section_master_user_id">Master User ID</string>
+ <string name="section_actions">Actions</string>
+ <string name="section_certification_key">Your Key used for certification</string>
+ <string name="section_upload_key">Upload Key</string>
+ <string name="section_key_server">Keyserver</string>
+ <string name="section_encrypt_and_or_sign">Encrypt and/or Sign</string>
+ <string name="section_decrypt_verify">Decrypt and Verify</string>
+
+ <!-- button -->
+ <string name="btn_sign">Sign</string>
+ <string name="btn_certify">Certify</string>
+ <string name="btn_decrypt">Decrypt</string>
+ <string name="btn_decrypt_verify">Decrypt and Verify</string>
+ <string name="btn_decrypt_verify_clipboard">From Clipboard</string>
+ <string name="btn_select_encrypt_keys">Select Recipients</string>
+ <string name="btn_encrypt_file">Encrypt File</string>
+ <string name="btn_save">Save</string>
+ <string name="btn_do_not_save">Cancel</string>
+ <string name="btn_delete">Delete</string>
+ <string name="btn_no_date">None</string>
+ <string name="btn_okay">Okay</string>
+ <string name="btn_change_passphrase">Change New Passphrase</string>
+ <string name="btn_set_passphrase">Set New Passphrase</string>
+ <string name="btn_search">Search</string>
+ <string name="btn_export_to_server">Upload To Keyserver</string>
+ <string name="btn_next">Next</string>
+ <string name="btn_back">Back</string>
+ <string name="btn_clipboard">Clipboard</string>
+ <string name="btn_share">Share with…</string>
+ <string name="btn_lookup_key">Lookup key</string>
+ <string name="btn_encryption_advanced_settings_show">Show advanced settings</string>
+ <string name="btn_encryption_advanced_settings_hide">Hide advanced settings</string>
+
+ <!-- menu -->
+ <string name="menu_preferences">Settings</string>
+ <string name="menu_help">Help</string>
+ <string name="menu_import_from_file">Import from file</string>
+ <string name="menu_import_from_qr_code">Import from QR Code</string>
+ <string name="menu_import">Import</string>
+ <string name="menu_import_from_nfc">Import from NFC</string>
+ <string name="menu_export_key">Export to file</string>
+ <string name="menu_delete_key">Delete key</string>
+ <string name="menu_create_key">Create key</string>
+ <string name="menu_create_key_expert">Create key (expert)</string>
+ <string name="menu_search">Search</string>
+ <string name="menu_import_from_key_server">Keyserver</string>
+ <string name="menu_key_server">Keyserver…</string>
+ <string name="menu_update_key">Update from keyserver</string>
+ <string name="menu_export_key_to_server">Upload to key server</string>
+ <string name="menu_share">Share…</string>
+ <string name="menu_share_title_fingerprint">Share fingerprint…</string>
+ <string name="menu_share_title">Share whole key…</string>
+ <string name="menu_share_default_fingerprint">with…</string>
+ <string name="menu_share_default">with…</string>
+ <string name="menu_share_qr_code">with QR Code</string>
+ <string name="menu_share_qr_code_fingerprint">with QR Code</string>
+ <string name="menu_share_nfc">with NFC</string>
+ <string name="menu_copy_to_clipboard">Copy to clipboard</string>
+ <string name="menu_sign_key">Sign key</string>
+ <string name="menu_beam_preferences">Beam settings</string>
+ <string name="menu_key_edit_cancel">Cancel</string>
+ <string name="menu_encrypt_to">Encrypt to…</string>
+ <string name="menu_select_all">Select all</string>
+ <string name="menu_add_keys">Add keys</string>
+ <string name="menu_export_all_keys">Export all keys</string>
+
+ <!-- label -->
+ <string name="label_sign">Sign</string>
+ <string name="label_message">Message</string>
+ <string name="label_file">File</string>
+ <string name="label_no_passphrase">No Passphrase</string>
+ <string name="label_passphrase">Passphrase</string>
+ <string name="label_passphrase_again">Again</string>
+ <string name="label_algorithm">Algorithm</string>
+ <string name="label_ascii_armor">ASCII Armor</string>
+ <string name="label_select_public_keys">Recipients</string>
+ <string name="label_delete_after_encryption">Delete After Encryption</string>
+ <string name="label_delete_after_decryption">Delete After Decryption</string>
+ <string name="label_share_after_encryption">Share After Encryption</string>
+ <string name="label_encryption_algorithm">Encryption Algorithm</string>
+ <string name="label_hash_algorithm">Hash Algorithm</string>
+ <string name="label_asymmetric">with Public Key</string>
+ <string name="label_symmetric">with Passphrase</string>
+ <string name="label_passphrase_cache_ttl">Passphrase Cache</string>
+ <string name="label_message_compression">Message Compression</string>
+ <string name="label_file_compression">File Compression</string>
+ <string name="label_force_v3_signature">Force old OpenPGPv3 Signatures</string>
+ <string name="label_key_servers">Keyservers</string>
+ <string name="label_key_id">Key ID</string>
+ <string name="label_creation">Creation</string>
+ <string name="label_expiry">Expiry</string>
+ <string name="label_usage">Usage</string>
+ <string name="label_key_size">Key Size</string>
+ <string name="label_main_user_id">Main User ID</string>
+ <string name="label_name">Name</string>
+ <string name="label_comment">Comment</string>
+ <string name="label_email">Email</string>
+ <string name="label_send_key">Upload key to selected keyserver after certification</string>
+ <string name="label_fingerprint">Fingerprint</string>
+ <string name="select_keys_button_default">Select</string>
+ <string name="expiry_date_dialog_title">Set expiry date</string>
+
+ <plurals name="select_keys_button">
+ <item quantity="one">%d selected</item>
+ <item quantity="other">%d selected</item>
+ </plurals>
+
+ <string name="user_id_no_name">&lt;no name&gt;</string>
+ <string name="none">&lt;none&gt;</string>
+ <string name="no_key">&lt;no key&gt;</string>
+ <string name="no_email">&lt;No Email&gt;</string>
+ <string name="unknown_status"></string>
+ <string name="can_encrypt">can encrypt</string>
+ <string name="can_sign">can sign</string>
+ <string name="expired">expired</string>
+ <string name="revoked">revoked</string>
+ <string name="user_id">User ID</string>
+
+ <plurals name="n_contacts">
+ <item quantity="one">1 contact</item>
+ <item quantity="other">%d contacts</item>
+ </plurals>
+
+ <plurals name="n_key_servers">
+ <item quantity="one">%d keyserver</item>
+ <item quantity="other">%d keyservers</item>
+ </plurals>
+
+ <string name="fingerprint">Fingerprint:</string>
+ <string name="secret_key">Secret Key:</string>
+
+ <!-- choice -->
+ <string name="choice_none">None</string>
+ <string name="choice_15secs">15 secs</string>
+ <string name="choice_1min">1 min</string>
+ <string name="choice_3mins">3 mins</string>
+ <string name="choice_5mins">5 mins</string>
+ <string name="choice_10mins">10 mins</string>
+ <string name="choice_20mins">20 mins</string>
+ <string name="choice_40mins">40 mins</string>
+ <string name="choice_1hour">1 hour</string>
+ <string name="choice_2hours">2 hours</string>
+ <string name="choice_4hours">4 hours</string>
+ <string name="choice_8hours">8 hours</string>
+ <string name="choice_forever">forever</string>
+ <string name="dsa">DSA</string>
+ <string name="elgamal">ElGamal</string>
+ <string name="rsa">RSA</string>
+ <string name="filemanager_title_open">Open…</string>
+ <string name="warning">Warning</string>
+ <string name="error">Error</string>
+ <string name="error_message">Error: %s</string>
+
+ <!-- key flags -->
+ <string name="flag_certify">Certify</string>
+ <string name="flag_sign">Sign</string>
+ <string name="flag_encrypt">Encrypt</string>
+ <string name="flag_authenticate">Authenticate</string>
+
+ <!-- sentences -->
+ <string name="wrong_passphrase">Wrong passphrase.</string>
+ <string name="using_clipboard_content">Using clipboard content.</string>
+ <string name="set_a_passphrase">Set a passphrase first.</string>
+ <string name="no_filemanager_installed">No compatible file manager installed.</string>
+ <string name="passphrases_do_not_match">The passphrases didn\'t match.</string>
+ <string name="passphrase_must_not_be_empty">Please enter a passphrase.</string>
+ <string name="passphrase_for_symmetric_encryption">Symmetric encryption.</string>
+ <string name="passphrase_for">Enter passphrase for \'%s\'</string>
+ <string name="file_delete_confirmation">Are you sure you want to delete\n%s?</string>
+ <string name="file_delete_successful">Successfully deleted.</string>
+ <string name="no_file_selected">Select a file first.</string>
+ <string name="decryption_successful">Successfully decrypted and/or verified.</string>
+ <string name="encryption_successful">Successfully signed and/or encrypted.</string>
+ <string name="encryption_to_clipboard_successful">Successfully signed and/or encrypted to clipboard.</string>
+ <string name="enter_passphrase_twice">Enter the passphrase twice.</string>
+ <string name="select_encryption_key">Select at least one encryption key.</string>
+ <string name="select_encryption_or_signature_key">Select at least one encryption key or a signature key.</string>
+ <string name="specify_file_to_encrypt_to">Please specify which file to encrypt to.\nWARNING: File will be overwritten if it exists.</string>
+ <string name="specify_file_to_decrypt_to">Please specify which file to decrypt to.\nWARNING: File will be overwritten if it exists.</string>
+ <string name="specify_file_to_export_to">Please specify which file to export to.\nWARNING: File will be overwritten if it exists.</string>
+ <string name="specify_file_to_export_secret_keys_to">Please specify which file to export to.\nWARNING: You are about to export SECRET keys.\nWARNING: File will be overwritten if it exists.</string>
+ <string name="key_deletion_confirmation">Do you really want to delete the key \'%s\'?\nYou can\'t undo this!</string>
+ <string name="key_deletion_confirmation_multi">Do you really want to delete all selected keys?\nYou can\'t undo this!</string>
+ <string name="secret_key_deletion_confirmation">Do you really want to delete the SECRET key \'%s\'?\nYou can\'t undo this!</string>
+ <string name="ask_save_changed_key">You have made changes to the keyring, would you like to save it?</string>
+ <string name="ask_empty_id_ok">"You have added an empty user ID, are you sure you want to continue?"</string>
+ <string name="public_key_deletetion_confirmation">Do you really want to delete the PUBLIC key \'%s\'?\nYou can\'t undo this!</string>
+ <string name="secret_key_delete_text">Delete Secret Keys ?</string>
+ <string name="also_export_secret_keys">Also export secret keys?</string>
+
+ <plurals name="keys_added_and_updated_1">
+ <item quantity="one">Successfully added %d key</item>
+ <item quantity="other">Successfully added %d keys</item>
+ </plurals>
+ <plurals name="keys_added_and_updated_2">
+ <item quantity="one"> and updated %d key.</item>
+ <item quantity="other"> and updated %d keys.</item>
+ </plurals>
+ <plurals name="keys_added">
+ <item quantity="one">Successfully added %d key.</item>
+ <item quantity="other">Successfully added %d keys.</item>
+ </plurals>
+ <plurals name="keys_updated">
+ <item quantity="one">Successfully updated %d key.</item>
+ <item quantity="other">Successfully updated %d keys.</item>
+ </plurals>
+
+ <string name="no_keys_added_or_updated">No keys added or updated.</string>
+ <string name="key_exported">Successfully exported 1 key.</string>
+ <string name="keys_exported">Successfully exported %d keys.</string>
+ <string name="no_keys_exported">No keys exported.</string>
+ <string name="key_creation_el_gamal_info">Note: only subkeys support ElGamal, and for ElGamal the nearest keysize of 1536, 2048, 3072, 4096, or 8192 will be used.</string>
+ <string name="key_creation_weak_rsa_info">Note: generating RSA key with length 1024-bit and less is considered unsafe and it\'s disabled for generating new keys.</string>
+ <string name="key_not_found">Couldn\'t find key %08X.</string>
+
+ <plurals name="keys_found">
+ <item quantity="one">Found %d key.</item>
+ <item quantity="other">Found %d keys.</item>
+ </plurals>
+
+ <string name="unknown_signature">Unknown signature, click button to lookup the missing key.</string>
+
+ <plurals name="bad_keys_encountered">
+ <item quantity="one">%d bad secret key ignored. Perhaps you exported with the option\n --export-secret-subkeys\nMake sure you export with\n --export-secret-keys\ninstead.</item>
+ <item quantity="other">%d bad secret keys ignored. Perhaps you exported with the option\n --export-secret-subkeys\nMake sure you export with\n --export-secret-keys\ninstead.</item>
+ </plurals>
+
+ <string name="key_send_success">Successfully uploaded key to server</string>
+ <string name="key_sign_success">Successfully signed key</string>
+ <string name="list_empty">This list is empty!</string>
+ <string name="nfc_successfull">Successfully sent key with NFC Beam!</string>
+ <string name="key_copied_to_clipboard">Key has been copied to the clipboard!</string>
+ <string name="key_has_already_been_signed">Key has already been signed!</string>
+ <string name="select_key_to_sign">Please select a key to be used for signing!</string>
+ <string name="key_too_big_for_sharing">Key is too big to be shared this way!</string>
+
+ <!--
+ errors
+ no punctuation, all lowercase,
+ they will be put after "error_message", e.g. "Error: file not found"
+ -->
+ <string name="error_file_delete_failed">deleting \'%s\' failed</string>
+ <string name="error_file_not_found">file not found</string>
+ <string name="error_no_secret_key_found">no suitable secret key found</string>
+ <string name="error_no_known_encryption_found">no known kind of encryption found</string>
+ <string name="error_external_storage_not_ready">external storage not ready</string>
+ <string name="error_invalid_email">invalid email \'%s\'</string>
+ <string name="error_key_size_minimum512bit">key size must be at least 512bit</string>
+ <string name="error_master_key_must_not_be_el_gamal">the master key cannot be an ElGamal key</string>
+ <string name="error_unknown_algorithm_choice">unknown algorithm choice</string>
+ <string name="error_user_id_needs_a_name">you need to specify a name</string>
+ <string name="error_user_id_no_email">no email found</string>
+ <string name="error_user_id_needs_an_email_address">you need to specify an email address</string>
+ <string name="error_key_needs_a_user_id">need at least one user id</string>
+ <string name="error_main_user_id_must_not_be_empty">main user id must not be empty</string>
+ <string name="error_key_needs_master_key">need at least a master key</string>
+ <string name="error_no_encryption_keys_or_passphrase">no encryption keys or passphrase given</string>
+ <string name="error_signature_failed">signature failed</string>
+ <string name="error_no_signature_passphrase">no passphrase given</string>
+ <string name="error_no_signature_key">no signature key given</string>
+ <string name="error_invalid_data">not valid encryption data</string>
+ <string name="error_corrupt_data">corrupt data</string>
+ <string name="error_integrity_check_failed">integrity check failed! Data has been modified!</string>
+ <string name="error_no_symmetric_encryption_packet">couldn\'t find a packet with symmetric encryption</string>
+ <string name="error_wrong_passphrase">wrong passphrase</string>
+ <string name="error_saving_keys">error saving some keys</string>
+ <string name="error_could_not_extract_private_key">could not extract private key</string>
+ <string name="error_only_files_are_supported">Direct binary data without actual file in filesystem is not supported. This is only supported by ACTION_ENCRYPT_STREAM_AND_RETURN.</string>
+ <string name="error_jelly_bean_needed">You need Android 4.1 alias Jelly Bean to use Androids NFC Beam feature!</string>
+ <string name="error_nfc_needed">NFC is not available on your device!</string>
+ <string name="error_nothing_import">Nothing to import!</string>
+ <string name="error_expiry_must_come_after_creation">expiry date must come after creation date</string>
+ <string name="error_save_first">please save the keyring first</string>
+ <string name="error_can_not_delete_contact">you can not delete this contact because it is your own.</string>
+ <string name="error_can_not_delete_contacts">you can not delete the following contacts because they are your own:\n%s</string>
+ <string name="error_keyserver_insufficient_query">Insufficient server query</string>
+ <string name="error_keyserver_query">Querying keyserver failed</string>
+ <string name="error_keyserver_too_many_responses">Too many responses</string>
+ <string name="error_import_file_no_content">File has no content</string>
+ <string name="error_generic_report_bug">A generic error occurred, please create a new bug report for OpenKeychain.</string>
+ <plurals name="error_can_not_delete_info">
+ <item quantity="one">Please delete it from the \'My Keys\' screen!</item>
+ <item quantity="other">Please delete them from the \'My Keys\' screen!</item>
+ </plurals>
+ <plurals name="error_import_non_pgp_part">
+ <item quantity="one">part of the loaded file is a valid OpenPGP object but not a OpenPGP key</item>
+ <item quantity="other">parts of the loaded file are valid OpenPGP objects but not OpenPGP keys</item>
+ </plurals>
+ <string name="error_change_something_first">You must make changes to the keyring before you can save it</string>
+
+ <!-- progress dialogs, usually ending in '…' -->
+ <string name="progress_done">Done.</string>
+ <string name="progress_cancel">Cancel</string>
+ <string name="progress_saving">saving…</string>
+ <string name="progress_importing">importing…</string>
+ <string name="progress_exporting">exporting…</string>
+ <string name="progress_building_key">building key…</string>
+ <string name="progress_preparing_master_key">preparing master key…</string>
+ <string name="progress_certifying_master_key">certifying master key…</string>
+ <string name="progress_building_master_key">building master ring…</string>
+ <string name="progress_adding_sub_keys">adding sub keys…</string>
+ <string name="progress_saving_key_ring">saving key…</string>
+
+ <plurals name="progress_exporting_key">
+ <item quantity="one">exporting key…</item>
+ <item quantity="other">exporting keys…</item>
+ </plurals>
+
+ <plurals name="progress_generating">
+ <item quantity="one">generating key, this can take up to 3 minutes…</item>
+ <item quantity="other">generating keys, this can take up to 3 minutes…</item>
+ </plurals>
+
+ <string name="progress_extracting_signature_key">extracting signature key…</string>
+ <string name="progress_extracting_key">extracting key…</string>
+ <string name="progress_preparing_streams">preparing streams…</string>
+ <string name="progress_encrypting">encrypting data…</string>
+ <string name="progress_decrypting">decrypting data…</string>
+ <string name="progress_preparing_signature">preparing signature…</string>
+ <string name="progress_generating_signature">generating signature…</string>
+ <string name="progress_processing_signature">processing signature…</string>
+ <string name="progress_verifying_signature">verifying signature…</string>
+ <string name="progress_signing">signing…</string>
+ <string name="progress_reading_data">reading data…</string>
+ <string name="progress_finding_key">finding key…</string>
+ <string name="progress_decompressing_data">decompressing data…</string>
+ <string name="progress_verifying_integrity">verifying integrity…</string>
+ <string name="progress_deleting_securely">deleting \'%s\' securely…</string>
+ <string name="progress_querying">querying…</string>
+
+ <!-- action strings -->
+ <string name="hint_public_keys">Search Public Keys</string>
+ <string name="hint_secret_keys">Search Secret Keys</string>
+ <string name="action_share_key_with">Share Key with…</string>
+
+ <!-- key bit length selections -->
+ <string name="key_size_512">512</string>
+ <string name="key_size_1024">1024</string>
+ <string name="key_size_2048">2048</string>
+ <string name="key_size_4096">4096</string>
+
+ <!-- compression -->
+ <string name="compression_fast">fast</string>
+ <string name="compression_very_slow">very slow</string>
+
+ <!-- Help -->
+ <string name="help_tab_start">Start</string>
+ <string name="help_tab_faq">FAQ</string>
+ <string name="help_tab_nfc_beam">NFC Beam</string>
+ <string name="help_tab_changelog">Changelog</string>
+ <string name="help_tab_about">About</string>
+ <string name="help_about_version">Version:</string>
+
+ <!-- Import -->
+ <string name="import_import">Import selected keys</string>
+ <string name="import_sign_and_upload">Import, Sign, and upload selected keys</string>
+ <string name="import_from_clipboard">Import from clipboard</string>
+
+ <plurals name="import_qr_code_missing">
+ <item quantity="one">Missing QR Code with ID %s</item>
+ <item quantity="other">Missing QR Codes with IDs %s</item>
+ </plurals>
+
+ <string name="import_qr_code_start_with_one">Please start with QR Code with ID 1</string>
+ <string name="import_qr_code_wrong">QR Code malformed! Please try again!</string>
+ <string name="import_qr_code_finished">QR Code scanning finished!</string>
+ <string name="import_qr_code_too_short_fingerprint">Fingerprint is too short (&lt; 16 characters)</string>
+ <string name="import_qr_scan_button">Scan QR Code with \'Barcode Scanner\'</string>
+ <string name="import_nfc_text">To receive keys via NFC, the device needs to be unlocked.</string>
+ <string name="import_nfc_help_button">Help</string>
+ <string name="import_clipboard_button">Get key from clipboard</string>
+
+ <!-- Intent labels -->
+ <string name="intent_decrypt_file">Decrypt File with OpenKeychain</string>
+ <string name="intent_import_key">Import Key with OpenKeychain</string>
+ <string name="intent_send_encrypt">Encrypt with OpenKeychain</string>
+ <string name="intent_send_decrypt">Decrypt with OpenKeychain</string>
+
+ <!-- Remote API -->
+ <string name="api_no_apps">No registered applications!\n\nThird-party applications can request access to OpenKeychain. After granting access, they will be listed here.</string>
+ <string name="api_settings_show_info">Show advanced information</string>
+ <string name="api_settings_hide_info">Hide advanced information</string>
+ <string name="api_settings_show_advanced">Show advanced settings</string>
+ <string name="api_settings_hide_advanced">Hide advanced settings</string>
+ <string name="api_settings_no_key">No key selected</string>
+ <string name="api_settings_select_key">Select key</string>
+ <string name="api_settings_create_key">Create new key for this account</string>
+ <string name="api_settings_save">Save</string>
+ <string name="api_settings_cancel">Cancel</string>
+ <string name="api_settings_revoke">Revoke access</string>
+ <string name="api_settings_delete_account">Delete account</string>
+ <string name="api_settings_package_name">Package Name</string>
+ <string name="api_settings_package_signature">SHA-256 of Package Signature</string>
+ <string name="api_settings_accounts">Accounts</string>
+ <string name="api_settings_accounts_empty">No accounts attached to this application.</string>
+ <string name="api_create_account_text">The application requests the creation of a new account. Please select an existing private key or create a new one.\nApplications are restricted to the usage of keys you select here!</string>
+ <string name="api_register_text">The displayed application requests access to OpenKeychain.\nAllow access?\n\nWARNING: If you do not know why this screen appeared, disallow access! You can revoke access later using the \'Registered Applications\' screen.</string>
+ <string name="api_register_allow">Allow access</string>
+ <string name="api_register_disallow">Disallow access</string>
+ <string name="api_register_error_select_key">Please select a key!</string>
+ <string name="api_select_pub_keys_missing_text">No public keys were found for these user ids:</string>
+ <string name="api_select_pub_keys_dublicates_text">More than one public key exist for these user ids:</string>
+ <string name="api_select_pub_keys_text">Please review the list of recipients!</string>
+ <string name="api_error_wrong_signature">Signature check failed! Have you installed this app from a different source? If you are sure that this is not an attack, revoke this app\'s registration in OpenKeychain and then register the app again.</string>
+
+ <!-- Share -->
+ <string name="share_qr_code_dialog_title">Share with QR Code</string>
+ <string name="share_qr_code_dialog_start">Go through all QR Codes using \'Next\', and scan them one by one.</string>
+ <string name="share_qr_code_dialog_fingerprint_text">Fingerprint:</string>
+ <string name="share_qr_code_dialog_progress">QR Code with ID %1$d of %2$d</string>
+ <string name="share_nfc_dialog">Share with NFC</string>
+
+ <!-- Key list -->
+ <plurals name="key_list_selected_keys">
+ <item quantity="one">1 key selected.</item>
+ <item quantity="other">%d keys selected.</item>
+ </plurals>
+
+ <string name="key_list_empty_text1">No keys available yet…</string>
+ <string name="key_list_empty_text2">You can start by</string>
+ <string name="key_list_empty_text3">or</string>
+ <string name="key_list_empty_button_create">creating your own key</string>
+ <string name="key_list_empty_button_import">importing keys.</string>
+
+ <!-- Key view -->
+ <string name="key_view_action_edit">Edit this key</string>
+ <string name="key_view_action_encrypt">Encrypt to this contact</string>
+ <string name="key_view_action_certify">Certify this contact\'s key</string>
+ <string name="key_view_tab_main">Info</string>
+ <string name="key_view_tab_certs">Certifications</string>
+
+ <!-- Navigation Drawer -->
+ <string name="nav_contacts">Keys</string>
+ <string name="nav_encrypt">Sign and Encrypt</string>
+ <string name="nav_decrypt">Decrypt and Verify</string>
+ <string name="nav_import">Import Keys</string>
+ <string name="nav_secret_keys">My Keys</string>
+ <string name="nav_apps">Registered Apps</string>
+ <string name="drawer_open">Open navigation drawer</string>
+ <string name="drawer_close">Close navigation drawer</string>
+ <string name="edit">Edit</string>
+ <string name="my_keys">My Keys</string>
+ <string name="label_secret_key">Secret Key</string>
+ <string name="secret_key_yes">available</string>
+ <string name="secret_key_no">unavailable</string>
+
+ <!-- hints -->
+ <string name="encrypt_content_edit_text_hint">Write message here to encrypt and/or sign…</string>
+ <string name="decrypt_content_edit_text_hint">Enter ciphertext here to decrypt and/or verify…</string>
+
+ <!-- unsorted -->
+ <string name="show_unknown_signatures">Show unknown signatures</string>
+ <string name="section_signer_id">Signer</string>
+ <string name="section_cert">Certificate Details</string>
+ <string name="label_user_id">User ID</string>
+ <string name="label_subkey_rank">Subkey Rank</string>
+ <string name="unknown_uid"><![CDATA[<unknown>]]></string>
+ <string name="empty_certs">No certificates for this key</string>
+ <string name="section_uids_to_sign">User IDs to sign</string>
+ <string name="progress_re_adding_certs">Reapplying certificates</string>
+ <string name="certs_list_known_secret">Show by known secret keys</string>
+ <string name="certs_list_known">Show by known public keys</string>
+ <string name="certs_list_all">Show all certificates</string>
+ <string name="cert_default">default</string>
+ <string name="cert_none">none</string>
+ <string name="cert_casual">casual</string>
+ <string name="cert_positive">positive</string>
+ <string name="cert_revoke">revoke</string>
+ <string name="never">never</string>
+
+</resources>
diff --git a/OpenKeychain/src/main/res/values/styles.xml b/OpenKeychain/src/main/res/values/styles.xml
new file mode 100644
index 000000000..f03d72605
--- /dev/null
+++ b/OpenKeychain/src/main/res/values/styles.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Used in Android < 4 -->
+
+
+ <style name="KeychainTheme" parent="@style/Theme.AppCompat.Light">
+ <item name="android:alertDialogStyle">@style/CustomDialogTheme</item>
+ </style>
+
+ <!-- Ugly fix to make content background of Dialogs on Android < 4 white not black! -->
+ <style name="CustomDialogTheme" parent="@android:style/Theme.Dialog">
+ <item name="android:fullDark">@drawable/popup_full_bright</item>
+ <!--<item name="android:topDark">@android:drawable/popup_full_dark</item>-->
+ <item name="android:centerDark">@drawable/popup_center_bright</item>
+ <!--<item name="android:bottomDark">@android:drawable/popup_bottom_dark</item>-->
+ <!--<item name="fullBright">@android:drawable/popup_full_bright</item>-->
+ <!--<item name="topBright">@android:drawable/popup_top_bright</item>-->
+ <!--<item name="centerBright">@android:drawable/popup_center_bright</item>-->
+ <!--<item name="bottomBright">@android:drawable/popup_bottom_bright</item>-->
+ <!--<item name="bottomMedium">@android:drawable/popup_bottom_medium</item>-->
+ <!--<item name="centerMedium">@android:drawable/popup_center_medium</item>-->
+ </style>
+
+ <style name="SectionHeader">
+ <item name="android:drawableBottom">@drawable/section_header</item>
+ <item name="android:drawablePadding">4dp</item>
+ <item name="android:layout_marginTop">8dp</item>
+ <item name="android:paddingLeft">4dp</item>
+ <item name="android:textColor">@color/emphasis</item>
+ <item name="android:textSize">14sp</item>
+ </style>
+
+
+</resources> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/xml/adv_preferences.xml b/OpenKeychain/src/main/res/xml/adv_preferences.xml
new file mode 100644
index 000000000..03f93b051
--- /dev/null
+++ b/OpenKeychain/src/main/res/xml/adv_preferences.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
+
+ 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.
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+ <PreferenceCategory android:title="@string/section_defaults" >
+ <org.sufficientlysecure.keychain.ui.widget.IntegerListPreference
+ android:key="defaultEncryptionAlgorithm"
+ android:persistent="false"
+ android:title="@string/label_encryption_algorithm" />
+ <org.sufficientlysecure.keychain.ui.widget.IntegerListPreference
+ android:key="defaultHashAlgorithm"
+ android:persistent="false"
+ android:title="@string/label_hash_algorithm" />
+ <org.sufficientlysecure.keychain.ui.widget.IntegerListPreference
+ android:key="defaultMessageCompression"
+ android:persistent="false"
+ android:title="@string/label_message_compression" />
+ <org.sufficientlysecure.keychain.ui.widget.IntegerListPreference
+ android:key="defaultFileCompression"
+ android:persistent="false"
+ android:title="@string/label_file_compression" />
+
+ <CheckBoxPreference
+ android:key="defaultAsciiArmor"
+ android:persistent="false"
+ android:title="@string/label_ascii_armor" />
+ </PreferenceCategory>
+ <PreferenceCategory android:title="@string/section_advanced" >
+ <CheckBoxPreference
+ android:key="forceV3Signatures"
+ android:persistent="false"
+ android:title="@string/label_force_v3_signature" />
+ </PreferenceCategory>
+</PreferenceScreen>
diff --git a/OpenKeychain/src/main/res/xml/gen_preferences.xml b/OpenKeychain/src/main/res/xml/gen_preferences.xml
new file mode 100644
index 000000000..9f1883df0
--- /dev/null
+++ b/OpenKeychain/src/main/res/xml/gen_preferences.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
+
+ 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.
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+ <PreferenceCategory android:title="@string/section_general" >
+ <org.sufficientlysecure.keychain.ui.widget.IntegerListPreference
+ android:entries="@array/passphrase_cache_ttl_entries"
+ android:entryValues="@array/passphrase_cache_ttl_values"
+ android:key="passphraseCacheTtl"
+ android:persistent="false"
+ android:title="@string/label_passphrase_cache_ttl" />
+
+ <PreferenceScreen
+ android:key="keyServers"
+ android:persistent="false"
+ android:title="@string/label_key_servers" />
+ </PreferenceCategory>
+</PreferenceScreen> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/xml/preference_headers.xml b/OpenKeychain/src/main/res/xml/preference_headers.xml
new file mode 100644
index 000000000..3506ba322
--- /dev/null
+++ b/OpenKeychain/src/main/res/xml/preference_headers.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
+
+ 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.
+-->
+
+<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
+ <header
+ android:fragment="org.sufficientlysecure.keychain.ui.PreferencesActivity$GeneralPrefsFragment"
+ android:title="@string/section_general" />
+ <header
+ android:fragment="org.sufficientlysecure.keychain.ui.PreferencesActivity$AdvancedPrefsFragment"
+ android:title="@string/section_advanced" />
+</preference-headers> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/xml/preference_headers_legacy.xml b/OpenKeychain/src/main/res/xml/preference_headers_legacy.xml
new file mode 100644
index 000000000..141bf93e5
--- /dev/null
+++ b/OpenKeychain/src/main/res/xml/preference_headers_legacy.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
+
+ 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.
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+ <Preference
+ android:title="@string/section_general" >
+ <intent
+ android:action="org.sufficientlysecure.keychain.ui.PREFS_GEN" />
+ </Preference>
+ <Preference
+ android:title="@string/section_advanced" >
+ <intent
+ android:action="org.sufficientlysecure.keychain.ui.PREFS_ADV" />
+ </Preference>
+</PreferenceScreen> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/xml/searchable_public_keys.xml b/OpenKeychain/src/main/res/xml/searchable_public_keys.xml
new file mode 100644
index 000000000..f07a809a0
--- /dev/null
+++ b/OpenKeychain/src/main/res/xml/searchable_public_keys.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
+
+ 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.
+-->
+
+<searchable xmlns:android="http://schemas.android.com/apk/res/android"
+ android:hint="@string/hint_public_keys"
+ android:label="@string/app_name" >
+
+</searchable> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/xml/searchable_secret_keys.xml b/OpenKeychain/src/main/res/xml/searchable_secret_keys.xml
new file mode 100644
index 000000000..59b9841db
--- /dev/null
+++ b/OpenKeychain/src/main/res/xml/searchable_secret_keys.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
+
+ 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.
+-->
+
+<searchable xmlns:android="http://schemas.android.com/apk/res/android"
+ android:hint="@string/hint_secret_keys"
+ android:label="@string/app_name" >
+
+</searchable> \ No newline at end of file
diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/PgpKeyOperationTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/PgpKeyOperationTest.java
new file mode 100644
index 000000000..72f29a1e3
--- /dev/null
+++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/PgpKeyOperationTest.java
@@ -0,0 +1,46 @@
+package org.sufficientlysecure.keychain;
+
+import org.junit.Before;
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+
+import org.sufficientlysecure.keychain.pgp.*;
+import org.spongycastle.openpgp.*;
+
+@RunWith(RobolectricGradleTestRunner.class)
+public class PgpKeyOperationTest {
+
+ PGPSecretKey key;
+
+ @Before
+ public void setUp() throws Exception {
+
+ /* Input */
+ int algorithm = Id.choice.algorithm.dsa;
+ String passphrase = "swag";
+ int keysize = 2048;
+ boolean masterKey = true;
+
+ /* Operation */
+ PgpKeyOperation keyOperations = new PgpKeyOperation(null);
+ key = keyOperations.createKey(algorithm, keysize, passphrase, masterKey);
+
+ System.err.println("initialized, test key: " + PgpKeyHelper.convertKeyIdToHex(key.getKeyID()));
+ }
+
+ @After
+ public void tearDown() {
+ }
+
+ @Test
+ public void createTest() {
+ }
+
+ @Test
+ public void certifyKey() {
+ System.err.println("swag");
+ }
+
+}
diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/RobolectricGradleTestRunner.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/RobolectricGradleTestRunner.java
new file mode 100644
index 000000000..b64ffde07
--- /dev/null
+++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/RobolectricGradleTestRunner.java
@@ -0,0 +1,23 @@
+package org.sufficientlysecure.keychain;
+
+import org.junit.runners.model.InitializationError;
+import org.robolectric.AndroidManifest;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.res.Fs;
+import org.robolectric.res.FsFile;
+
+import org.sufficientlysecure.keychain.KeychainApplication;
+
+public class RobolectricGradleTestRunner extends RobolectricTestRunner {
+ public RobolectricGradleTestRunner(Class<?> testClass) throws InitializationError {
+ super(testClass);
+ }
+
+ @Override protected AndroidManifest getAppManifest(Config config) {
+ String myAppPath = KeychainApplication.class.getProtectionDomain().getCodeSource().getLocation().getPath();
+ String manifestPath = myAppPath + "../../../src/main/AndroidManifest.xml";
+ return createAppManifest(Fs.fileFromPath(manifestPath));
+ }
+}
+