aboutsummaryrefslogtreecommitdiffstats
path: root/OpenPGP-Keychain/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'OpenPGP-Keychain/src/main')
-rw-r--r--OpenPGP-Keychain/src/main/AndroidManifest.xml408
-rw-r--r--OpenPGP-Keychain/src/main/aidl/org/openintents/openpgp/IOpenPgpCallback.aidl45
-rw-r--r--OpenPGP-Keychain/src/main/aidl/org/openintents/openpgp/IOpenPgpKeyIdsCallback.aidl39
-rw-r--r--OpenPGP-Keychain/src/main/aidl/org/openintents/openpgp/IOpenPgpService.aidl143
-rw-r--r--OpenPGP-Keychain/src/main/aidl/org/openintents/openpgp/OpenPgpData.aidl20
-rw-r--r--OpenPGP-Keychain/src/main/aidl/org/openintents/openpgp/OpenPgpError.aidl20
-rw-r--r--OpenPGP-Keychain/src/main/aidl/org/openintents/openpgp/OpenPgpSignatureResult.aidl20
-rw-r--r--OpenPGP-Keychain/src/main/aidl/org/sufficientlysecure/keychain/service/remote/IExtendedApiCallback.aidl24
-rw-r--r--OpenPGP-Keychain/src/main/aidl/org/sufficientlysecure/keychain/service/remote/IExtendedApiService.aidl48
-rw-r--r--OpenPGP-Keychain/src/main/assets/fontawesome-webfont.ttfbin0 -> 80776 bytes
-rw-r--r--OpenPGP-Keychain/src/main/java/org/openintents/openpgp/OpenPgpConstants.java10
-rw-r--r--OpenPGP-Keychain/src/main/java/org/openintents/openpgp/OpenPgpData.java127
-rw-r--r--OpenPGP-Keychain/src/main/java/org/openintents/openpgp/OpenPgpError.java81
-rw-r--r--OpenPGP-Keychain/src/main/java/org/openintents/openpgp/OpenPgpHelper.java52
-rw-r--r--OpenPGP-Keychain/src/main/java/org/openintents/openpgp/OpenPgpListPreference.java201
-rw-r--r--OpenPGP-Keychain/src/main/java/org/openintents/openpgp/OpenPgpServiceConnection.java93
-rw-r--r--OpenPGP-Keychain/src/main/java/org/openintents/openpgp/OpenPgpSignatureResult.java110
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/Constants.java61
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/Id.java190
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java74
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/compatibility/ClipboardReflection.java98
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/compatibility/DialogFragmentWorkaround.java67
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/compatibility/ListFragmentWorkaround.java38
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/ActionBarHelper.java117
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/ExportHelper.java184
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/FileHelper.java138
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/OtherHelper.java82
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/Preferences.java172
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpConversionHelper.java139
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java219
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java314
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java563
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java466
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpOperation.java1154
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpToX509.java307
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/exception/NoAsymmetricEncryptionException.java9
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/exception/PgpGeneralException.java9
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java268
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java126
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java971
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainServiceBlobContract.java41
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainServiceBlobDatabase.java48
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainServiceBlobProvider.java152
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java809
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java869
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentServiceHandler.java110
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java370
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/exception/NoUserIdsException.java10
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/exception/UserInteractionRequiredException.java10
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/exception/WrongPackageSignatureException.java10
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/exception/WrongPassphraseException.java10
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/AppSettings.java94
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/AppSettingsActivity.java109
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/AppSettingsFragment.java234
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/ExtendedApiService.java122
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/OpenPgpService.java602
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/RegisteredAppsAdapter.java76
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/RegisteredAppsListActivity.java36
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/RegisteredAppsListFragment.java103
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/RemoteService.java354
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/RemoteServiceActivity.java352
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java846
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DrawerActivity.java485
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java666
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java1003
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpActivity.java157
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpFragmentAbout.java87
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpFragmentHtml.java88
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java468
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysClipboardFragment.java79
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java124
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java177
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysNFCFragment.java70
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysQrCodeFragment.java209
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysServerFragment.java67
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListPublicActivity.java72
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListPublicFragment.java299
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListSecretActivity.java95
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListSecretFragment.java215
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyServerQueryActivity.java391
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyServerUploadActivity.java127
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesActivity.java221
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesKeyServerActivity.java126
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyActivity.java144
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyFragment.java253
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectSecretKeyActivity.java139
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectSecretKeyFragment.java188
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectSecretKeyLayoutFragment.java139
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SignKeyActivity.java308
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java484
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivityJB.java122
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java159
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListEntry.java105
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java128
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyListPublicAdapter.java221
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyListSecretAdapter.java139
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyValueSpinnerAdapter.java101
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java167
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyKeysAdapter.java123
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyUserIdsAdapter.java76
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteFileDialogFragment.java121
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java151
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java224
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/LookupUnknownKeyDialogFragment.java138
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/PassphraseDialogFragment.java286
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ProgressDialogFragment.java125
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SetPassphraseDialogFragment.java191
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ShareNfcDialogFragment.java102
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ShareQrCodeDialogFragment.java209
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/Editor.java25
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/FixedListView.java55
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/IntegerListPreference.java94
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyEditor.java256
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyServerEditor.java79
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SectionView.java364
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/UnderlineTextView.java69
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/UserIdEditor.java207
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/AlgorithmNames.java92
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/Choice.java45
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/HkpKeyServer.java257
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/InputData.java39
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/IntentIntegratorSupportV4.java47
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/IterableIterator.java31
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/KeyServer.java97
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/Log.java84
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/PRNGFixes.java361
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/PausableThreadPoolExecutor.java90
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/PositionAwareInputStream.java81
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/Primes.java188
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/ProgressDialogUpdater.java25
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/QrCodeUtils.java72
-rw-r--r--OpenPGP-Keychain/src/main/res/anim/push_left_in.xml20
-rw-r--r--OpenPGP-Keychain/src/main/res/anim/push_left_out.xml20
-rw-r--r--OpenPGP-Keychain/src/main/res/anim/push_right_in.xml20
-rw-r--r--OpenPGP-Keychain/src/main/res/anim/push_right_out.xml20
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-hdpi/certify_small.pngbin0 -> 2091 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-hdpi/drawer_shadow.9.pngbin0 -> 161 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-hdpi/encrypted_small.pngbin0 -> 2187 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-hdpi/ic_action_add_person.pngbin0 -> 679 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-hdpi/ic_action_cancel.pngbin0 -> 1358 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-hdpi/ic_action_discard.pngbin0 -> 454 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-hdpi/ic_action_done.pngbin0 -> 1320 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-hdpi/ic_action_import_export.pngbin0 -> 497 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-hdpi/ic_action_share.pngbin0 -> 647 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-hdpi/ic_drawer.pngbin0 -> 2829 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-hdpi/ic_menu_search.pngbin0 -> 1218 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-hdpi/ic_menu_search_list.pngbin0 -> 1190 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-hdpi/ic_next.pngbin0 -> 1722 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-hdpi/ic_previous.pngbin0 -> 1712 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-hdpi/icon.pngbin0 -> 5101 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-hdpi/key_small.pngbin0 -> 2088 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-hdpi/overlay_error.pngbin0 -> 1986 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-hdpi/overlay_ok.pngbin0 -> 1702 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-hdpi/signed_large.pngbin0 -> 5928 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-hdpi/signed_small.pngbin0 -> 2219 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-ldpi/encrypted_small.pngbin0 -> 1176 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-ldpi/ic_next.pngbin0 -> 916 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-ldpi/ic_previous.pngbin0 -> 922 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-ldpi/icon.pngbin0 -> 1969 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-ldpi/key_small.pngbin0 -> 1074 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-ldpi/overlay_error.pngbin0 -> 1192 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-ldpi/overlay_ok.pngbin0 -> 1038 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-ldpi/signed_large.pngbin0 -> 2611 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-ldpi/signed_small.pngbin0 -> 1149 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-mdpi/certify_small.pngbin0 -> 1401 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-mdpi/drawer_shadow.9.pngbin0 -> 142 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-mdpi/encrypted_small.pngbin0 -> 1513 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-mdpi/ic_action_add_person.pngbin0 -> 513 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-mdpi/ic_action_cancel.pngbin0 -> 1202 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-mdpi/ic_action_discard.pngbin0 -> 333 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-mdpi/ic_action_done.pngbin0 -> 1197 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-mdpi/ic_action_import_export.pngbin0 -> 410 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-mdpi/ic_action_share.pngbin0 -> 472 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-mdpi/ic_drawer.pngbin0 -> 2820 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-mdpi/ic_menu_search.pngbin0 -> 858 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-mdpi/ic_menu_search_list.pngbin0 -> 863 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-mdpi/ic_next.pngbin0 -> 1360 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-mdpi/ic_previous.pngbin0 -> 1352 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-mdpi/icon.pngbin0 -> 2906 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-mdpi/key_small.pngbin0 -> 1484 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-mdpi/overlay_error.pngbin0 -> 1539 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-mdpi/overlay_ok.pngbin0 -> 1305 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-mdpi/signed_large.pngbin0 -> 3858 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-mdpi/signed_small.pngbin0 -> 1576 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-xhdpi/drawer_shadow.9.pngbin0 -> 174 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-xhdpi/ic_action_add_person.pngbin0 -> 884 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-xhdpi/ic_action_cancel.pngbin0 -> 1488 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-xhdpi/ic_action_discard.pngbin0 -> 552 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-xhdpi/ic_action_done.pngbin0 -> 1546 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-xhdpi/ic_action_import_export.pngbin0 -> 633 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-xhdpi/ic_action_share.pngbin0 -> 785 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-xhdpi/ic_drawer.pngbin0 -> 2836 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-xhdpi/ic_menu_search.pngbin0 -> 1629 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-xhdpi/ic_menu_search_list.pngbin0 -> 1571 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-xhdpi/icon.pngbin0 -> 7895 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-xxhdpi/drawer_shadow.9.pngbin0 -> 208 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-xxhdpi/ic_action_add_person.pngbin0 -> 1171 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-xxhdpi/ic_action_discard.pngbin0 -> 781 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-xxhdpi/ic_action_import_export.pngbin0 -> 896 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-xxhdpi/ic_action_share.pngbin0 -> 1094 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-xxhdpi/ic_drawer.pngbin0 -> 202 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-xxhdpi/icon.pngbin0 -> 14188 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable-xxxhdpi/icon.pngbin0 -> 20919 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable/certify_small.pngbin0 -> 1401 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable/encrypted_small.pngbin0 -> 1513 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable/ic_next.pngbin0 -> 1360 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable/ic_previous.pngbin0 -> 1352 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable/key_small.pngbin0 -> 1484 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable/overlay_error.pngbin0 -> 1539 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable/overlay_ok.pngbin0 -> 1305 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable/section_header.xml11
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable/signed_large.pngbin0 -> 3858 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/drawable/signed_small.pngbin0 -> 1576 bytes
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/actionbar_custom_view_done.xml27
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/actionbar_custom_view_done_cancel.xml29
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/actionbar_include_cancel_button.xml36
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/actionbar_include_done_button.xml36
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/api_app_error_message.xml17
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/api_app_register_activity.xml25
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/api_app_select_pub_keys_activity.xml22
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/api_app_settings_activity.xml17
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/api_app_settings_fragment.xml129
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/api_apps_adapter_list_item.xml28
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/api_apps_list_activity.xml20
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/create_key_dialog.xml54
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/decrypt_activity.xml177
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/drawer_list.xml18
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/drawer_list_item.xml28
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/edit_key_activity.xml62
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/edit_key_key_item.xml125
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/edit_key_section.xml44
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/edit_key_user_id_item.xml98
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/encrypt_activity.xml307
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/file_dialog.xml61
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/help_about_fragment.xml62
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/help_activity.xml12
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/import_keys_activity.xml63
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/import_keys_clipboard_fragment.xml18
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/import_keys_file_fragment.xml32
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/import_keys_keyserver_fragment.xml43
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/import_keys_list_entry.xml108
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/import_keys_list_entry_user_id.xml26
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/import_keys_nfc_fragment.xml28
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/import_keys_qr_code_fragment.xml36
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/key_list_item.xml24
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/key_list_public_activity.xml20
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/key_list_public_fragment.xml77
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/key_list_public_header.xml16
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/key_list_secret_activity.xml20
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/key_server_editor.xml40
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/key_server_export.xml41
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/key_server_preference.xml78
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/key_server_query.xml40
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/key_server_query_result_item.xml77
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/key_server_query_result_user_id.xml10
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/passphrase_dialog.xml24
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/passphrase_repeat_dialog.xml46
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/select_key_item.xml64
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/select_public_key_activity.xml12
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/select_secret_key_activity.xml12
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/select_secret_key_layout_fragment.xml55
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/share_qr_code_dialog.xml19
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/sign_key_activity.xml68
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/view_key_activity.xml220
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/view_key_keys_item.xml63
-rw-r--r--OpenPGP-Keychain/src/main/res/layout/view_key_userids_item.xml17
-rw-r--r--OpenPGP-Keychain/src/main/res/menu/api_app_settings.xml13
-rw-r--r--OpenPGP-Keychain/src/main/res/menu/key_edit.xml17
-rw-r--r--OpenPGP-Keychain/src/main/res/menu/key_list_public.xml14
-rw-r--r--OpenPGP-Keychain/src/main/res/menu/key_list_public_multi.xml12
-rw-r--r--OpenPGP-Keychain/src/main/res/menu/key_list_secret.xml21
-rw-r--r--OpenPGP-Keychain/src/main/res/menu/key_list_secret_multi.xml9
-rw-r--r--OpenPGP-Keychain/src/main/res/menu/key_view.xml79
-rw-r--r--OpenPGP-Keychain/src/main/res/raw-de/help_about.html41
-rw-r--r--OpenPGP-Keychain/src/main/res/raw-de/help_changelog.html96
-rw-r--r--OpenPGP-Keychain/src/main/res/raw-de/help_nfc_beam.html12
-rw-r--r--OpenPGP-Keychain/src/main/res/raw-de/help_start.html22
-rw-r--r--OpenPGP-Keychain/src/main/res/raw-de/nfc_beam_share.html11
-rw-r--r--OpenPGP-Keychain/src/main/res/raw-es-rCO/help_about.html41
-rw-r--r--OpenPGP-Keychain/src/main/res/raw-es-rCO/help_changelog.html96
-rw-r--r--OpenPGP-Keychain/src/main/res/raw-es-rCO/help_nfc_beam.html12
-rw-r--r--OpenPGP-Keychain/src/main/res/raw-es-rCO/help_start.html22
-rw-r--r--OpenPGP-Keychain/src/main/res/raw-es-rCO/nfc_beam_share.html11
-rw-r--r--OpenPGP-Keychain/src/main/res/raw-fr/help_about.html41
-rw-r--r--OpenPGP-Keychain/src/main/res/raw-fr/help_changelog.html96
-rw-r--r--OpenPGP-Keychain/src/main/res/raw-fr/help_nfc_beam.html12
-rw-r--r--OpenPGP-Keychain/src/main/res/raw-fr/help_start.html22
-rw-r--r--OpenPGP-Keychain/src/main/res/raw-fr/nfc_beam_share.html11
-rw-r--r--OpenPGP-Keychain/src/main/res/raw-it-rIT/help_about.html41
-rw-r--r--OpenPGP-Keychain/src/main/res/raw-it-rIT/help_changelog.html96
-rw-r--r--OpenPGP-Keychain/src/main/res/raw-it-rIT/help_nfc_beam.html12
-rw-r--r--OpenPGP-Keychain/src/main/res/raw-it-rIT/help_start.html22
-rw-r--r--OpenPGP-Keychain/src/main/res/raw-it-rIT/nfc_beam_share.html11
-rw-r--r--OpenPGP-Keychain/src/main/res/raw-nl-rNL/help_about.html41
-rw-r--r--OpenPGP-Keychain/src/main/res/raw-nl-rNL/help_changelog.html96
-rw-r--r--OpenPGP-Keychain/src/main/res/raw-nl-rNL/help_nfc_beam.html12
-rw-r--r--OpenPGP-Keychain/src/main/res/raw-nl-rNL/help_start.html22
-rw-r--r--OpenPGP-Keychain/src/main/res/raw-nl-rNL/nfc_beam_share.html11
-rw-r--r--OpenPGP-Keychain/src/main/res/raw-pt-rBR/help_about.html41
-rw-r--r--OpenPGP-Keychain/src/main/res/raw-pt-rBR/help_changelog.html96
-rw-r--r--OpenPGP-Keychain/src/main/res/raw-pt-rBR/help_nfc_beam.html12
-rw-r--r--OpenPGP-Keychain/src/main/res/raw-pt-rBR/help_start.html22
-rw-r--r--OpenPGP-Keychain/src/main/res/raw-pt-rBR/nfc_beam_share.html11
-rw-r--r--OpenPGP-Keychain/src/main/res/raw-ru/help_about.html41
-rw-r--r--OpenPGP-Keychain/src/main/res/raw-ru/help_changelog.html96
-rw-r--r--OpenPGP-Keychain/src/main/res/raw-ru/help_nfc_beam.html12
-rw-r--r--OpenPGP-Keychain/src/main/res/raw-ru/help_start.html22
-rw-r--r--OpenPGP-Keychain/src/main/res/raw-ru/nfc_beam_share.html11
-rw-r--r--OpenPGP-Keychain/src/main/res/raw-sl-rSI/help_about.html41
-rw-r--r--OpenPGP-Keychain/src/main/res/raw-sl-rSI/help_changelog.html96
-rw-r--r--OpenPGP-Keychain/src/main/res/raw-sl-rSI/help_nfc_beam.html12
-rw-r--r--OpenPGP-Keychain/src/main/res/raw-sl-rSI/help_start.html22
-rw-r--r--OpenPGP-Keychain/src/main/res/raw-sl-rSI/nfc_beam_share.html11
-rw-r--r--OpenPGP-Keychain/src/main/res/raw-tr/help_about.html41
-rw-r--r--OpenPGP-Keychain/src/main/res/raw-tr/help_changelog.html96
-rw-r--r--OpenPGP-Keychain/src/main/res/raw-tr/help_nfc_beam.html12
-rw-r--r--OpenPGP-Keychain/src/main/res/raw-tr/help_start.html22
-rw-r--r--OpenPGP-Keychain/src/main/res/raw-tr/nfc_beam_share.html11
-rw-r--r--OpenPGP-Keychain/src/main/res/raw-uk/help_about.html41
-rw-r--r--OpenPGP-Keychain/src/main/res/raw-uk/help_changelog.html96
-rw-r--r--OpenPGP-Keychain/src/main/res/raw-uk/help_nfc_beam.html12
-rw-r--r--OpenPGP-Keychain/src/main/res/raw-uk/help_start.html22
-rw-r--r--OpenPGP-Keychain/src/main/res/raw-uk/nfc_beam_share.html11
-rw-r--r--OpenPGP-Keychain/src/main/res/raw-zh/help_about.html41
-rw-r--r--OpenPGP-Keychain/src/main/res/raw-zh/help_changelog.html96
-rw-r--r--OpenPGP-Keychain/src/main/res/raw-zh/help_nfc_beam.html12
-rw-r--r--OpenPGP-Keychain/src/main/res/raw-zh/help_start.html22
-rw-r--r--OpenPGP-Keychain/src/main/res/raw-zh/nfc_beam_share.html11
-rw-r--r--OpenPGP-Keychain/src/main/res/raw/help_about.html40
-rw-r--r--OpenPGP-Keychain/src/main/res/raw/help_changelog.html112
-rw-r--r--OpenPGP-Keychain/src/main/res/raw/help_nfc_beam.html16
-rw-r--r--OpenPGP-Keychain/src/main/res/raw/help_start.html27
-rw-r--r--OpenPGP-Keychain/src/main/res/raw/nfc_beam_share.html15
-rw-r--r--OpenPGP-Keychain/src/main/res/values-de/strings.xml376
-rw-r--r--OpenPGP-Keychain/src/main/res/values-es-rCO/strings.xml113
-rw-r--r--OpenPGP-Keychain/src/main/res/values-fr/strings.xml377
-rw-r--r--OpenPGP-Keychain/src/main/res/values-it-rIT/strings.xml92
-rw-r--r--OpenPGP-Keychain/src/main/res/values-nl-rNL/strings.xml263
-rw-r--r--OpenPGP-Keychain/src/main/res/values-pt-rBR/strings.xml26
-rw-r--r--OpenPGP-Keychain/src/main/res/values-ru/strings.xml387
-rw-r--r--OpenPGP-Keychain/src/main/res/values-sl-rSI/strings.xml26
-rw-r--r--OpenPGP-Keychain/src/main/res/values-tr/strings.xml152
-rw-r--r--OpenPGP-Keychain/src/main/res/values-uk/strings.xml263
-rw-r--r--OpenPGP-Keychain/src/main/res/values-zh/strings.xml26
-rw-r--r--OpenPGP-Keychain/src/main/res/values/arrays.xml46
-rw-r--r--OpenPGP-Keychain/src/main/res/values/colors.xml7
-rw-r--r--OpenPGP-Keychain/src/main/res/values/static_strings.xml6
-rw-r--r--OpenPGP-Keychain/src/main/res/values/strings.xml419
-rw-r--r--OpenPGP-Keychain/src/main/res/values/styles.xml14
-rw-r--r--OpenPGP-Keychain/src/main/res/xml/preferences.xml63
-rw-r--r--OpenPGP-Keychain/src/main/res/xml/searchable_public_keys.xml22
-rw-r--r--OpenPGP-Keychain/src/main/res/xml/searchable_secret_keys.xml22
352 files changed, 33000 insertions, 0 deletions
diff --git a/OpenPGP-Keychain/src/main/AndroidManifest.xml b/OpenPGP-Keychain/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..1863d2b62
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/AndroidManifest.xml
@@ -0,0 +1,408 @@
+<?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="22000"
+ android:versionName="2.2" >
+
+ <!--
+ 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="8"
+ 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" />
+
+ <!-- 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:label="@string/app_name"
+ android:theme="@style/Theme.Sherlock.Light" >
+ <activity
+ android:name=".ui.KeyListPublicActivity"
+ 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>
+
+ <!-- <intent-filter> -->
+ <!-- <action android:name="android.intent.action.SEARCH" /> -->
+ <!-- </intent-filter> -->
+
+
+ <!-- <meta-data -->
+ <!-- android:name="android.app.searchable" -->
+ <!-- android:resource="@xml/searchable_public_keys" /> -->
+ </activity>
+ <activity
+ android:name=".ui.KeyListSecretActivity"
+ android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
+ android:label="@string/title_manage_secret_keys"
+ android:launchMode="singleTop" >
+
+ <!-- <intent-filter> -->
+ <!-- <action android:name="android.intent.action.SEARCH" /> -->
+ <!-- </intent-filter> -->
+
+
+ <!-- <meta-data -->
+ <!-- android:name="android.app.searchable" -->
+ <!-- android:resource="@xml/searchable_secret_keys" /> -->
+ </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.KeyListPublicActivity" >
+ <meta-data
+ android:name="android.support.PARENT_ACTIVITY"
+ android:value=".ui.KeyListPublicActivity" />
+ </activity>
+ <activity
+ android:name=".ui.ViewKeyActivityJB"
+ android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
+ android:label="@string/title_key_details"
+ android:parentActivityName=".ui.KeyListPublicActivity" >
+ <meta-data
+ android:name="android.support.PARENT_ACTIVITY"
+ android:value=".ui.KeyListPublicActivity" />
+ </activity>
+ <activity
+ android:name=".ui.SelectPublicKeyActivity"
+ android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
+ android:label="@string/title_select_recipients"
+ android:launchMode="singleTop"
+ android:uiOptions="splitActionBarWhenNarrow" >
+
+ <!-- <intent-filter> -->
+ <!-- <action android:name="android.intent.action.SEARCH" /> -->
+ <!-- </intent-filter> -->
+
+
+ <!-- <meta-data -->
+ <!-- android:name="android.app.searchable" -->
+ <!-- android:resource="@xml/searchable_public_keys" /> -->
+ </activity>
+ <activity
+ android:name=".ui.SelectSecretKeyActivity"
+ android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
+ android:label="@string/title_select_secret_key"
+ android:launchMode="singleTop" >
+
+ <!-- <intent-filter> -->
+ <!-- <action android:name="android.intent.action.SEARCH" /> -->
+ <!-- </intent-filter> -->
+
+
+ <!-- <meta-data -->
+ <!-- android:name="android.app.searchable" -->
+ <!-- android:resource="@xml/searchable_secret_keys" /> -->
+ </activity>
+ <activity
+ android:name=".ui.EncryptActivity"
+ android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
+ android:label="@string/title_encrypt"
+ android:uiOptions="splitActionBarWhenNarrow"
+ android:windowSoftInputMode="stateHidden" >
+
+ <!-- Keychain's own Actions -->
+ <intent-filter>
+ <action android:name="org.sufficientlysecure.keychain.action.ENCRYPT" />
+
+ <category android:name="android.intent.category.DEFAULT" />
+
+ <data android:mimeType="*/*" />
+ </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:uiOptions="splitActionBarWhenNarrow"
+ android:windowSoftInputMode="stateHidden" >
+
+ <!-- Keychain's own Actions -->
+ <intent-filter>
+ <action android:name="org.sufficientlysecure.keychain.action.DECRYPT" />
+
+ <category android:name="android.intent.category.DEFAULT" />
+
+ <data android:mimeType="*/*" />
+ </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.KeyServerQueryActivity"
+ android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
+ android:label="@string/title_key_server_query" />
+ <activity
+ android:name=".ui.KeyServerUploadActivity"
+ 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" />
+ <activity
+ android:name=".ui.PreferencesKeyServerActivity"
+ android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
+ android:label="@string/title_key_server_preference"
+ android:uiOptions="splitActionBarWhenNarrow"
+ android:windowSoftInputMode="stateHidden" />
+ <activity
+ android:name=".ui.SignKeyActivity"
+ android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
+ android:label="@string/title_sign_key" />
+ <activity
+ android:name=".ui.ImportKeysActivity"
+ android:label="@string/title_import_keys"
+ android:launchMode="singleTop"
+ android:windowSoftInputMode="stateHidden" >
+
+ <!-- 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, section 7 -->
+ <data android:mimeType="application/pgp-keys" />
+ </intent-filter>
+ <!-- Keychain's own Actions -->
+ <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 again without mimeType to also allow data only without filename -->
+ <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" />
+
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <!-- Linking "Import key" to file types -->
+ <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>
+ <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>
+ </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="org.sufficientlysecure.keychain.service.KeychainIntentService"
+ android:exported="false" />
+
+ <provider
+ android:name="org.sufficientlysecure.keychain.provider.KeychainProvider"
+ android:authorities="org.sufficientlysecure.keychain.provider"
+ android:exported="false" />
+
+ <!-- Internal classes of the remote APIs (not exported) -->
+ <activity
+ android:name="org.sufficientlysecure.keychain.service.remote.RemoteServiceActivity"
+ android:exported="false"
+ android:label="@string/app_name"
+ android:launchMode="singleTop"
+ android:process=":remote_api"
+ android:taskAffinity=":remote_api" />
+ <activity
+ android:name="org.sufficientlysecure.keychain.service.remote.RegisteredAppsListActivity"
+ android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
+ android:exported="false"
+ android:label="@string/title_api_registered_apps" />
+ <activity
+ android:name="org.sufficientlysecure.keychain.service.remote.AppSettingsActivity"
+ android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
+ android:exported="false" />
+
+ <!-- OpenPGP Remote API -->
+ <service
+ android:name="org.sufficientlysecure.keychain.service.remote.OpenPgpService"
+ android:enabled="true"
+ android:exported="true"
+ android:process=":remote_api" >
+ <intent-filter>
+ <action android:name="org.openintents.openpgp.IOpenPgpService" />
+ </intent-filter>
+
+ <meta-data
+ android:name="api_version"
+ android:value="1" />
+ </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>
+
+ <meta-data
+ android:name="api_version"
+ android:value="1" />
+ </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> \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/aidl/org/openintents/openpgp/IOpenPgpCallback.aidl b/OpenPGP-Keychain/src/main/aidl/org/openintents/openpgp/IOpenPgpCallback.aidl
new file mode 100644
index 000000000..ba41de1ba
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/aidl/org/openintents/openpgp/IOpenPgpCallback.aidl
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2013 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.openintents.openpgp;
+
+import org.openintents.openpgp.OpenPgpData;
+import org.openintents.openpgp.OpenPgpSignatureResult;
+import org.openintents.openpgp.OpenPgpError;
+
+interface IOpenPgpCallback {
+
+ /**
+ * onSuccess returns on successful OpenPGP operations.
+ *
+ * @param output
+ * contains resulting output (decrypted content (when input was encrypted)
+ * or content without signature (when input was signed-only))
+ * @param signatureResult
+ * signatureResult is only non-null if decryptAndVerify() was called and the content
+ * was encrypted or signed-and-encrypted.
+ */
+ oneway void onSuccess(in OpenPgpData output, in OpenPgpSignatureResult signatureResult);
+
+ /**
+ * onError returns on errors or when allowUserInteraction was set to false, but user interaction
+ * was required execute an OpenPGP operation.
+ *
+ * @param error
+ * See OpenPgpError class for more information.
+ */
+ oneway void onError(in OpenPgpError error);
+} \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/aidl/org/openintents/openpgp/IOpenPgpKeyIdsCallback.aidl b/OpenPGP-Keychain/src/main/aidl/org/openintents/openpgp/IOpenPgpKeyIdsCallback.aidl
new file mode 100644
index 000000000..4ca356fad
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/aidl/org/openintents/openpgp/IOpenPgpKeyIdsCallback.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2013 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.openintents.openpgp;
+
+import org.openintents.openpgp.OpenPgpError;
+
+interface IOpenPgpKeyIdsCallback {
+
+ /**
+ * onSuccess returns on successful getKeyIds operations.
+ *
+ * @param keyIds
+ * returned key ids
+ */
+ oneway void onSuccess(in long[] keyIds);
+
+ /**
+ * onError returns on errors or when allowUserInteraction was set to false, but user interaction
+ * was required execute an OpenPGP operation.
+ *
+ * @param error
+ * See OpenPgpError class for more information.
+ */
+ oneway void onError(in OpenPgpError error);
+} \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/aidl/org/openintents/openpgp/IOpenPgpService.aidl b/OpenPGP-Keychain/src/main/aidl/org/openintents/openpgp/IOpenPgpService.aidl
new file mode 100644
index 000000000..8f9e8a0fd
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/aidl/org/openintents/openpgp/IOpenPgpService.aidl
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2013 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.openintents.openpgp;
+
+import org.openintents.openpgp.OpenPgpData;
+import org.openintents.openpgp.IOpenPgpCallback;
+import org.openintents.openpgp.IOpenPgpKeyIdsCallback;
+
+/**
+ * All methods are oneway, which means they are asynchronous and non-blocking.
+ * Results are returned to the callback, which has to be implemented on client side.
+ */
+interface IOpenPgpService {
+
+ /**
+ * Sign
+ *
+ * After successful signing, callback's onSuccess will contain the resulting output.
+ *
+ * @param input
+ * OpenPgpData object containing String, byte[], ParcelFileDescriptor, or Uri
+ * @param output
+ * Request output format by defining OpenPgpData object
+ *
+ * new OpenPgpData(OpenPgpData.TYPE_STRING)
+ * Returns as String
+ * (OpenPGP Radix-64, 33 percent overhead compared to binary, see http://tools.ietf.org/html/rfc4880#page-53)
+ * new OpenPgpData(OpenPgpData.TYPE_BYTE_ARRAY)
+ * Returns as byte[]
+ * new OpenPgpData(uri)
+ * Writes output to given Uri
+ * new OpenPgpData(fileDescriptor)
+ * Writes output to given ParcelFileDescriptor
+ * @param callback
+ * Callback where to return results
+ */
+ oneway void sign(in OpenPgpData input, in OpenPgpData output, in IOpenPgpCallback callback);
+
+ /**
+ * Encrypt
+ *
+ * After successful encryption, callback's onSuccess will contain the resulting output.
+ *
+ * @param input
+ * OpenPgpData object containing String, byte[], ParcelFileDescriptor, or Uri
+ * @param output
+ * Request output format by defining OpenPgpData object
+ *
+ * new OpenPgpData(OpenPgpData.TYPE_STRING)
+ * Returns as String
+ * (OpenPGP Radix-64, 33 percent overhead compared to binary, see http://tools.ietf.org/html/rfc4880#page-53)
+ * new OpenPgpData(OpenPgpData.TYPE_BYTE_ARRAY)
+ * Returns as byte[]
+ * new OpenPgpData(uri)
+ * Writes output to given Uri
+ * new OpenPgpData(fileDescriptor)
+ * Writes output to given ParcelFileDescriptor
+ * @param keyIds
+ * Key Ids of recipients. Can be retrieved with getKeyIds()
+ * @param callback
+ * Callback where to return results
+ */
+ oneway void encrypt(in OpenPgpData input, in OpenPgpData output, in long[] keyIds, in IOpenPgpCallback callback);
+
+ /**
+ * Sign then encrypt
+ *
+ * After successful signing and encryption, callback's onSuccess will contain the resulting output.
+ *
+ * @param input
+ * OpenPgpData object containing String, byte[], ParcelFileDescriptor, or Uri
+ * @param output
+ * Request output format by defining OpenPgpData object
+ *
+ * new OpenPgpData(OpenPgpData.TYPE_STRING)
+ * Returns as String
+ * (OpenPGP Radix-64, 33 percent overhead compared to binary, see http://tools.ietf.org/html/rfc4880#page-53)
+ * new OpenPgpData(OpenPgpData.TYPE_BYTE_ARRAY)
+ * Returns as byte[]
+ * new OpenPgpData(uri)
+ * Writes output to given Uri
+ * new OpenPgpData(fileDescriptor)
+ * Writes output to given ParcelFileDescriptor
+ * @param keyIds
+ * Key Ids of recipients. Can be retrieved with getKeyIds()
+ * @param callback
+ * Callback where to return results
+ */
+ oneway void signAndEncrypt(in OpenPgpData input, in OpenPgpData output, in long[] keyIds, in IOpenPgpCallback callback);
+
+ /**
+ * Decrypts and verifies given input bytes. This methods handles encrypted-only, signed-and-encrypted,
+ * and also signed-only input.
+ *
+ * After successful decryption/verification, callback's onSuccess will contain the resulting output.
+ * The signatureResult in onSuccess is only non-null if signed-and-encrypted or signed-only inputBytes were given.
+ *
+ * @param input
+ * OpenPgpData object containing String, byte[], ParcelFileDescriptor, or Uri
+ * @param output
+ * Request output format by defining OpenPgpData object
+ *
+ * new OpenPgpData(OpenPgpData.TYPE_STRING)
+ * Returns as String
+ * (OpenPGP Radix-64, 33 percent overhead compared to binary, see http://tools.ietf.org/html/rfc4880#page-53)
+ * new OpenPgpData(OpenPgpData.TYPE_BYTE_ARRAY)
+ * Returns as byte[]
+ * new OpenPgpData(uri)
+ * Writes output to given Uri
+ * new OpenPgpData(fileDescriptor)
+ * Writes output to given ParcelFileDescriptor
+ * @param callback
+ * Callback where to return results
+ */
+ oneway void decryptAndVerify(in OpenPgpData input, in OpenPgpData output, in IOpenPgpCallback callback);
+
+ /**
+ * Get available key ids based on given user ids
+ *
+ * @param ids
+ * User Ids (emails) of recipients OR key ids
+ * @param allowUserInteraction
+ * Enable user interaction to lookup and import unknown keys
+ * @param callback
+ * Callback where to return results (different type than callback in other functions!)
+ */
+ oneway void getKeyIds(in String[] ids, in boolean allowUserInteraction, in IOpenPgpKeyIdsCallback callback);
+
+} \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/aidl/org/openintents/openpgp/OpenPgpData.aidl b/OpenPGP-Keychain/src/main/aidl/org/openintents/openpgp/OpenPgpData.aidl
new file mode 100644
index 000000000..3711e4fb4
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/aidl/org/openintents/openpgp/OpenPgpData.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2013 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.openintents.openpgp;
+
+// Declare OpenPgpData so AIDL can find it and knows that it implements the parcelable protocol.
+parcelable OpenPgpData; \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/aidl/org/openintents/openpgp/OpenPgpError.aidl b/OpenPGP-Keychain/src/main/aidl/org/openintents/openpgp/OpenPgpError.aidl
new file mode 100644
index 000000000..7a6bed1e6
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/aidl/org/openintents/openpgp/OpenPgpError.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2013 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.openintents.openpgp;
+
+// Declare OpenPgpError so AIDL can find it and knows that it implements the parcelable protocol.
+parcelable OpenPgpError; \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/aidl/org/openintents/openpgp/OpenPgpSignatureResult.aidl b/OpenPGP-Keychain/src/main/aidl/org/openintents/openpgp/OpenPgpSignatureResult.aidl
new file mode 100644
index 000000000..e246792d0
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/aidl/org/openintents/openpgp/OpenPgpSignatureResult.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2013 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.openintents.openpgp;
+
+// Declare OpenPgpSignatureResult so AIDL can find it and knows that it implements the parcelable protocol.
+parcelable OpenPgpSignatureResult; \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/aidl/org/sufficientlysecure/keychain/service/remote/IExtendedApiCallback.aidl b/OpenPGP-Keychain/src/main/aidl/org/sufficientlysecure/keychain/service/remote/IExtendedApiCallback.aidl
new file mode 100644
index 000000000..f69f66fd7
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/aidl/org/sufficientlysecure/keychain/service/remote/IExtendedApiCallback.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2013 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.service.remote;
+
+interface IExtendedApiCallback {
+
+ oneway void onSuccess(in byte[] outputBytes);
+
+ oneway void onError(in String error);
+} \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/aidl/org/sufficientlysecure/keychain/service/remote/IExtendedApiService.aidl b/OpenPGP-Keychain/src/main/aidl/org/sufficientlysecure/keychain/service/remote/IExtendedApiService.aidl
new file mode 100644
index 000000000..669bd31b5
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/aidl/org/sufficientlysecure/keychain/service/remote/IExtendedApiService.aidl
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2013 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.service.remote;
+
+import org.sufficientlysecure.keychain.service.remote.IExtendedApiCallback;
+
+/**
+ * All methods are oneway, which means they are asynchronous and non-blocking.
+ * Results are returned to the callback, which has to be implemented on client side.
+ */
+interface IExtendedApiService {
+
+ /**
+ * Symmetric Encrypt
+ *
+ * @param inputBytes
+ * Byte array you want to encrypt
+ * @param passphrase
+ * symmetric passhprase
+ * @param callback
+ * Callback where to return results
+ */
+ oneway void encrypt(in byte[] inputBytes, in String passphrase, in IExtendedApiCallback callback);
+
+ /**
+ * Generates self signed X509 certificate signed by OpenPGP private key (from app settings)
+ *
+ * @param subjAltNameURI
+ * @param callback
+ * Callback where to return results
+ */
+ oneway void selfSignedX509Cert(in String subjAltNameURI, in IExtendedApiCallback callback);
+
+} \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/assets/fontawesome-webfont.ttf b/OpenPGP-Keychain/src/main/assets/fontawesome-webfont.ttf
new file mode 100644
index 000000000..7ec2e1de8
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/assets/fontawesome-webfont.ttf
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/java/org/openintents/openpgp/OpenPgpConstants.java b/OpenPGP-Keychain/src/main/java/org/openintents/openpgp/OpenPgpConstants.java
new file mode 100644
index 000000000..b1ca1bfe6
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/openintents/openpgp/OpenPgpConstants.java
@@ -0,0 +1,10 @@
+package org.openintents.openpgp;
+
+public class OpenPgpConstants {
+
+ public static final String TAG = "OpenPgp API";
+
+ public static final int REQUIRED_API_VERSION = 1;
+ public static final String SERVICE_INTENT = "org.openintents.openpgp.IOpenPgpService";
+
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/openintents/openpgp/OpenPgpData.java b/OpenPGP-Keychain/src/main/java/org/openintents/openpgp/OpenPgpData.java
new file mode 100644
index 000000000..6615c2146
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/openintents/openpgp/OpenPgpData.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2013 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.openintents.openpgp;
+
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+
+public class OpenPgpData implements Parcelable {
+ public static final int TYPE_STRING = 0;
+ public static final int TYPE_BYTE_ARRAY = 1;
+ public static final int TYPE_FILE_DESCRIPTOR = 2;
+ public static final int TYPE_URI = 3;
+
+ int type;
+
+ String string;
+ byte[] bytes = new byte[0];
+ ParcelFileDescriptor fileDescriptor;
+ Uri uri;
+
+ public int getType() {
+ return type;
+ }
+
+ public String getString() {
+ return string;
+ }
+
+ public byte[] getBytes() {
+ return bytes;
+ }
+
+ public ParcelFileDescriptor getFileDescriptor() {
+ return fileDescriptor;
+ }
+
+ public Uri getUri() {
+ return uri;
+ }
+
+ public OpenPgpData() {
+
+ }
+
+ /**
+ * Not a real constructor. This can be used to define requested output type.
+ *
+ * @param type
+ */
+ public OpenPgpData(int type) {
+ this.type = type;
+ }
+
+ public OpenPgpData(String string) {
+ this.string = string;
+ this.type = TYPE_STRING;
+ }
+
+ public OpenPgpData(byte[] bytes) {
+ this.bytes = bytes;
+ this.type = TYPE_BYTE_ARRAY;
+ }
+
+ public OpenPgpData(ParcelFileDescriptor fileDescriptor) {
+ this.fileDescriptor = fileDescriptor;
+ this.type = TYPE_FILE_DESCRIPTOR;
+ }
+
+ public OpenPgpData(Uri uri) {
+ this.uri = uri;
+ this.type = TYPE_URI;
+ }
+
+ public OpenPgpData(OpenPgpData b) {
+ this.string = b.string;
+ this.bytes = b.bytes;
+ this.fileDescriptor = b.fileDescriptor;
+ this.uri = b.uri;
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(type);
+ dest.writeString(string);
+ dest.writeInt(bytes.length);
+ dest.writeByteArray(bytes);
+ dest.writeParcelable(fileDescriptor, 0);
+ dest.writeParcelable(uri, 0);
+ }
+
+ public static final Creator<OpenPgpData> CREATOR = new Creator<OpenPgpData>() {
+ public OpenPgpData createFromParcel(final Parcel source) {
+ OpenPgpData vr = new OpenPgpData();
+ vr.type = source.readInt();
+ vr.string = source.readString();
+ vr.bytes = new byte[source.readInt()];
+ source.readByteArray(vr.bytes);
+ vr.fileDescriptor = source.readParcelable(ParcelFileDescriptor.class.getClassLoader());
+ vr.fileDescriptor = source.readParcelable(Uri.class.getClassLoader());
+ return vr;
+ }
+
+ public OpenPgpData[] newArray(final int size) {
+ return new OpenPgpData[size];
+ }
+ };
+
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/openintents/openpgp/OpenPgpError.java b/OpenPGP-Keychain/src/main/java/org/openintents/openpgp/OpenPgpError.java
new file mode 100644
index 000000000..f108d3169
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/openintents/openpgp/OpenPgpError.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2013 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.openintents.openpgp;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public class OpenPgpError implements Parcelable {
+ public static final int GENERIC_ERROR = 0;
+ public static final int NO_OR_WRONG_PASSPHRASE = 1;
+ public static final int NO_USER_IDS = 2;
+ public static final int USER_INTERACTION_REQUIRED = 3;
+
+ int errorId;
+ String message;
+
+ public OpenPgpError() {
+ }
+
+ public OpenPgpError(int errorId, String message) {
+ this.errorId = errorId;
+ this.message = message;
+ }
+
+ public OpenPgpError(OpenPgpError b) {
+ this.errorId = b.errorId;
+ this.message = b.message;
+ }
+
+ public int getErrorId() {
+ return errorId;
+ }
+
+ public void setErrorId(int errorId) {
+ this.errorId = errorId;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(errorId);
+ dest.writeString(message);
+ }
+
+ public static final Creator<OpenPgpError> CREATOR = new Creator<OpenPgpError>() {
+ public OpenPgpError createFromParcel(final Parcel source) {
+ OpenPgpError error = new OpenPgpError();
+ error.errorId = source.readInt();
+ error.message = source.readString();
+ return error;
+ }
+
+ public OpenPgpError[] newArray(final int size) {
+ return new OpenPgpError[size];
+ }
+ };
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/openintents/openpgp/OpenPgpHelper.java b/OpenPGP-Keychain/src/main/java/org/openintents/openpgp/OpenPgpHelper.java
new file mode 100644
index 000000000..7305c47ce
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/openintents/openpgp/OpenPgpHelper.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2013 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.openintents.openpgp;
+
+import java.util.List;
+import java.util.regex.Pattern;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+
+public class OpenPgpHelper {
+ private Context context;
+
+ public static Pattern PGP_MESSAGE = Pattern.compile(
+ ".*?(-----BEGIN PGP MESSAGE-----.*?-----END PGP MESSAGE-----).*", Pattern.DOTALL);
+
+ public static Pattern PGP_SIGNED_MESSAGE = Pattern
+ .compile(
+ ".*?(-----BEGIN PGP SIGNED MESSAGE-----.*?-----BEGIN PGP SIGNATURE-----.*?-----END PGP SIGNATURE-----).*",
+ Pattern.DOTALL);
+
+ public OpenPgpHelper(Context context) {
+ super();
+ this.context = context;
+ }
+
+ public boolean isAvailable() {
+ Intent intent = new Intent(OpenPgpConstants.SERVICE_INTENT);
+ List<ResolveInfo> resInfo = context.getPackageManager().queryIntentServices(intent, 0);
+ if (!resInfo.isEmpty()) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/openintents/openpgp/OpenPgpListPreference.java b/OpenPGP-Keychain/src/main/java/org/openintents/openpgp/OpenPgpListPreference.java
new file mode 100644
index 000000000..4ddd97485
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/openintents/openpgp/OpenPgpListPreference.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2013 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.openintents.openpgp;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import android.app.AlertDialog.Builder;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.graphics.drawable.Drawable;
+import android.preference.DialogPreference;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ListAdapter;
+import android.widget.TextView;
+
+public class OpenPgpListPreference extends DialogPreference {
+ ArrayList<OpenPgpProviderEntry> mProviderList = new ArrayList<OpenPgpProviderEntry>();
+ private String mSelectedPackage;
+
+ public OpenPgpListPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ List<ResolveInfo> resInfo = context.getPackageManager().queryIntentServices(
+ new Intent(OpenPgpConstants.SERVICE_INTENT), PackageManager.GET_META_DATA);
+ if (!resInfo.isEmpty()) {
+ for (ResolveInfo resolveInfo : resInfo) {
+ if (resolveInfo.serviceInfo == null)
+ continue;
+
+ String packageName = resolveInfo.serviceInfo.packageName;
+ String simpleName = String.valueOf(resolveInfo.serviceInfo.loadLabel(context
+ .getPackageManager()));
+ Drawable icon = resolveInfo.serviceInfo.loadIcon(context.getPackageManager());
+
+ // get api version
+ ServiceInfo si = resolveInfo.serviceInfo;
+ int apiVersion = si.metaData.getInt("api_version");
+
+ mProviderList.add(new OpenPgpProviderEntry(packageName, simpleName, icon,
+ apiVersion));
+ }
+ }
+ }
+
+ public OpenPgpListPreference(Context context) {
+ this(context, null);
+ }
+
+ /**
+ * Can be used to add "no selection"
+ *
+ * @param packageName
+ * @param simpleName
+ * @param icon
+ */
+ public void addProvider(int position, String packageName, String simpleName, Drawable icon,
+ int apiVersion) {
+ mProviderList.add(position, new OpenPgpProviderEntry(packageName, simpleName, icon,
+ apiVersion));
+ }
+
+ @Override
+ protected void onPrepareDialogBuilder(Builder builder) {
+ // Init ArrayAdapter with OpenPGP Providers
+ ListAdapter adapter = new ArrayAdapter<OpenPgpProviderEntry>(getContext(),
+ android.R.layout.select_dialog_singlechoice, android.R.id.text1, mProviderList) {
+ public View getView(int position, View convertView, ViewGroup parent) {
+ // User super class to create the View
+ View v = super.getView(position, convertView, parent);
+ TextView tv = (TextView) v.findViewById(android.R.id.text1);
+
+ // Put the image on the TextView
+ tv.setCompoundDrawablesWithIntrinsicBounds(mProviderList.get(position).icon, null,
+ null, null);
+
+ // Add margin between image and text (support various screen densities)
+ int dp10 = (int) (10 * getContext().getResources().getDisplayMetrics().density + 0.5f);
+ tv.setCompoundDrawablePadding(dp10);
+
+ // disable if it has the wrong api_version
+ if (mProviderList.get(position).apiVersion == OpenPgpConstants.REQUIRED_API_VERSION) {
+ tv.setEnabled(true);
+ } else {
+ tv.setEnabled(false);
+ tv.setText(tv.getText() + " (API v" + mProviderList.get(position).apiVersion
+ + ", needs v" + OpenPgpConstants.REQUIRED_API_VERSION + ")");
+ }
+
+ return v;
+ }
+ };
+
+ builder.setSingleChoiceItems(adapter, getIndexOfProviderList(getValue()),
+ new DialogInterface.OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ mSelectedPackage = mProviderList.get(which).packageName;
+
+ /*
+ * Clicking on an item simulates the positive button click, and dismisses
+ * the dialog.
+ */
+ OpenPgpListPreference.this.onClick(dialog, DialogInterface.BUTTON_POSITIVE);
+ dialog.dismiss();
+ }
+ });
+
+ /*
+ * The typical interaction for list-based dialogs is to have click-on-an-item dismiss the
+ * dialog instead of the user having to press 'Ok'.
+ */
+ builder.setPositiveButton(null, null);
+ }
+
+ @Override
+ protected void onDialogClosed(boolean positiveResult) {
+ super.onDialogClosed(positiveResult);
+
+ if (positiveResult && (mSelectedPackage != null)) {
+ if (callChangeListener(mSelectedPackage)) {
+ setValue(mSelectedPackage);
+ }
+ }
+ }
+
+ private int getIndexOfProviderList(String packageName) {
+ for (OpenPgpProviderEntry app : mProviderList) {
+ if (app.packageName.equals(packageName)) {
+ return mProviderList.indexOf(app);
+ }
+ }
+
+ return -1;
+ }
+
+ public void setValue(String packageName) {
+ mSelectedPackage = packageName;
+ persistString(packageName);
+ }
+
+ public String getValue() {
+ return mSelectedPackage;
+ }
+
+ public String getEntry() {
+ return getEntryByValue(mSelectedPackage);
+ }
+
+ public String getEntryByValue(String packageName) {
+ for (OpenPgpProviderEntry app : mProviderList) {
+ if (app.packageName.equals(packageName)) {
+ return app.simpleName;
+ }
+ }
+
+ return null;
+ }
+
+ private static class OpenPgpProviderEntry {
+ private String packageName;
+ private String simpleName;
+ private Drawable icon;
+ private int apiVersion;
+
+ public OpenPgpProviderEntry(String packageName, String simpleName, Drawable icon,
+ int apiVersion) {
+ this.packageName = packageName;
+ this.simpleName = simpleName;
+ this.icon = icon;
+ this.apiVersion = apiVersion;
+ }
+
+ @Override
+ public String toString() {
+ return simpleName;
+ }
+ }
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/openintents/openpgp/OpenPgpServiceConnection.java b/OpenPGP-Keychain/src/main/java/org/openintents/openpgp/OpenPgpServiceConnection.java
new file mode 100644
index 000000000..f7ba06aaf
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/openintents/openpgp/OpenPgpServiceConnection.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2013 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.openintents.openpgp;
+
+import org.openintents.openpgp.IOpenPgpService;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.util.Log;
+
+public class OpenPgpServiceConnection {
+ private Context mApplicationContext;
+
+ private IOpenPgpService mService;
+ private boolean mBound;
+ private String mCryptoProviderPackageName;
+
+ public OpenPgpServiceConnection(Context context, String cryptoProviderPackageName) {
+ this.mApplicationContext = context.getApplicationContext();
+ this.mCryptoProviderPackageName = cryptoProviderPackageName;
+ }
+
+ public IOpenPgpService getService() {
+ return mService;
+ }
+
+ public boolean isBound() {
+ return mBound;
+ }
+
+ private ServiceConnection mCryptoServiceConnection = new ServiceConnection() {
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ mService = IOpenPgpService.Stub.asInterface(service);
+ Log.d(OpenPgpConstants.TAG, "connected to service");
+ mBound = true;
+ }
+
+ public void onServiceDisconnected(ComponentName name) {
+ mService = null;
+ Log.d(OpenPgpConstants.TAG, "disconnected from service");
+ mBound = false;
+ }
+ };
+
+ /**
+ * If not already bound, bind!
+ *
+ * @return
+ */
+ public boolean bindToService() {
+ if (mService == null && !mBound) { // if not already connected
+ try {
+ Log.d(OpenPgpConstants.TAG, "not bound yet");
+
+ Intent serviceIntent = new Intent();
+ serviceIntent.setAction(IOpenPgpService.class.getName());
+ serviceIntent.setPackage(mCryptoProviderPackageName);
+ mApplicationContext.bindService(serviceIntent, mCryptoServiceConnection,
+ Context.BIND_AUTO_CREATE);
+
+ return true;
+ } catch (Exception e) {
+ Log.d(OpenPgpConstants.TAG, "Exception on binding", e);
+ return false;
+ }
+ } else {
+ Log.d(OpenPgpConstants.TAG, "already bound");
+ return true;
+ }
+ }
+
+ public void unbindFromService() {
+ mApplicationContext.unbindService(mCryptoServiceConnection);
+ }
+
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/openintents/openpgp/OpenPgpSignatureResult.java b/OpenPGP-Keychain/src/main/java/org/openintents/openpgp/OpenPgpSignatureResult.java
new file mode 100644
index 000000000..829f8f8cf
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/openintents/openpgp/OpenPgpSignatureResult.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2013 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.openintents.openpgp;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public class OpenPgpSignatureResult implements Parcelable {
+ // generic error on signature verification
+ public static final int SIGNATURE_ERROR = 0;
+ // successfully verified signature, with trusted public key
+ public static final int SIGNATURE_SUCCESS_TRUSTED = 1;
+ // no public key was found for this signature verification
+ // you can retrieve the key with
+ // getKeys(new String[] {String.valueOf(signatureResult.getKeyId)}, true, callback)
+ public static final int SIGNATURE_UNKNOWN_PUB_KEY = 2;
+ // successfully verified signature, but with untrusted public key
+ public static final int SIGNATURE_SUCCESS_UNTRUSTED = 3;
+
+ int status;
+ boolean signatureOnly;
+ String userId;
+ long keyId;
+
+ public int getStatus() {
+ return status;
+ }
+
+ public boolean isSignatureOnly() {
+ return signatureOnly;
+ }
+
+ public String getUserId() {
+ return userId;
+ }
+
+ public long getKeyId() {
+ return keyId;
+ }
+
+ public OpenPgpSignatureResult() {
+
+ }
+
+ public OpenPgpSignatureResult(int signatureStatus, String signatureUserId,
+ boolean signatureOnly, long keyId) {
+ this.status = signatureStatus;
+ this.signatureOnly = signatureOnly;
+ this.userId = signatureUserId;
+ this.keyId = keyId;
+ }
+
+ public OpenPgpSignatureResult(OpenPgpSignatureResult b) {
+ this.status = b.status;
+ this.userId = b.userId;
+ this.signatureOnly = b.signatureOnly;
+ this.keyId = b.keyId;
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(status);
+ dest.writeByte((byte) (signatureOnly ? 1 : 0));
+ dest.writeString(userId);
+ dest.writeLong(keyId);
+ }
+
+ public static final Creator<OpenPgpSignatureResult> CREATOR = new Creator<OpenPgpSignatureResult>() {
+ public OpenPgpSignatureResult createFromParcel(final Parcel source) {
+ OpenPgpSignatureResult vr = new OpenPgpSignatureResult();
+ vr.status = source.readInt();
+ vr.signatureOnly = source.readByte() == 1;
+ vr.userId = source.readString();
+ vr.keyId = source.readLong();
+ return vr;
+ }
+
+ public OpenPgpSignatureResult[] newArray(final int size) {
+ return new OpenPgpSignatureResult[size];
+ }
+ };
+
+ @Override
+ public String toString() {
+ String out = new String();
+ out += "\nstatus: " + status;
+ out += "\nuserId: " + userId;
+ out += "\nsignatureOnly: " + signatureOnly;
+ out += "\nkeyId: " + keyId;
+ return out;
+ }
+
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/Constants.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/Constants.java
new file mode 100644
index 000000000..74b407cd4
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/Constants.java
@@ -0,0 +1,61 @@
+/*
+ * 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.jce.provider.BouncyCastleProvider;
+
+import android.os.Environment;
+
+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";
+
+ // 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()
+ + "/OpenPGP-Keychain";
+ }
+
+ 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_ARMOUR = "defaultAsciiArmour";
+ public static final String DEFAULT_MESSAGE_COMPRESSION = "defaultMessageCompression";
+ public static final String DEFAULT_FILE_COMPRESSION = "defaultFileCompression";
+ public static final String PASS_PHRASE_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";
+ }
+
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/Id.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/Id.java
new file mode 100644
index 000000000..22f30cb1f
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/Id.java
@@ -0,0 +1,190 @@
+/*
+ * 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_pass_phrase = 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 pass_phrase = 0x21070001;
+ public static final int encrypting = 0x21070002;
+ public static final int decrypting = 0x21070003;
+ public static final int new_pass_phrase = 0x21070004;
+ public static final int pass_phrases_do_not_match = 0x21070005;
+ public static final int no_pass_phrase = 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 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/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java
new file mode 100644
index 000000000..4a25f2df1
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java
@@ -0,0 +1,74 @@
+/*
+ * 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 java.io.File;
+import java.security.Provider;
+import java.security.Security;
+
+import org.spongycastle.jce.provider.BouncyCastleProvider;
+import org.sufficientlysecure.keychain.util.Log;
+import org.sufficientlysecure.keychain.util.PRNGFixes;
+
+import android.app.Application;
+import android.os.Environment;
+
+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/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/compatibility/ClipboardReflection.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/compatibility/ClipboardReflection.java
new file mode 100644
index 000000000..704448e47
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/compatibility/ClipboardReflection.java
@@ -0,0 +1,98 @@
+/*
+ * 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 java.lang.reflect.Method;
+
+import android.content.Context;
+
+import org.sufficientlysecure.keychain.util.Log;
+
+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("ProjectsException", "There was an error copying the text to the clipboard: "
+ + e.getMessage());
+ }
+ }
+
+ /**
+ * Wrapper around ClipboardManager based on Android version using Reflection API
+ *
+ * @param context
+ * @param text
+ */
+ 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("ProjectsException", "There was an error getting the text from the clipboard: "
+ + e.getMessage());
+
+ return null;
+ }
+ }
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/compatibility/DialogFragmentWorkaround.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/compatibility/DialogFragmentWorkaround.java
new file mode 100644
index 000000000..a209f16cf
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/compatibility/DialogFragmentWorkaround.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.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/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/compatibility/ListFragmentWorkaround.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/compatibility/ListFragmentWorkaround.java
new file mode 100644
index 000000000..e4aa6d229
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/compatibility/ListFragmentWorkaround.java
@@ -0,0 +1,38 @@
+/*
+ * 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.view.View;
+import android.widget.ListView;
+
+import com.actionbarsherlock.app.SherlockListFragment;
+
+/**
+ * Bug on Android >= 4.1
+ *
+ * http://code.google.com/p/android/issues/detail?id=35885
+ *
+ * Items are not checked in layout
+ */
+public class ListFragmentWorkaround extends SherlockListFragment {
+
+ @Override
+ public void onListItemClick(ListView l, View v, int position, long id) {
+ l.setItemChecked(position, l.isItemChecked(position));
+ }
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/ActionBarHelper.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/ActionBarHelper.java
new file mode 100644
index 000000000..fee120273
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/ActionBarHelper.java
@@ -0,0 +1,117 @@
+/*
+ * 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 org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.util.Log;
+
+import android.app.Activity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.View.OnClickListener;
+import android.widget.TextView;
+
+import com.actionbarsherlock.app.ActionBar;
+import com.actionbarsherlock.app.SherlockFragmentActivity;
+
+public class ActionBarHelper {
+
+ /**
+ * Set actionbar without home button if called from another app
+ *
+ * @param activity
+ */
+ public static void setBackButton(SherlockFragmentActivity activity) {
+ // set actionbar without home button if called from another app
+ 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 doneText
+ * @param doneOnClickListener
+ * @param cancelText
+ * @param cancelOnClickListener
+ */
+ public static void setDoneCancelView(ActionBar actionBar, int doneText,
+ OnClickListener doneOnClickListener, int cancelText,
+ OnClickListener cancelOnClickListener) {
+
+ // Inflate a "Done"/"Cancel" 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) customActionBarView.findViewById(R.id.actionbar_done_text)).setText(doneText);
+ customActionBarView.findViewById(R.id.actionbar_done).setOnClickListener(
+ doneOnClickListener);
+ ((TextView) customActionBarView.findViewById(R.id.actionbar_cancel_text))
+ .setText(cancelText);
+ customActionBarView.findViewById(R.id.actionbar_cancel).setOnClickListener(
+ cancelOnClickListener);
+
+ // 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 doneText
+ * @param doneOnClickListener
+ */
+ public static void setDoneView(ActionBar actionBar, int doneText,
+ OnClickListener doneOnClickListener) {
+ // 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) customActionBarView.findViewById(R.id.actionbar_done_text)).setText(doneText);
+ customActionBarView.findViewById(R.id.actionbar_done).setOnClickListener(
+ doneOnClickListener);
+
+ // 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/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/ExportHelper.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/ExportHelper.java
new file mode 100644
index 000000000..9b817cb50
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/ExportHelper.java
@@ -0,0 +1,184 @@
+/*
+ * 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 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;
+
+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.widget.Toast;
+
+import com.actionbarsherlock.app.SherlockFragmentActivity;
+
+public class ExportHelper {
+ protected FileDialogFragment mFileDialog;
+ protected String mExportFilename;
+
+ SherlockFragmentActivity activity;
+
+ public ExportHelper(SherlockFragmentActivity activity) {
+ super();
+ this.activity = activity;
+ }
+
+ public void deleteKey(Uri dataUri, final int keyType, Handler deleteHandler) {
+ long keyRingRowId = Long.valueOf(dataUri.getLastPathSegment());
+
+ // Create a new Messenger for the communication back
+ Messenger messenger = new Messenger(deleteHandler);
+
+ DeleteKeyDialogFragment deleteKeyDialog = DeleteKeyDialogFragment.newInstance(messenger,
+ new long[] { keyRingRowId }, keyType);
+
+ deleteKeyDialog.show(activity.getSupportFragmentManager(), "deleteKeyDialog");
+ }
+
+ /**
+ * Show dialog where to export keys
+ *
+ * @param keyRingMasterKeyId
+ * if -1 export all keys
+ */
+ public void showExportKeysDialog(final Uri dataUri, final int keyType,
+ final String exportFilename) {
+ 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(dataUri, keyType);
+ }
+ }
+ };
+
+ // 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 (dataUri == null) {
+ // export all keys
+ title = activity.getString(R.string.title_export_keys);
+ } else {
+ // export only key specified at data uri
+ title = activity.getString(R.string.title_export_key);
+ }
+
+ String message = null;
+ if (keyType == Id.type.public_key) {
+ message = activity.getString(R.string.specify_file_to_export_to);
+ } else {
+ message = activity.getString(R.string.specify_file_to_export_secret_keys_to);
+ }
+
+ mFileDialog = FileDialogFragment.newInstance(messenger, title, message,
+ exportFilename, null);
+
+ mFileDialog.show(activity.getSupportFragmentManager(), "fileDialog");
+ }
+ });
+ }
+
+ /**
+ * Export keys
+ *
+ * @param keyRingMasterKeyId
+ * if -1 export all keys
+ */
+ public void exportKeys(Uri dataUri, int keyType) {
+ Log.d(Constants.TAG, "exportKeys started");
+
+ // Send all information needed to service to export key in other thread
+ Intent intent = new Intent(activity, KeychainIntentService.class);
+
+ intent.setAction(KeychainIntentService.ACTION_EXPORT_KEYRING);
+
+ // fill values for this action
+ Bundle data = new Bundle();
+
+ data.putString(KeychainIntentService.EXPORT_FILENAME, mExportFilename);
+ data.putInt(KeychainIntentService.EXPORT_KEY_TYPE, keyType);
+
+ if (dataUri == null) {
+ data.putBoolean(KeychainIntentService.EXPORT_ALL, true);
+ } else {
+ // TODO: put data uri into service???
+ long keyRingMasterKeyId = ProviderHelper.getMasterKeyId(activity, dataUri);
+
+ data.putLong(KeychainIntentService.EXPORT_KEY_RING_MASTER_KEY_ID, keyRingMasterKeyId);
+ }
+
+ intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
+
+ // Message is received after exporting is done in ApgService
+ KeychainIntentServiceHandler exportHandler = new KeychainIntentServiceHandler(activity,
+ R.string.progress_exporting, ProgressDialog.STYLE_HORIZONTAL) {
+ public void handleMessage(Message message) {
+ // handle messages by standard ApgHandler 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 = activity.getString(R.string.key_exported);
+ } else if (exported > 0) {
+ toastMessage = activity.getString(R.string.keys_exported, exported);
+ } else {
+ toastMessage = activity.getString(R.string.no_keys_exported);
+ }
+ Toast.makeText(activity, 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(activity);
+
+ // start service with intent
+ activity.startService(intent);
+ }
+
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/FileHelper.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/FileHelper.java
new file mode 100644
index 000000000..ec56fe912
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/FileHelper.java
@@ -0,0 +1,138 @@
+/*
+ * 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 org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.util.Log;
+
+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;
+
+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.
+ *
+ * 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 column_index = cursor.getColumnIndexOrThrow("_data");
+ if (cursor.moveToFirst()) {
+ return cursor.getString(column_index);
+ }
+ } catch (Exception e) {
+ // Eat it
+ }
+ }
+
+ else if ("file".equalsIgnoreCase(uri.getScheme())) {
+ return uri.getPath();
+ }
+
+ return null;
+ }
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/OtherHelper.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/OtherHelper.java
new file mode 100644
index 000000000..9f3cd8e88
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/OtherHelper.java
@@ -0,0 +1,82 @@
+/*
+ * 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 java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.Iterator;
+import java.util.Set;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.util.Log;
+
+import android.os.Bundle;
+
+public class OtherHelper {
+
+ /**
+ * Return the number if days between two dates
+ *
+ * @param first
+ * @param second
+ * @return number of days
+ */
+ public static long getNumDaysBetween(GregorianCalendar first, GregorianCalendar second) {
+ GregorianCalendar tmp = new GregorianCalendar();
+ tmp.setTime(first.getTime());
+ long numDays = (second.getTimeInMillis() - first.getTimeInMillis()) / 1000 / 86400;
+ tmp.add(Calendar.DAY_OF_MONTH, (int) numDays);
+ while (tmp.before(second)) {
+ tmp.add(Calendar.DAY_OF_MONTH, 1);
+ ++numDays;
+ }
+ return numDays;
+ }
+
+ /**
+ * 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");
+ }
+ }
+ }
+
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/Preferences.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/Preferences.java
new file mode 100644
index 000000000..493f25707
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/Preferences.java
@@ -0,0 +1,172 @@
+/*
+ * 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 org.spongycastle.bcpg.HashAlgorithmTags;
+import org.spongycastle.openpgp.PGPEncryptedData;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.Id;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import java.util.Vector;
+
+/**
+ * Singleton Implementation of a Preference Helper
+ */
+public class Preferences {
+ private static Preferences mPreferences;
+ private SharedPreferences mSharedPreferences;
+
+ public static synchronized Preferences getPreferences(Context context) {
+ return getPreferences(context, false);
+ }
+
+ public static synchronized Preferences getPreferences(Context context, boolean force_new) {
+ if (mPreferences == null || force_new) {
+ mPreferences = new Preferences(context);
+ }
+ return mPreferences;
+ }
+
+ 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.PASS_PHRASE_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.PASS_PHRASE_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 getDefaultAsciiArmour() {
+ return mSharedPreferences.getBoolean(Constants.pref.DEFAULT_ASCII_ARMOUR, false);
+ }
+
+ public void setDefaultAsciiArmour(boolean value) {
+ SharedPreferences.Editor editor = mSharedPreferences.edit();
+ editor.putBoolean(Constants.pref.DEFAULT_ASCII_ARMOUR, 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 (int i = 0; i < chunks.length; ++i) {
+ String tmp = chunks[i].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 (int i = 0; i < value.length; ++i) {
+ String tmp = value[i].trim();
+ if (tmp.length() == 0) {
+ continue;
+ }
+ if (!"".equals(rawData)) {
+ rawData += ",";
+ }
+ rawData += tmp;
+ }
+ editor.putString(Constants.pref.KEY_SERVERS, rawData);
+ editor.commit();
+ }
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpConversionHelper.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpConversionHelper.java
new file mode 100644
index 000000000..e406a142e
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpConversionHelper.java
@@ -0,0 +1,139 @@
+/*
+ * 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 java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import org.spongycastle.openpgp.PGPKeyRing;
+import org.spongycastle.openpgp.PGPObjectFactory;
+import org.spongycastle.openpgp.PGPSecretKey;
+import org.spongycastle.openpgp.PGPSecretKeyRing;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.util.Log;
+
+
+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) {
+ PGPSecretKeyRing keyRing = (PGPSecretKeyRing) BytesToPGPKeyRing(keysBytes);
+ ArrayList<PGPSecretKey> keys = new ArrayList<PGPSecretKey>();
+
+ @SuppressWarnings("unchecked")
+ Iterator<PGPSecretKey> itr = keyRing.getSecretKeys();
+ while (itr.hasNext()) {
+ keys.add(itr.next());
+ }
+
+ return keys;
+ }
+
+ /**
+ * Convert from byte[] to PGPSecretKey
+ *
+ * Singles keys are encoded as keyRings with one single key in it by Bouncy Castle
+ *
+ * @param keysBytes
+ * @return
+ */
+ public static PGPSecretKey BytesToPGPSecretKey(byte[] keyBytes) {
+ PGPSecretKey key = BytesToPGPSecretKeyList(keyBytes).get(0);
+
+ return key;
+ }
+
+ /**
+ * 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 keysBytes
+ * @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 keysBytes
+ * @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/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java
new file mode 100644
index 000000000..7ac904d89
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java
@@ -0,0 +1,219 @@
+/*
+ * 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 java.io.File;
+import java.io.FileNotFoundException;
+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;
+
+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 android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+
+public class PgpHelper {
+
+ public static Pattern PGP_MESSAGE = Pattern.compile(
+ ".*?(-----BEGIN PGP MESSAGE-----.*?-----END PGP MESSAGE-----).*", Pattern.DOTALL);
+
+ public static Pattern PGP_SIGNED_MESSAGE = Pattern
+ .compile(
+ ".*?(-----BEGIN PGP SIGNED MESSAGE-----.*?-----BEGIN PGP SIGNATURE-----.*?-----END PGP SIGNATURE-----).*",
+ Pattern.DOTALL);
+
+ public static 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.getPGPSecretKeyByKeyId(context, pbe.getKeyID());
+ 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.
+ *
+ * TODO: Does this really help on flash storage?
+ *
+ * @param context
+ * @param progress
+ * @param file
+ * @throws FileNotFoundException
+ * @throws IOException
+ */
+ public static void deleteFileSecurely(Context context, ProgressDialogUpdater progress, File file)
+ throws FileNotFoundException, 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/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java
new file mode 100644
index 000000000..0a4806239
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java
@@ -0,0 +1,314 @@
+/*
+ * 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 java.io.BufferedInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+
+import org.spongycastle.bcpg.ArmoredOutputStream;
+import org.spongycastle.openpgp.PGPException;
+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.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.util.HkpKeyServer;
+import org.sufficientlysecure.keychain.util.InputData;
+import org.sufficientlysecure.keychain.util.IterableIterator;
+import org.sufficientlysecure.keychain.util.KeyServer.AddKeyException;
+import org.sufficientlysecure.keychain.util.Log;
+import org.sufficientlysecure.keychain.util.PositionAwareInputStream;
+import org.sufficientlysecure.keychain.util.ProgressDialogUpdater;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Environment;
+
+public class PgpImportExport {
+ private Context mContext;
+ private ProgressDialogUpdater mProgress;
+
+ public PgpImportExport(Context context, ProgressDialogUpdater progress) {
+ super();
+ this.mContext = context;
+ this.mProgress = progress;
+ }
+
+ 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 = new ArmoredOutputStream(bos);
+ try {
+ aos.write(keyring.getEncoded());
+ aos.close();
+
+ String armouredKey = bos.toString("UTF-8");
+ server.add(armouredKey);
+
+ return true;
+ } catch (IOException e) {
+ return false;
+ } catch (AddKeyException e) {
+ // TODO: tell the user?
+ return false;
+ } finally {
+ try {
+ bos.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ /**
+ * Imports keys from given data. If keyIds is given only those are imported
+ *
+ * @param data
+ * @param keyIds
+ * @return
+ * @throws PgpGeneralException
+ * @throws FileNotFoundException
+ * @throws PGPException
+ * @throws IOException
+ */
+ public Bundle importKeyRings(InputData data, ArrayList<Long> keyIds)
+ throws PgpGeneralException, FileNotFoundException, PGPException, IOException {
+ Bundle returnData = new Bundle();
+
+ updateProgress(R.string.progress_importing_secret_keys, 0, 100);
+
+ PositionAwareInputStream progressIn = new PositionAwareInputStream(data.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
+ // armour blocks
+ BufferedInputStream bufferedInput = new BufferedInputStream(progressIn);
+ int newKeys = 0;
+ int oldKeys = 0;
+ int badKeys = 0;
+ try {
+
+ // read all available blocks... (asc files can contain many blocks with BEGIN END)
+ while (bufferedInput.available() > 0) {
+ 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 keyring = (PGPKeyRing) obj;
+
+ int status = Integer.MIN_VALUE; // out of bounds value
+
+ if (keyIds != null) {
+ if (keyIds.contains(keyring.getPublicKey().getKeyID())) {
+ status = storeKeyRingInCache(keyring);
+ } else {
+ Log.d(Constants.TAG, "not selected! key id: "
+ + keyring.getPublicKey().getKeyID());
+ }
+ } else {
+ 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;
+ }
+
+ updateProgress((int) (100 * progressIn.position() / data.getSize()), 100);
+ } else {
+ Log.e(Constants.TAG, "Object not recognized as PGPKeyRing!");
+ }
+ }
+ }
+ } 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);
+
+ updateProgress(R.string.progress_done, 100, 100);
+
+ return returnData;
+ }
+
+ public Bundle exportKeyRings(ArrayList<Long> keyRingMasterKeyIds, int keyType,
+ OutputStream outStream) throws PgpGeneralException, FileNotFoundException,
+ PGPException, IOException {
+ Bundle returnData = new Bundle();
+
+ updateProgress(
+ mContext.getResources().getQuantityString(R.plurals.progress_exporting_key,
+ keyRingMasterKeyIds.size()), 0, 100);
+
+ if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+ throw new PgpGeneralException(
+ mContext.getString(R.string.error_external_storage_not_ready));
+ }
+
+ // export public keyrings...
+ ArmoredOutputStream outPub = new ArmoredOutputStream(outStream);
+ outPub.setHeader("Version", PgpHelper.getFullVersion(mContext));
+
+ int numKeys = 0;
+ for (int i = 0; i < keyRingMasterKeyIds.size(); ++i) {
+ // double the needed time if exporting both public and secret parts
+ if (keyType == Id.type.secret_key) {
+ updateProgress(i * 100 / keyRingMasterKeyIds.size() / 2, 100);
+ } else {
+ updateProgress(i * 100 / keyRingMasterKeyIds.size(), 100);
+ }
+
+ PGPPublicKeyRing publicKeyRing = ProviderHelper.getPGPPublicKeyRingByMasterKeyId(
+ mContext, keyRingMasterKeyIds.get(i));
+
+ if (publicKeyRing != null) {
+ publicKeyRing.encode(outPub);
+ }
+ ++numKeys;
+ }
+ outPub.close();
+
+ // if we export secret keyrings, append all secret parts after the public parts
+ if (keyType == Id.type.secret_key) {
+ ArmoredOutputStream outSec = new ArmoredOutputStream(outStream);
+ outSec.setHeader("Version", PgpHelper.getFullVersion(mContext));
+
+ for (int i = 0; i < keyRingMasterKeyIds.size(); ++i) {
+ updateProgress(i * 100 / keyRingMasterKeyIds.size() / 2, 100);
+
+ PGPSecretKeyRing secretKeyRing = ProviderHelper.getPGPSecretKeyRingByMasterKeyId(
+ mContext, keyRingMasterKeyIds.get(i));
+
+ if (secretKeyRing != null) {
+ secretKeyRing.encode(outSec);
+ }
+ }
+ outSec.close();
+ }
+
+ returnData.putInt(KeychainIntentService.RESULT_EXPORT, numKeys);
+
+ updateProgress(R.string.progress_done, 100, 100);
+
+ return returnData;
+ }
+
+ /**
+ * TODO: implement Id.return_value.updated as status when key already existed
+ *
+ * @param context
+ * @param keyring
+ * @return
+ */
+ @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 (PgpKeyHelper.isSecretKeyPrivateEmpty(testSecretKey)) {
+ // this is bad, something is very wrong...
+ save = false;
+ status = Id.return_value.bad;
+ }
+ }
+ }
+
+ if (save) {
+ ProviderHelper.saveKeyRing(mContext, secretKeyRing);
+ // 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);
+ // 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/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java
new file mode 100644
index 000000000..6a5ce7415
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java
@@ -0,0 +1,563 @@
+/*
+ * 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 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;
+
+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.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 android.content.Context;
+
+public class PgpKeyHelper {
+
+ /**
+ * Returns the last 9 chars of a fingerprint
+ *
+ * @param fingerprint
+ * String containing short or long fingerprint
+ * @return
+ */
+ public static String shortifyFingerprint(String fingerprint) {
+ return fingerprint.substring(41);
+ }
+
+ public static Date getCreationDate(PGPPublicKey key) {
+ return key.getCreationTime();
+ }
+
+ public static Date getCreationDate(PGPSecretKey key) {
+ return key.getPublicKey().getCreationTime();
+ }
+
+ @SuppressWarnings("unchecked")
+ public static PGPPublicKey getMasterKey(PGPPublicKeyRing keyRing) {
+ if (keyRing == null) {
+ return null;
+ }
+ for (PGPPublicKey key : new IterableIterator<PGPPublicKey>(keyRing.getPublicKeys())) {
+ if (key.isMasterKey()) {
+ return key;
+ }
+ }
+
+ return null;
+ }
+
+ @SuppressWarnings("unchecked")
+ public static PGPSecretKey getMasterKey(PGPSecretKeyRing keyRing) {
+ if (keyRing == null) {
+ return null;
+ }
+ for (PGPSecretKey key : new IterableIterator<PGPSecretKey>(keyRing.getSecretKeys())) {
+ if (key.isMasterKey()) {
+ return key;
+ }
+ }
+
+ return null;
+ }
+
+ @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 boolean isExpired(PGPSecretKey key) {
+ return isExpired(key.getPublicKey());
+ }
+
+ 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());
+ Date expiryDate = calendar.getTime();
+
+ return expiryDate;
+ }
+
+ public static Date getExpiryDate(PGPSecretKey key) {
+ return getExpiryDate(key.getPublicKey());
+ }
+
+ public static PGPPublicKey getEncryptPublicKey(Context context, long masterKeyId) {
+ PGPPublicKeyRing keyRing = ProviderHelper.getPGPPublicKeyRingByMasterKeyId(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.getPGPSecretKeyRingByMasterKeyId(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.getPGPSecretKeyRingByMasterKeyId(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.unknown_user_id);
+ }
+ return userId;
+ }
+
+ public static String getMainUserIdSafe(Context context, PGPSecretKey key) {
+ String userId = getMainUserId(key);
+ if (userId == null || userId.equals("")) {
+ userId = context.getString(R.string.unknown_user_id);
+ }
+ return userId;
+ }
+
+ @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 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 = null;
+
+ 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;
+ }
+ }
+ return algorithmStr + ", " + keySize + " bit";
+ }
+
+ /**
+ * Converts fingerprint to hex with whitespaces after 4 characters
+ *
+ * @param fp
+ * @return
+ */
+ public static String convertFingerprintToHex(byte[] fp) {
+ String fingerPrint = "";
+ for (int i = 0; i < fp.length; ++i) {
+ if (i != 0 && i % 10 == 0) {
+ fingerPrint += " ";
+ } else if (i != 0 && i % 2 == 0) {
+ fingerPrint += " ";
+ }
+ String chunk = Integer.toHexString((fp[i] + 256) % 256).toUpperCase(Locale.US);
+ while (chunk.length() < 2) {
+ chunk = "0" + chunk;
+ }
+ fingerPrint += chunk;
+ }
+
+ return fingerPrint;
+
+ }
+
+ public static String getFingerPrint(Context context, long keyId) {
+ PGPPublicKey key = ProviderHelper.getPGPPublicKeyByKeyId(context, keyId);
+ // if it is no public key get it from your own keys...
+ if (key == null) {
+ PGPSecretKey secretKey = ProviderHelper.getPGPSecretKeyByKeyId(context, keyId);
+ if (secretKey == null) {
+ Log.e(Constants.TAG, "Key could not be found!");
+ return null;
+ }
+ key = secretKey.getPublicKey();
+ }
+
+ return convertFingerprintToHex(key.getFingerprint());
+ }
+
+ public static boolean isSecretKeyPrivateEmpty(PGPSecretKey secretKey) {
+ return secretKey.isPrivateKeyEmpty();
+ }
+
+ public static boolean isSecretKeyPrivateEmpty(Context context, long keyId) {
+ PGPSecretKey secretKey = ProviderHelper.getPGPSecretKeyByKeyId(context, keyId);
+ if (secretKey == null) {
+ Log.e(Constants.TAG, "Key could not be found!");
+ return false; // could be a public key, assume it is not empty
+ }
+ return isSecretKeyPrivateEmpty(secretKey);
+ }
+
+ public static String convertKeyIdToHex(long keyId) {
+ String fingerPrint = Long.toHexString(keyId & 0xffffffffL).toUpperCase(Locale.US);
+ while (fingerPrint.length() < 8) {
+ fingerPrint = "0" + fingerPrint;
+ }
+ return fingerPrint;
+ }
+
+ /**
+ * TODO: documentation
+ *
+ * @param keyId
+ * @return
+ */
+ public static String convertKeyToHex(long keyId) {
+ return convertKeyIdToHex(keyId >> 32) + convertKeyIdToHex(keyId);
+ }
+
+ public static long convertHexToKeyId(String data) {
+ int len = data.length();
+ String s2 = data.substring(len - 8);
+ String s1 = data.substring(0, len - 8);
+ return (Long.parseLong(s1, 16) << 32) | Long.parseLong(s2, 16);
+ }
+
+ /**
+ * 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[] { "", "", "" };
+
+ Pattern withComment = Pattern.compile("^(.*) \\((.*)\\) <(.*)>$");
+ Matcher matcher = withComment.matcher(userId);
+ if (matcher.matches()) {
+ result[0] = matcher.group(1);
+ result[1] = matcher.group(3);
+ result[2] = matcher.group(2);
+ return result;
+ }
+
+ Pattern withoutComment = Pattern.compile("^(.*) <(.*)>$");
+ matcher = withoutComment.matcher(userId);
+ if (matcher.matches()) {
+ result[0] = matcher.group(1);
+ result[1] = matcher.group(2);
+ return result;
+ }
+ return result;
+ }
+
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java
new file mode 100644
index 000000000..3ec6fd2a8
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java
@@ -0,0 +1,466 @@
+/*
+ * 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 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 org.spongycastle.bcpg.CompressionAlgorithmTags;
+import org.spongycastle.bcpg.HashAlgorithmTags;
+import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags;
+import org.spongycastle.bcpg.sig.KeyFlags;
+import org.spongycastle.jce.provider.BouncyCastleProvider;
+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.PgpGeneralException;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.util.Log;
+import org.sufficientlysecure.keychain.util.Primes;
+import org.sufficientlysecure.keychain.util.ProgressDialogUpdater;
+
+import android.content.Context;
+
+public class PgpKeyOperation {
+ private Context mContext;
+ 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(Context context, ProgressDialogUpdater progress) {
+ super();
+ this.mContext = context;
+ this.mProgress = progress;
+ }
+
+ 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);
+ }
+ }
+
+ /**
+ * Creates new secret key. The returned PGPSecretKeyRing contains only one newly generated key
+ * when this key is the new masterkey. If a masterkey is supplied in the parameters
+ * PGPSecretKeyRing contains the masterkey and the new key as a subkey (certified by the
+ * masterkey).
+ *
+ * @param algorithmChoice
+ * @param keySize
+ * @param passPhrase
+ * @param masterSecretKey
+ * @return
+ * @throws NoSuchAlgorithmException
+ * @throws PGPException
+ * @throws NoSuchProviderException
+ * @throws PgpGeneralException
+ * @throws InvalidAlgorithmParameterException
+ */
+ public PGPSecretKeyRing createKey(int algorithmChoice, int keySize, String passPhrase,
+ PGPSecretKey masterSecretKey) throws NoSuchAlgorithmException, PGPException,
+ NoSuchProviderException, PgpGeneralException, InvalidAlgorithmParameterException {
+
+ if (keySize < 512) {
+ throw new PgpGeneralException(mContext.getString(R.string.error_key_size_minimum512bit));
+ }
+
+ if (passPhrase == null) {
+ passPhrase = "";
+ }
+
+ int algorithm = 0;
+ KeyPairGenerator keyGen = null;
+
+ 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 (masterSecretKey == null) {
+ throw new PgpGeneralException(
+ mContext.getString(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 PgpGeneralException(
+ mContext.getString(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());
+ PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
+ Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passPhrase.toCharArray());
+
+ PGPKeyRingGenerator ringGen = null;
+ PGPContentSignerBuilder certificationSignerBuilder = null;
+ if (masterSecretKey == null) {
+ certificationSignerBuilder = new JcaPGPContentSignerBuilder(keyPair.getPublicKey()
+ .getAlgorithm(), HashAlgorithmTags.SHA1);
+
+ // build keyRing with only this one master key in it!
+ ringGen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION, keyPair, "",
+ sha1Calc, null, null, certificationSignerBuilder, keyEncryptor);
+ } else {
+ PGPPublicKey masterPublicKey = masterSecretKey.getPublicKey();
+ PGPPrivateKey masterPrivateKey = masterSecretKey.extractPrivateKey(keyDecryptor);
+ PGPKeyPair masterKeyPair = new PGPKeyPair(masterPublicKey, masterPrivateKey);
+
+ certificationSignerBuilder = new JcaPGPContentSignerBuilder(masterKeyPair
+ .getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA1);
+
+ // build keyRing with master key and new key as subkey (certified by masterkey)
+ ringGen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION, masterKeyPair,
+ "", sha1Calc, null, null, certificationSignerBuilder, keyEncryptor);
+
+ ringGen.addSubKey(keyPair);
+ }
+
+ PGPSecretKeyRing secKeyRing = ringGen.generateSecretKeyRing();
+
+ return secKeyRing;
+ }
+
+ public void changeSecretKeyPassphrase(PGPSecretKeyRing keyRing, String oldPassPhrase,
+ String newPassPhrase) throws IOException, PGPException, 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()));
+
+ updateProgress(R.string.progress_saving_key_ring, 50, 100);
+
+ ProviderHelper.saveKeyRing(mContext, newKeyRing);
+
+ updateProgress(R.string.progress_done, 100, 100);
+
+ }
+
+ public void buildSecretKey(ArrayList<String> userIds, ArrayList<PGPSecretKey> keys,
+ ArrayList<Integer> keysUsages, long masterKeyId, String oldPassPhrase,
+ String newPassPhrase) throws PgpGeneralException, NoSuchProviderException,
+ PGPException, NoSuchAlgorithmException, SignatureException, IOException {
+
+ Log.d(Constants.TAG, "userIds: " + userIds.toString());
+
+ updateProgress(R.string.progress_building_key, 0, 100);
+
+ if (oldPassPhrase == null) {
+ oldPassPhrase = "";
+ }
+ if (newPassPhrase == null) {
+ newPassPhrase = "";
+ }
+
+ updateProgress(R.string.progress_preparing_master_key, 10, 100);
+
+ int usageId = keysUsages.get(0);
+ boolean canSign = (usageId == Id.choice.usage.sign_only || usageId == Id.choice.usage.sign_and_encrypt);
+ boolean canEncrypt = (usageId == Id.choice.usage.encrypt_only || usageId == Id.choice.usage.sign_and_encrypt);
+
+ String mainUserId = userIds.get(0);
+
+ PGPSecretKey masterKey = keys.get(0);
+
+ // this removes all userIds and certifications previously attached to the masterPublicKey
+ PGPPublicKey tmpKey = masterKey.getPublicKey();
+ PGPPublicKey masterPublicKey = new PGPPublicKey(tmpKey.getAlgorithm(),
+ tmpKey.getKey(new BouncyCastleProvider()), tmpKey.getCreationTime());
+
+ // already done by code above:
+ // PGPPublicKey masterPublicKey = masterKey.getPublicKey();
+ // // Somehow, the PGPPublicKey already has an empty certification attached to it when the
+ // // keyRing is generated the first time, we remove that when it exists, before adding the
+ // new
+ // // ones
+ // PGPPublicKey masterPublicKeyRmCert = PGPPublicKey.removeCertification(masterPublicKey,
+ // "");
+ // if (masterPublicKeyRmCert != null) {
+ // masterPublicKey = masterPublicKeyRmCert;
+ // }
+
+ 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);
+
+ // TODO: if we are editing a key, keep old certs, don't remake certs we don't have to.
+
+ 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();
+
+ int keyFlags = KeyFlags.CERTIFY_OTHER | KeyFlags.SIGN_DATA;
+ if (canEncrypt) {
+ keyFlags |= KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE;
+ }
+ hashedPacketsGen.setKeyFlags(true, keyFlags);
+
+ hashedPacketsGen.setPreferredSymmetricAlgorithms(true, PREFERRED_SYMMETRIC_ALGORITHMS);
+ hashedPacketsGen.setPreferredHashAlgorithms(true, PREFERRED_HASH_ALGORITHMS);
+ hashedPacketsGen.setPreferredCompressionAlgorithms(true, PREFERRED_COMPRESSION_ALGORITHMS);
+
+ // TODO: this doesn't work quite right yet (APG 1)
+ // if (keyEditor.getExpiryDate() != null) {
+ // GregorianCalendar creationDate = new GregorianCalendar();
+ // creationDate.setTime(getCreationDate(masterKey));
+ // GregorianCalendar expiryDate = keyEditor.getExpiryDate();
+ // long numDays = Utils.getNumDaysBetween(creationDate, expiryDate);
+ // if (numDays <= 0) {
+ // throw new GeneralException(
+ // context.getString(R.string.error_expiryMustComeAfterCreation));
+ // }
+ // hashedPacketsGen.setKeyExpirationTime(true, numDays * 86400);
+ // }
+
+ 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 + 50 * (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();
+
+ keyFlags = 0;
+
+ usageId = keysUsages.get(i);
+ canSign = (usageId == Id.choice.usage.sign_only || usageId == Id.choice.usage.sign_and_encrypt);
+ canEncrypt = (usageId == Id.choice.usage.encrypt_only || usageId == Id.choice.usage.sign_and_encrypt);
+ if (canSign) { // TODO: ensure signing times are the same, like gpg
+ keyFlags |= KeyFlags.SIGN_DATA;
+ // cross-certify signing keys
+ 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);
+ PGPSignature certification = sGen.generateCertification(masterPublicKey,
+ subPublicKey);
+ unhashedPacketsGen.setEmbeddedSignature(false, certification);
+ }
+ if (canEncrypt) {
+ keyFlags |= KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE;
+ }
+ hashedPacketsGen.setKeyFlags(false, keyFlags);
+
+ // TODO: this doesn't work quite right yet (APG 1)
+ // if (keyEditor.getExpiryDate() != null) {
+ // GregorianCalendar creationDate = new GregorianCalendar();
+ // creationDate.setTime(getCreationDate(masterKey));
+ // GregorianCalendar expiryDate = keyEditor.getExpiryDate();
+ // long numDays = Utils.getNumDaysBetween(creationDate, expiryDate);
+ // if (numDays <= 0) {
+ // throw new GeneralException(
+ // context.getString(R.string.error_expiryMustComeAfterCreation));
+ // }
+ // hashedPacketsGen.setKeyExpirationTime(true, numDays * 86400);
+ // }
+
+ keyGen.addSubKey(subKeyPair, hashedPacketsGen.generate(), unhashedPacketsGen.generate());
+ }
+
+ PGPSecretKeyRing secretKeyRing = keyGen.generateSecretKeyRing();
+ PGPPublicKeyRing publicKeyRing = keyGen.generatePublicKeyRing();
+
+ updateProgress(R.string.progress_saving_key_ring, 90, 100);
+
+ ProviderHelper.saveKeyRing(mContext, secretKeyRing);
+ ProviderHelper.saveKeyRing(mContext, publicKeyRing);
+
+ updateProgress(R.string.progress_done, 100, 100);
+ }
+
+ public PGPPublicKeyRing signKey(long masterKeyId, long pubKeyId, String passphrase)
+ throws PgpGeneralException, NoSuchAlgorithmException, NoSuchProviderException,
+ PGPException, SignatureException {
+ if (passphrase == null) {
+ throw new PgpGeneralException("Unable to obtain passphrase");
+ } else {
+ PGPPublicKeyRing pubring = ProviderHelper
+ .getPGPPublicKeyRingByKeyId(mContext, pubKeyId);
+
+ PGPSecretKey signingKey = PgpKeyHelper.getCertificationKey(mContext, masterKeyId);
+ if (signingKey == null) {
+ throw new PgpGeneralException(mContext.getString(R.string.error_signature_failed));
+ }
+
+ PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
+ Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray());
+ PGPPrivateKey signaturePrivateKey = signingKey.extractPrivateKey(keyDecryptor);
+ if (signaturePrivateKey == null) {
+ throw new PgpGeneralException(
+ mContext.getString(R.string.error_could_not_extract_private_key));
+ }
+
+ // TODO: SHA256 fixed?
+ JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder(
+ signingKey.getPublicKey().getAlgorithm(), PGPUtil.SHA256)
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
+
+ PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(
+ contentSignerBuilder);
+
+ signatureGenerator.init(PGPSignature.DIRECT_KEY, signaturePrivateKey);
+
+ PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
+
+ PGPSignatureSubpacketVector packetVector = spGen.generate();
+ signatureGenerator.setHashedSubpackets(packetVector);
+
+ PGPPublicKey signedKey = PGPPublicKey.addCertification(pubring.getPublicKey(pubKeyId),
+ signatureGenerator.generate());
+ pubring = PGPPublicKeyRing.insertPublicKey(pubring, signedKey);
+
+ return pubring;
+ }
+ }
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpOperation.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpOperation.java
new file mode 100644
index 000000000..f3f327e85
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpOperation.java
@@ -0,0 +1,1154 @@
+/*
+ * 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 java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+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;
+import java.util.Iterator;
+
+import org.spongycastle.bcpg.ArmoredInputStream;
+import org.spongycastle.bcpg.ArmoredOutputStream;
+import org.spongycastle.bcpg.BCPGInputStream;
+import org.spongycastle.bcpg.BCPGOutputStream;
+
+import org.spongycastle.bcpg.SignaturePacket;
+
+import org.spongycastle.bcpg.SignatureSubpacket;
+import org.spongycastle.bcpg.SignatureSubpacketTags;
+import org.spongycastle.openpgp.PGPCompressedData;
+import org.spongycastle.openpgp.PGPCompressedDataGenerator;
+import org.spongycastle.openpgp.PGPEncryptedData;
+import org.spongycastle.openpgp.PGPEncryptedDataGenerator;
+import org.spongycastle.openpgp.PGPEncryptedDataList;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPLiteralData;
+import org.spongycastle.openpgp.PGPLiteralDataGenerator;
+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.PGPSignatureGenerator;
+import org.spongycastle.openpgp.PGPSignatureList;
+import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator;
+import org.spongycastle.openpgp.PGPSignatureSubpacketVector;
+import org.spongycastle.openpgp.PGPUtil;
+import org.spongycastle.openpgp.PGPV3SignatureGenerator;
+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.JcaPGPContentSignerBuilder;
+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.JcePBEKeyEncryptionMethodGenerator;
+import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
+import org.spongycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder;
+import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder;
+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.service.KeychainIntentService;
+import org.sufficientlysecure.keychain.util.InputData;
+import org.sufficientlysecure.keychain.util.Log;
+import org.sufficientlysecure.keychain.util.ProgressDialogUpdater;
+
+import android.content.Context;
+import android.os.Bundle;
+
+public class PgpOperation {
+ private Context mContext;
+ private ProgressDialogUpdater mProgress;
+ private InputData mData;
+ private OutputStream mOutStream;
+
+ public PgpOperation(Context context, ProgressDialogUpdater progress, InputData data,
+ OutputStream outStream) {
+ super();
+ this.mContext = context;
+ this.mProgress = progress;
+ this.mData = data;
+ this.mOutStream = outStream;
+ }
+
+ 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);
+ }
+ }
+
+ public void signAndEncrypt(boolean useAsciiArmor, int compression, long[] encryptionKeyIds,
+ String encryptionPassphrase, int symmetricEncryptionAlgorithm, long signatureKeyId,
+ int signatureHashAlgorithm, boolean signatureForceV3, String signaturePassphrase)
+ throws IOException, PgpGeneralException, PGPException, NoSuchProviderException,
+ NoSuchAlgorithmException, SignatureException {
+
+ if (encryptionKeyIds == null) {
+ encryptionKeyIds = new long[0];
+ }
+
+ ArmoredOutputStream armorOut = null;
+ OutputStream out = null;
+ OutputStream encryptOut = null;
+ if (useAsciiArmor) {
+ armorOut = new ArmoredOutputStream(mOutStream);
+ armorOut.setHeader("Version", PgpHelper.getFullVersion(mContext));
+ out = armorOut;
+ } else {
+ out = mOutStream;
+ }
+ PGPSecretKey signingKey = null;
+ PGPSecretKeyRing signingKeyRing = null;
+ PGPPrivateKey signaturePrivateKey = null;
+
+ if (encryptionKeyIds.length == 0 && encryptionPassphrase == null) {
+ throw new PgpGeneralException(
+ mContext.getString(R.string.error_no_encryption_keys_or_passphrase));
+ }
+
+ if (signatureKeyId != Id.key.none) {
+ signingKeyRing = ProviderHelper.getPGPSecretKeyRingByKeyId(mContext, signatureKeyId);
+ signingKey = PgpKeyHelper.getSigningKey(mContext, signatureKeyId);
+ if (signingKey == null) {
+ throw new PgpGeneralException(mContext.getString(R.string.error_signature_failed));
+ }
+
+ if (signaturePassphrase == 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(signaturePassphrase.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);
+
+ // encrypt and compress input file content
+ JcePGPDataEncryptorBuilder encryptorBuilder = new JcePGPDataEncryptorBuilder(
+ symmetricEncryptionAlgorithm).setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME)
+ .setWithIntegrityPacket(true);
+
+ PGPEncryptedDataGenerator cPk = new PGPEncryptedDataGenerator(encryptorBuilder);
+
+ if (encryptionKeyIds.length == 0) {
+ // Symmetric encryption
+ Log.d(Constants.TAG, "encryptionKeyIds length is 0 -> symmetric encryption");
+
+ JcePBEKeyEncryptionMethodGenerator symmetricEncryptionGenerator = new JcePBEKeyEncryptionMethodGenerator(
+ encryptionPassphrase.toCharArray());
+ cPk.addMethod(symmetricEncryptionGenerator);
+ } else {
+ // Asymmetric encryption
+ for (long id : encryptionKeyIds) {
+ PGPPublicKey key = PgpKeyHelper.getEncryptPublicKey(mContext, id);
+ if (key != null) {
+
+ JcePublicKeyKeyEncryptionMethodGenerator pubKeyEncryptionGenerator = new JcePublicKeyKeyEncryptionMethodGenerator(
+ key);
+ cPk.addMethod(pubKeyEncryptionGenerator);
+ }
+ }
+ }
+ encryptOut = cPk.open(out, new byte[1 << 16]);
+
+ PGPSignatureGenerator signatureGenerator = null;
+ PGPV3SignatureGenerator signatureV3Generator = null;
+
+ if (signatureKeyId != Id.key.none) {
+ updateProgress(R.string.progress_preparing_signature, 10, 100);
+
+ // content signer based on signing key algorithm and choosen hash algorithm
+ JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder(
+ signingKey.getPublicKey().getAlgorithm(), signatureHashAlgorithm)
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
+
+ if (signatureForceV3) {
+ signatureV3Generator = new PGPV3SignatureGenerator(contentSignerBuilder);
+ signatureV3Generator.init(PGPSignature.BINARY_DOCUMENT, signaturePrivateKey);
+ } else {
+ signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder);
+ signatureGenerator.init(PGPSignature.BINARY_DOCUMENT, signaturePrivateKey);
+
+ String userId = PgpKeyHelper.getMainUserId(PgpKeyHelper
+ .getMasterKey(signingKeyRing));
+ PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
+ spGen.setSignerUserID(false, userId);
+ signatureGenerator.setHashedSubpackets(spGen.generate());
+ }
+ }
+
+ PGPCompressedDataGenerator compressGen = null;
+ BCPGOutputStream bcpgOut = null;
+ if (compression == Id.choice.compression.none) {
+ bcpgOut = new BCPGOutputStream(encryptOut);
+ } else {
+ compressGen = new PGPCompressedDataGenerator(compression);
+ bcpgOut = new BCPGOutputStream(compressGen.open(encryptOut));
+ }
+ if (signatureKeyId != Id.key.none) {
+ if (signatureForceV3) {
+ signatureV3Generator.generateOnePassVersion(false).encode(bcpgOut);
+ } else {
+ signatureGenerator.generateOnePassVersion(false).encode(bcpgOut);
+ }
+ }
+
+ PGPLiteralDataGenerator literalGen = new PGPLiteralDataGenerator();
+ // file name not needed, so empty string
+ OutputStream pOut = literalGen.open(bcpgOut, PGPLiteralData.BINARY, "", new Date(),
+ new byte[1 << 16]);
+ updateProgress(R.string.progress_encrypting, 20, 100);
+
+ long done = 0;
+ int n = 0;
+ byte[] buffer = new byte[1 << 16];
+ InputStream in = mData.getInputStream();
+ while ((n = in.read(buffer)) > 0) {
+ pOut.write(buffer, 0, n);
+ if (signatureKeyId != Id.key.none) {
+ if (signatureForceV3) {
+ signatureV3Generator.update(buffer, 0, n);
+ } else {
+ signatureGenerator.update(buffer, 0, n);
+ }
+ }
+ done += n;
+ if (mData.getSize() != 0) {
+ updateProgress((int) (20 + (95 - 20) * done / mData.getSize()), 100);
+ }
+ }
+
+ literalGen.close();
+
+ if (signatureKeyId != Id.key.none) {
+ updateProgress(R.string.progress_generating_signature, 95, 100);
+ if (signatureForceV3) {
+ signatureV3Generator.generate().encode(pOut);
+ } else {
+ signatureGenerator.generate().encode(pOut);
+ }
+ }
+ if (compressGen != null) {
+ compressGen.close();
+ }
+ encryptOut.close();
+ if (useAsciiArmor) {
+ armorOut.close();
+ }
+
+ updateProgress(R.string.progress_done, 100, 100);
+ }
+
+ public void signText(long signatureKeyId, String signaturePassphrase,
+ int signatureHashAlgorithm, boolean forceV3Signature) throws PgpGeneralException,
+ PGPException, IOException, NoSuchAlgorithmException, SignatureException {
+
+ ArmoredOutputStream armorOut = new ArmoredOutputStream(mOutStream);
+ armorOut.setHeader("Version", PgpHelper.getFullVersion(mContext));
+
+ PGPSecretKey signingKey = null;
+ PGPSecretKeyRing signingKeyRing = null;
+ PGPPrivateKey signaturePrivateKey = null;
+
+ if (signatureKeyId == 0) {
+ armorOut.close();
+ throw new PgpGeneralException(mContext.getString(R.string.error_no_signature_key));
+ }
+
+ signingKeyRing = ProviderHelper.getPGPSecretKeyRingByKeyId(mContext, signatureKeyId);
+ signingKey = PgpKeyHelper.getSigningKey(mContext, signatureKeyId);
+ if (signingKey == null) {
+ armorOut.close();
+ throw new PgpGeneralException(mContext.getString(R.string.error_signature_failed));
+ }
+
+ if (signaturePassphrase == null) {
+ armorOut.close();
+ throw new PgpGeneralException(mContext.getString(R.string.error_no_signature_passphrase));
+ }
+ PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
+ Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(signaturePassphrase.toCharArray());
+ signaturePrivateKey = signingKey.extractPrivateKey(keyDecryptor);
+ if (signaturePrivateKey == null) {
+ armorOut.close();
+ 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);
+
+ PGPSignatureGenerator signatureGenerator = null;
+ PGPV3SignatureGenerator signatureV3Generator = null;
+
+ // content signer based on signing key algorithm and choosen hash algorithm
+ JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder(signingKey
+ .getPublicKey().getAlgorithm(), signatureHashAlgorithm)
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
+
+ if (forceV3Signature) {
+ signatureV3Generator = new PGPV3SignatureGenerator(contentSignerBuilder);
+ signatureV3Generator.init(PGPSignature.CANONICAL_TEXT_DOCUMENT, signaturePrivateKey);
+ } else {
+ signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder);
+ signatureGenerator.init(PGPSignature.CANONICAL_TEXT_DOCUMENT, signaturePrivateKey);
+
+ PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
+ String userId = PgpKeyHelper.getMainUserId(PgpKeyHelper.getMasterKey(signingKeyRing));
+ spGen.setSignerUserID(false, userId);
+ signatureGenerator.setHashedSubpackets(spGen.generate());
+ }
+
+ updateProgress(R.string.progress_signing, 40, 100);
+
+ armorOut.beginClearText(signatureHashAlgorithm);
+
+ InputStream inStream = mData.getInputStream();
+ final BufferedReader reader = new BufferedReader(new InputStreamReader(inStream));
+
+ final byte[] newline = "\r\n".getBytes("UTF-8");
+
+ if (forceV3Signature) {
+ processLine(reader.readLine(), armorOut, signatureV3Generator);
+ } else {
+ processLine(reader.readLine(), armorOut, signatureGenerator);
+ }
+
+ while (true) {
+ final String line = reader.readLine();
+
+ if (line == null) {
+ armorOut.write(newline);
+ break;
+ }
+
+ armorOut.write(newline);
+ if (forceV3Signature) {
+ signatureV3Generator.update(newline);
+ processLine(line, armorOut, signatureV3Generator);
+ } else {
+ signatureGenerator.update(newline);
+ processLine(line, armorOut, signatureGenerator);
+ }
+ }
+
+ armorOut.endClearText();
+
+ BCPGOutputStream bOut = new BCPGOutputStream(armorOut);
+ if (forceV3Signature) {
+ signatureV3Generator.generate().encode(bOut);
+ } else {
+ signatureGenerator.generate().encode(bOut);
+ }
+ armorOut.close();
+
+ updateProgress(R.string.progress_done, 100, 100);
+ }
+
+ public void generateSignature(boolean armored, boolean binary, long signatureKeyId,
+ String signaturePassPhrase, int hashAlgorithm, boolean forceV3Signature)
+ throws PgpGeneralException, PGPException, IOException, NoSuchAlgorithmException,
+ SignatureException {
+
+ OutputStream out = null;
+
+ // Ascii Armor (Base64)
+ ArmoredOutputStream armorOut = null;
+ if (armored) {
+ armorOut = new ArmoredOutputStream(mOutStream);
+ armorOut.setHeader("Version", PgpHelper.getFullVersion(mContext));
+ out = armorOut;
+ } else {
+ out = mOutStream;
+ }
+
+ PGPSecretKey signingKey = null;
+ PGPSecretKeyRing signingKeyRing = null;
+ PGPPrivateKey signaturePrivateKey = null;
+
+ if (signatureKeyId == 0) {
+ throw new PgpGeneralException(mContext.getString(R.string.error_no_signature_key));
+ }
+
+ signingKeyRing = ProviderHelper.getPGPSecretKeyRingByKeyId(mContext, signatureKeyId);
+ signingKey = PgpKeyHelper.getSigningKey(mContext, signatureKeyId);
+ if (signingKey == null) {
+ throw new PgpGeneralException(mContext.getString(R.string.error_signature_failed));
+ }
+
+ if (signaturePassPhrase == null) {
+ throw new PgpGeneralException(mContext.getString(R.string.error_no_signature_passphrase));
+ }
+
+ PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
+ Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(signaturePassPhrase.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, 0, 100);
+
+ updateProgress(R.string.progress_preparing_signature, 30, 100);
+
+ PGPSignatureGenerator signatureGenerator = null;
+ PGPV3SignatureGenerator signatureV3Generator = null;
+
+ int type = PGPSignature.CANONICAL_TEXT_DOCUMENT;
+ if (binary) {
+ type = PGPSignature.BINARY_DOCUMENT;
+ }
+
+ // content signer based on signing key algorithm and choosen hash algorithm
+ JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder(signingKey
+ .getPublicKey().getAlgorithm(), hashAlgorithm)
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
+
+ if (forceV3Signature) {
+ 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(PgpKeyHelper.getMasterKey(signingKeyRing));
+ 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 (forceV3Signature) {
+ 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");
+
+ while (true) {
+ final String line = reader.readLine();
+
+ if (line == null) {
+ break;
+ }
+
+ if (forceV3Signature) {
+ processLine(line, null, signatureV3Generator);
+ signatureV3Generator.update(newline);
+ } else {
+ processLine(line, null, signatureGenerator);
+ signatureGenerator.update(newline);
+ }
+ }
+ }
+
+ BCPGOutputStream bOut = new BCPGOutputStream(out);
+ if (forceV3Signature) {
+ signatureV3Generator.generate().encode(bOut);
+ } else {
+ signatureGenerator.generate().encode(bOut);
+ }
+ out.close();
+ mOutStream.close();
+
+ if (mProgress != null)
+ mProgress.setProgress(R.string.progress_done, 100, 100);
+ }
+
+ public static boolean hasSymmetricEncryption(Context context, InputStream inputStream)
+ throws PgpGeneralException, 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));
+ }
+
+ Iterator<?> it = enc.getEncryptedDataObjects();
+ while (it.hasNext()) {
+ Object obj = it.next();
+ if (obj instanceof PGPPBEEncryptedData) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public Bundle decryptAndVerify(String passphrase, boolean assumeSymmetric) throws IOException,
+ PgpGeneralException, PGPException, SignatureException {
+ if (passphrase == null) {
+ passphrase = "";
+ }
+
+ Bundle returnData = new Bundle();
+ InputStream in = PGPUtil.getDecoderStream(mData.getInputStream());
+ PGPObjectFactory pgpF = new PGPObjectFactory(in);
+ PGPEncryptedDataList enc;
+ Object o = pgpF.nextObject();
+ long signatureKeyId = 0;
+
+ 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 = null;
+ PGPEncryptedData encryptedData = null;
+
+ currentProgress += 5;
+
+ // TODO: currently we always only look at the first known key or symmetric encryption,
+ // there might be more...
+ if (assumeSymmetric) {
+ PGPPBEEncryptedData pbe = null;
+ Iterator<?> it = enc.getEncryptedDataObjects();
+ // find secret key
+ while (it.hasNext()) {
+ Object obj = it.next();
+ if (obj instanceof PGPPBEEncryptedData) {
+ pbe = (PGPPBEEncryptedData) obj;
+ break;
+ }
+ }
+
+ if (pbe == null) {
+ throw new PgpGeneralException(
+ mContext.getString(R.string.error_no_symmetric_encryption_packet));
+ }
+
+ 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(
+ passphrase.toCharArray());
+
+ clear = pbe.getDataStream(decryptorFactory);
+
+ encryptedData = pbe;
+ currentProgress += 5;
+ } else {
+ updateProgress(R.string.progress_finding_key, currentProgress, 100);
+
+ PGPPublicKeyEncryptedData pbe = null;
+ PGPSecretKey secretKey = null;
+ Iterator<?> it = enc.getEncryptedDataObjects();
+ // find secret key
+ while (it.hasNext()) {
+ Object obj = it.next();
+ if (obj instanceof PGPPublicKeyEncryptedData) {
+ PGPPublicKeyEncryptedData encData = (PGPPublicKeyEncryptedData) obj;
+ secretKey = ProviderHelper.getPGPSecretKeyByKeyId(mContext, encData.getKeyID());
+ if (secretKey != null) {
+ pbe = encData;
+ break;
+ }
+ }
+ }
+
+ 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 = null;
+ try {
+ PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder()
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
+ passphrase.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 = pbe.getDataStream(decryptorFactory);
+
+ encryptedData = pbe;
+ currentProgress += 5;
+ }
+
+ PGPObjectFactory plainFact = new PGPObjectFactory(clear);
+ Object dataChunk = plainFact.nextObject();
+ PGPOnePassSignature signature = 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;
+ }
+
+ if (dataChunk instanceof PGPOnePassSignatureList) {
+ updateProgress(R.string.progress_processing_signature, currentProgress, 100);
+
+ returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE, true);
+ PGPOnePassSignatureList sigList = (PGPOnePassSignatureList) dataChunk;
+ for (int i = 0; i < sigList.size(); ++i) {
+ signature = sigList.get(i);
+ signatureKey = ProviderHelper
+ .getPGPPublicKeyByKeyId(mContext, signature.getKeyID());
+ if (signatureKeyId == 0) {
+ signatureKeyId = signature.getKeyID();
+ }
+ if (signatureKey == null) {
+ signature = null;
+ } else {
+ signatureIndex = i;
+ signatureKeyId = signature.getKeyID();
+ String userId = null;
+ PGPPublicKeyRing signKeyRing = ProviderHelper.getPGPPublicKeyRingByKeyId(
+ mContext, signatureKeyId);
+ if (signKeyRing != null) {
+ userId = PgpKeyHelper.getMainUserId(PgpKeyHelper.getMasterKey(signKeyRing));
+ }
+ returnData.putString(KeychainIntentService.RESULT_SIGNATURE_USER_ID, userId);
+ break;
+ }
+ }
+
+ returnData.putLong(KeychainIntentService.RESULT_SIGNATURE_KEY_ID, signatureKeyId);
+
+ if (signature != null) {
+ JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = new JcaPGPContentVerifierBuilderProvider()
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
+
+ signature.init(contentVerifierBuilderProvider, signatureKey);
+ } else {
+ returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE_UNKNOWN, true);
+ }
+
+ 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;
+ OutputStream out = mOutStream;
+
+ 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 = 0;
+ int done = 0;
+ long startPos = mData.getStreamPosition();
+ while ((n = dataIn.read(buffer)) > 0) {
+ out.write(buffer, 0, n);
+ done += n;
+ if (signature != null) {
+ try {
+ signature.update(buffer, 0, n);
+ } catch (SignatureException e) {
+ returnData
+ .putBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS, false);
+ signature = null;
+ }
+ }
+ // unknown size, but try to at least have a moving, slowing down progress bar
+ currentProgress = startProgress + (endProgress - startProgress) * done
+ / (done + 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);
+
+ //Now check binding signatures
+ boolean keyBinding_isok = verifyKeyBinding(mContext, messageSignature, signatureKey);
+ boolean sig_isok = signature.verify(messageSignature);
+ returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS, keyBinding_isok & sig_isok);
+ }
+ }
+
+ // TODO: add integrity somewhere
+ if (encryptedData.isIntegrityProtected()) {
+ updateProgress(R.string.progress_verifying_integrity, 95, 100);
+
+ if (encryptedData.verify()) {
+ // passed
+ } else {
+ // failed
+ }
+ } else {
+ // no integrity check
+ }
+
+ updateProgress(R.string.progress_done, 100, 100);
+ return returnData;
+ }
+
+ public Bundle verifyText(boolean lookupUnknownKey) throws IOException, PgpGeneralException,
+ PGPException, SignatureException {
+ Bundle returnData = new Bundle();
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ ArmoredInputStream aIn = new ArmoredInputStream(mData.getInputStream());
+
+ updateProgress(R.string.progress_done, 0, 100);
+
+ // mostly taken from ClearSignedFileProcessor
+ 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);
+
+ returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE, true);
+
+ 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);
+ signatureKey = ProviderHelper.getPGPPublicKeyByKeyId(mContext, signature.getKeyID());
+ if (signatureKeyId == 0) {
+ signatureKeyId = signature.getKeyID();
+ }
+ // if key is not known and we want to lookup unknown ones...
+ if (signatureKey == null && lookupUnknownKey) {
+
+ returnData = new Bundle();
+ returnData.putLong(KeychainIntentService.RESULT_SIGNATURE_KEY_ID, signatureKeyId);
+ returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE_LOOKUP_KEY, true);
+
+ // return directly now, decrypt will be done again after importing unknown key
+ return returnData;
+ }
+
+ if (signatureKey == null) {
+ signature = null;
+ } else {
+ signatureKeyId = signature.getKeyID();
+ String userId = null;
+ PGPPublicKeyRing signKeyRing = ProviderHelper.getPGPPublicKeyRingByKeyId(mContext,
+ signatureKeyId);
+ if (signKeyRing != null) {
+ userId = PgpKeyHelper.getMainUserId(PgpKeyHelper.getMasterKey(signKeyRing));
+ }
+ returnData.putString(KeychainIntentService.RESULT_SIGNATURE_USER_ID, userId);
+ break;
+ }
+ }
+
+ returnData.putLong(KeychainIntentService.RESULT_SIGNATURE_KEY_ID, signatureKeyId);
+
+ if (signature == null) {
+ returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE_UNKNOWN, true);
+ if (mProgress != null)
+ mProgress.setProgress(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);
+ }
+
+ boolean sig_isok = signature.verify();
+
+ //Now check binding signatures
+ boolean keyBinding_isok = verifyKeyBinding(mContext, signature, signatureKey);
+
+ returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS, sig_isok & keyBinding_isok);
+
+ updateProgress(R.string.progress_done, 100, 100);
+ return returnData;
+ }
+
+ public boolean verifyKeyBinding(Context mContext, PGPSignature signature, PGPPublicKey signatureKey)
+ {
+ long signatureKeyId = signature.getKeyID();
+ boolean keyBinding_isok = false;
+ String userId = null;
+ PGPPublicKeyRing signKeyRing = ProviderHelper.getPGPPublicKeyRingByKeyId(mContext,
+ signatureKeyId);
+ PGPPublicKey mKey = null;
+ if (signKeyRing != null) {
+ mKey = PgpKeyHelper.getMasterKey(signKeyRing);
+ }
+ if (signature.getKeyID() != mKey.getKeyID()) {
+ keyBinding_isok = verifyKeyBinding(mKey, signatureKey);
+ } else { //if the key used to make the signature was the master key, no need to check binding sigs
+ keyBinding_isok = true;
+ }
+ return keyBinding_isok;
+ }
+
+ public boolean verifyKeyBinding(PGPPublicKey masterPublicKey, PGPPublicKey signingPublicKey)
+ {
+ boolean subkeyBinding_isok = false;
+ boolean tmp_subkeyBinding_isok = false;
+ boolean primkeyBinding_isok = false;
+ JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = new JcaPGPContentVerifierBuilderProvider()
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
+
+ Iterator<PGPSignature> itr = signingPublicKey.getSignatures();
+
+ subkeyBinding_isok = false;
+ tmp_subkeyBinding_isok = false;
+ primkeyBinding_isok = false;
+ 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);
+ tmp_subkeyBinding_isok = sig.verifyCertification(masterPublicKey, signingPublicKey);
+ } catch (PGPException e) {
+ continue;
+ } catch (SignatureException e) {
+ continue;
+ }
+
+ if (tmp_subkeyBinding_isok)
+ subkeyBinding_isok = true;
+ if (tmp_subkeyBinding_isok) {
+ primkeyBinding_isok = verifyPrimaryBinding(sig.getUnhashedSubPackets(), masterPublicKey, signingPublicKey);
+ if (primkeyBinding_isok)
+ break;
+ primkeyBinding_isok = verifyPrimaryBinding(sig.getHashedSubPackets(), masterPublicKey, signingPublicKey);
+ if (primkeyBinding_isok)
+ break;
+ }
+ }
+ }
+ return (subkeyBinding_isok & primkeyBinding_isok);
+ }
+
+ private boolean verifyPrimaryBinding(PGPSignatureSubpacketVector Pkts, PGPPublicKey masterPublicKey, PGPPublicKey signingPublicKey)
+ {
+ boolean primkeyBinding_isok = false;
+ JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = new JcaPGPContentVerifierBuilderProvider()
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
+ PGPSignatureList eSigList = null;
+
+ 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);
+ primkeyBinding_isok = emSig.verifyCertification(masterPublicKey, signingPublicKey);
+ if (primkeyBinding_isok)
+ break;
+ } catch (PGPException e) {
+ continue;
+ } catch (SignatureException e) {
+ continue;
+ }
+ }
+ }
+ }
+ return primkeyBinding_isok;
+ }
+
+ 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);
+ }
+
+ // taken from ClearSignedFileProcessor in BC
+ private static void processLine(PGPSignature sig, byte[] line) throws SignatureException,
+ IOException {
+ 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/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpToX509.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpToX509.java
new file mode 100644
index 000000000..e18eb0d6d
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpToX509.java
@@ -0,0 +1,307 @@
+package org.sufficientlysecure.keychain.pgp;
+
+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;
+
+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;
+
+public class PgpToX509 {
+ public final static String DN_COMMON_PART_O = "OpenPGP to X.509 Bridge";
+ public final static 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 final static class PredefinedPasswordCallbackHandler implements CallbackHandler {
+
+ private char[] password;
+ private String prompt;
+
+ 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.password = password;
+ this.prompt = prompt;
+ }
+
+ public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+ for (Callback callback : callbacks) {
+ if (callback instanceof PasswordCallback) {
+ PasswordCallback pwCallback = (PasswordCallback) callback;
+ if ((this.prompt == null) || (this.prompt.equals(pwCallback.getPrompt()))) {
+ pwCallback.setPassword(this.password);
+ }
+ } else {
+ throw new UnsupportedCallbackException(callback, "Unrecognised callback.");
+ }
+ }
+ }
+
+ protected final Object clone() throws CloneNotSupportedException {
+ throw new CloneNotSupportedException();
+ }
+ }
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/exception/NoAsymmetricEncryptionException.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/exception/NoAsymmetricEncryptionException.java
new file mode 100644
index 000000000..92542fa35
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/exception/NoAsymmetricEncryptionException.java
@@ -0,0 +1,9 @@
+package org.sufficientlysecure.keychain.pgp.exception;
+
+public class NoAsymmetricEncryptionException extends Exception {
+ static final long serialVersionUID = 0xf812773343L;
+
+ public NoAsymmetricEncryptionException() {
+ super();
+ }
+} \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/exception/PgpGeneralException.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/exception/PgpGeneralException.java
new file mode 100644
index 000000000..36c663727
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/exception/PgpGeneralException.java
@@ -0,0 +1,9 @@
+package org.sufficientlysecure.keychain.pgp.exception;
+
+public class PgpGeneralException extends Exception {
+ static final long serialVersionUID = 0xf812773342L;
+
+ public PgpGeneralException(String message) {
+ super(message);
+ }
+} \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java
new file mode 100644
index 000000000..d2381f6f0
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java
@@ -0,0 +1,268 @@
+/*
+ * 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 org.sufficientlysecure.keychain.Constants;
+
+import android.net.Uri;
+import android.provider.BaseColumns;
+
+public class KeychainContract {
+
+ interface KeyRingsColumns {
+ String MASTER_KEY_ID = "master_key_id"; // not a database id
+ String TYPE = "type"; // see KeyTypes
+ String KEY_RING_DATA = "key_ring_data"; // PGPPublicKeyRing / PGPSecretKeyRing blob
+ }
+
+ interface KeysColumns {
+ String KEY_ID = "key_id"; // not a database id
+ String TYPE = "type"; // see KeyTypes
+ String IS_MASTER_KEY = "is_master_key";
+ String ALGORITHM = "algorithm";
+ String KEY_SIZE = "key_size";
+ String CAN_CERTIFY = "can_certify";
+ String CAN_SIGN = "can_sign";
+ String CAN_ENCRYPT = "can_encrypt";
+ String IS_REVOKED = "is_revoked";
+ String CREATION = "creation";
+ String EXPIRY = "expiry";
+ String KEY_RING_ROW_ID = "key_ring_row_id"; // foreign key to key_rings._ID
+ String KEY_DATA = "key_data"; // PGPPublicKey/PGPSecretKey blob
+ String RANK = "rank";
+ }
+
+ interface UserIdsColumns {
+ String KEY_RING_ROW_ID = "key_ring_row_id"; // foreign key to key_rings._ID
+ String USER_ID = "user_id"; // not a database id
+ String RANK = "rank";
+ }
+
+ interface ApiAppsColumns {
+ String PACKAGE_NAME = "package_name";
+ String PACKAGE_SIGNATURE = "package_signature";
+ String KEY_ID = "key_id"; // not a database id
+ String ENCRYPTION_ALGORITHM = "encryption_algorithm";
+ String HASH_ALORITHM = "hash_algorithm";
+ String COMPRESSION = "compression";
+ }
+
+ 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_PUBLIC = "public";
+ public static final String PATH_SECRET = "secret";
+
+ public static final String PATH_BY_MASTER_KEY_ID = "master_key_id";
+ public static final String PATH_BY_KEY_ID = "key_id";
+ public static final String PATH_BY_EMAILS = "emails";
+ public static final String PATH_BY_LIKE_EMAIL = "like_email";
+
+ public static final String PATH_USER_IDS = "user_ids";
+ public static final String PATH_KEYS = "keys";
+
+ public static final String BASE_API_APPS = "api_apps";
+ public static final String PATH_BY_PACKAGE_NAME = "package_name";
+
+ public static class KeyRings implements KeyRingsColumns, 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.thialfihar.apg.key_ring";
+
+ /** Use if a single item is returned */
+ public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.thialfihar.apg.key_ring";
+
+ public static Uri buildPublicKeyRingsUri() {
+ return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).build();
+ }
+
+ public static Uri buildPublicKeyRingsUri(String keyRingRowId) {
+ return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).appendPath(keyRingRowId).build();
+ }
+
+ public static Uri buildPublicKeyRingsByMasterKeyIdUri(String masterKeyId) {
+ return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC)
+ .appendPath(PATH_BY_MASTER_KEY_ID).appendPath(masterKeyId).build();
+ }
+
+ public static Uri buildPublicKeyRingsByKeyIdUri(String keyId) {
+ return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).appendPath(PATH_BY_KEY_ID)
+ .appendPath(keyId).build();
+ }
+
+ public static Uri buildPublicKeyRingsByEmailsUri(String emails) {
+ return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).appendPath(PATH_BY_EMAILS)
+ .appendPath(emails).build();
+ }
+
+ public static Uri buildPublicKeyRingsByLikeEmailUri(String emails) {
+ return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).appendPath(PATH_BY_LIKE_EMAIL)
+ .appendPath(emails).build();
+ }
+
+ public static Uri buildSecretKeyRingsUri() {
+ return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).build();
+ }
+
+ public static Uri buildSecretKeyRingsUri(String keyRingRowId) {
+ return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).appendPath(keyRingRowId).build();
+ }
+
+ public static Uri buildSecretKeyRingsByMasterKeyIdUri(String masterKeyId) {
+ return CONTENT_URI.buildUpon().appendPath(PATH_SECRET)
+ .appendPath(PATH_BY_MASTER_KEY_ID).appendPath(masterKeyId).build();
+ }
+
+ public static Uri buildSecretKeyRingsByKeyIdUri(String keyId) {
+ return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).appendPath(PATH_BY_KEY_ID)
+ .appendPath(keyId).build();
+ }
+
+ public static Uri buildSecretKeyRingsByEmailsUri(String emails) {
+ return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).appendPath(PATH_BY_EMAILS)
+ .appendPath(emails).build();
+ }
+
+ public static Uri buildSecretKeyRingsByLikeEmails(String emails) {
+ return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).appendPath(PATH_BY_LIKE_EMAIL)
+ .appendPath(emails).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.thialfihar.apg.key";
+
+ /** Use if a single item is returned */
+ public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.thialfihar.apg.key";
+
+ public static Uri buildPublicKeysUri(String keyRingRowId) {
+ return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).appendPath(keyRingRowId)
+ .appendPath(PATH_KEYS).build();
+ }
+
+ public static Uri buildPublicKeysUri(String keyRingRowId, String keyRowId) {
+ return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).appendPath(keyRingRowId)
+ .appendPath(PATH_KEYS).appendPath(keyRowId).build();
+ }
+
+ public static Uri buildSecretKeysUri(String keyRingRowId) {
+ return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).appendPath(keyRingRowId)
+ .appendPath(PATH_KEYS).build();
+ }
+
+ public static Uri buildSecretKeysUri(String keyRingRowId, String keyRowId) {
+ return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).appendPath(keyRingRowId)
+ .appendPath(PATH_KEYS).appendPath(keyRowId).build();
+ }
+
+ public static Uri buildKeysUri(Uri keyRingUri) {
+ return keyRingUri.buildUpon().appendPath(PATH_KEYS).build();
+ }
+
+ public static Uri buildKeysUri(Uri keyRingUri, String keyRowId) {
+ return keyRingUri.buildUpon().appendPath(PATH_KEYS).appendPath(keyRowId).build();
+ }
+ }
+
+ public static class UserIds implements UserIdsColumns, 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.thialfihar.apg.user_id";
+
+ /** Use if a single item is returned */
+ public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.thialfihar.apg.user_id";
+
+ public static Uri buildPublicUserIdsUri(String keyRingRowId) {
+ return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).appendPath(keyRingRowId)
+ .appendPath(PATH_USER_IDS).build();
+ }
+
+ public static Uri buildPublicUserIdsUri(String keyRingRowId, String userIdRowId) {
+ return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).appendPath(keyRingRowId)
+ .appendPath(PATH_USER_IDS).appendPath(userIdRowId).build();
+ }
+
+ public static Uri buildSecretUserIdsUri(String keyRingRowId) {
+ return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).appendPath(keyRingRowId)
+ .appendPath(PATH_USER_IDS).build();
+ }
+
+ public static Uri buildSecretUserIdsUri(String keyRingRowId, String userIdRowId) {
+ return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).appendPath(keyRingRowId)
+ .appendPath(PATH_USER_IDS).appendPath(userIdRowId).build();
+ }
+
+ public static Uri buildUserIdsUri(Uri keyRingUri) {
+ return keyRingUri.buildUpon().appendPath(PATH_USER_IDS).build();
+ }
+
+ public static Uri buildUserIdsUri(Uri keyRingUri, String userIdRowId) {
+ return keyRingUri.buildUpon().appendPath(PATH_USER_IDS).appendPath(userIdRowId).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.thialfihar.apg.api_apps";
+
+ /** Use if a single item is returned */
+ public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.thialfihar.apg.api_apps";
+
+ public static Uri buildIdUri(String rowId) {
+ return CONTENT_URI.buildUpon().appendPath(rowId).build();
+ }
+
+ public static Uri buildByPackageNameUri(String packageName) {
+ return CONTENT_URI.buildUpon().appendPath(PATH_BY_PACKAGE_NAME).appendPath(packageName)
+ .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/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java
new file mode 100644
index 000000000..60c5c91a8
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java
@@ -0,0 +1,126 @@
+/*
+ * 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 org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsColumns;
+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.util.Log;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.provider.BaseColumns;
+
+public class KeychainDatabase extends SQLiteOpenHelper {
+ private static final String DATABASE_NAME = "apg.db";
+ private static final int DATABASE_VERSION = 6;
+
+ public interface Tables {
+ String KEY_RINGS = "key_rings";
+ String KEYS = "keys";
+ String USER_IDS = "user_ids";
+ String API_APPS = "api_apps";
+ }
+
+ private static final String CREATE_KEY_RINGS = "CREATE TABLE IF NOT EXISTS " + Tables.KEY_RINGS
+ + " (" + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ + KeyRingsColumns.MASTER_KEY_ID + " INT64, " + KeyRingsColumns.TYPE + " INTEGER, "
+ + KeyRingsColumns.KEY_RING_DATA + " BLOB)";
+
+ private static final String CREATE_KEYS = "CREATE TABLE IF NOT EXISTS " + Tables.KEYS + " ("
+ + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + KeysColumns.KEY_ID
+ + " INT64, " + KeysColumns.TYPE + " INTEGER, " + KeysColumns.IS_MASTER_KEY
+ + " INTEGER, " + KeysColumns.ALGORITHM + " INTEGER, " + KeysColumns.KEY_SIZE
+ + " INTEGER, " + KeysColumns.CAN_CERTIFY + " INTEGER, " + KeysColumns.CAN_SIGN
+ + " INTEGER, " + KeysColumns.CAN_ENCRYPT + " INTEGER, " + KeysColumns.IS_REVOKED
+ + " INTEGER, " + KeysColumns.CREATION + " INTEGER, " + KeysColumns.EXPIRY
+ + " INTEGER, " + KeysColumns.KEY_DATA + " BLOB," + KeysColumns.RANK + " INTEGER, "
+ + KeysColumns.KEY_RING_ROW_ID + " INTEGER NOT NULL, FOREIGN KEY("
+ + KeysColumns.KEY_RING_ROW_ID + ") REFERENCES " + Tables.KEY_RINGS + "("
+ + BaseColumns._ID + ") ON DELETE CASCADE)";
+
+ private static final String CREATE_USER_IDS = "CREATE TABLE IF NOT EXISTS " + Tables.USER_IDS
+ + " (" + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ + UserIdsColumns.USER_ID + " TEXT, " + UserIdsColumns.RANK + " INTEGER, "
+ + UserIdsColumns.KEY_RING_ROW_ID + " INTEGER NOT NULL, FOREIGN KEY("
+ + UserIdsColumns.KEY_RING_ROW_ID + ") REFERENCES " + Tables.KEY_RINGS + "("
+ + BaseColumns._ID + ") ON DELETE CASCADE)";
+
+ private static final String CREATE_API_APPS = "CREATE TABLE IF NOT EXISTS " + Tables.API_APPS
+ + " (" + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ + ApiAppsColumns.PACKAGE_NAME + " TEXT UNIQUE, " + ApiAppsColumns.PACKAGE_SIGNATURE
+ + " BLOB, " + ApiAppsColumns.KEY_ID + " INT64, " + ApiAppsColumns.ENCRYPTION_ALGORITHM
+ + " INTEGER, " + ApiAppsColumns.HASH_ALORITHM + " INTEGER, "
+ + ApiAppsColumns.COMPRESSION + " INTEGER)";
+
+ KeychainDatabase(Context context) {
+ super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ Log.w(Constants.TAG, "Creating database...");
+
+ db.execSQL(CREATE_KEY_RINGS);
+ db.execSQL(CREATE_KEYS);
+ db.execSQL(CREATE_USER_IDS);
+ db.execSQL(CREATE_API_APPS);
+ }
+
+ @Override
+ public void onOpen(SQLiteDatabase db) {
+ super.onOpen(db);
+ if (!db.isReadOnly()) {
+ // Enable foreign key constraints
+ db.execSQL("PRAGMA foreign_keys=ON;");
+ }
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ Log.w(Constants.TAG, "Upgrading database from version " + oldVersion + " to " + newVersion);
+
+ // Upgrade from oldVersion through all methods to newest one
+ for (int version = oldVersion; version < newVersion; ++version) {
+ Log.w(Constants.TAG, "Upgrading database to version " + version);
+
+ switch (version) {
+ case 3:
+ db.execSQL("ALTER TABLE " + Tables.KEYS + " ADD COLUMN " + KeysColumns.CAN_CERTIFY
+ + " INTEGER DEFAULT 0;");
+ db.execSQL("UPDATE " + Tables.KEYS + " SET " + KeysColumns.CAN_CERTIFY
+ + " = 1 WHERE " + KeysColumns.IS_MASTER_KEY + "= 1;");
+ break;
+ case 4:
+ db.execSQL(CREATE_API_APPS);
+ case 5:
+ // new column: package_signature
+ db.execSQL("DROP TABLE IF EXISTS " + Tables.API_APPS);
+ db.execSQL(CREATE_API_APPS);
+
+ default:
+ break;
+
+ }
+ }
+ }
+
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java
new file mode 100644
index 000000000..70fb11e47
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java
@@ -0,0 +1,971 @@
+/*
+ * 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.provider;
+
+import java.util.Arrays;
+import java.util.HashMap;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
+import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
+import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingsColumns;
+import org.sufficientlysecure.keychain.provider.KeychainContract.KeyTypes;
+import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
+import org.sufficientlysecure.keychain.provider.KeychainContract.KeysColumns;
+import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
+import org.sufficientlysecure.keychain.provider.KeychainContract.UserIdsColumns;
+import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
+import org.sufficientlysecure.keychain.util.Log;
+
+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.provider.BaseColumns;
+import android.text.TextUtils;
+
+public class KeychainProvider extends ContentProvider {
+ // public static final String ACTION_BROADCAST_DATABASE_CHANGE = Constants.PACKAGE_NAME
+ // + ".action.DATABASE_CHANGE";
+ //
+ // public static final String EXTRA_BROADCAST_KEY_TYPE = "key_type";
+ // public static final String EXTRA_BROADCAST_CONTENT_ITEM_TYPE = "contentItemType";
+
+ private static final int PUBLIC_KEY_RING = 101;
+ private static final int PUBLIC_KEY_RING_BY_ROW_ID = 102;
+ private static final int PUBLIC_KEY_RING_BY_MASTER_KEY_ID = 103;
+ private static final int PUBLIC_KEY_RING_BY_KEY_ID = 104;
+ private static final int PUBLIC_KEY_RING_BY_EMAILS = 105;
+ private static final int PUBLIC_KEY_RING_BY_LIKE_EMAIL = 106;
+
+ private static final int PUBLIC_KEY_RING_KEY = 111;
+ private static final int PUBLIC_KEY_RING_KEY_BY_ROW_ID = 112;
+
+ private static final int PUBLIC_KEY_RING_USER_ID = 121;
+ private static final int PUBLIC_KEY_RING_USER_ID_BY_ROW_ID = 122;
+
+ private static final int SECRET_KEY_RING = 201;
+ private static final int SECRET_KEY_RING_BY_ROW_ID = 202;
+ private static final int SECRET_KEY_RING_BY_MASTER_KEY_ID = 203;
+ private static final int SECRET_KEY_RING_BY_KEY_ID = 204;
+ private static final int SECRET_KEY_RING_BY_EMAILS = 205;
+ private static final int SECRET_KEY_RING_BY_LIKE_EMAIL = 206;
+
+ private static final int SECRET_KEY_RING_KEY = 211;
+ private static final int SECRET_KEY_RING_KEY_BY_ROW_ID = 212;
+
+ private static final int SECRET_KEY_RING_USER_ID = 221;
+ private static final int SECRET_KEY_RING_USER_ID_BY_ROW_ID = 222;
+
+ private static final int API_APPS = 301;
+ private static final int API_APPS_BY_ROW_ID = 302;
+ private static final int API_APPS_BY_PACKAGE_NAME = 303;
+
+ // private static final int DATA_STREAM = 401;
+
+ 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;
+
+ /**
+ * public key rings
+ *
+ * <pre>
+ * key_rings/public
+ * key_rings/public/#
+ * key_rings/public/master_key_id/_
+ * key_rings/public/key_id/_
+ * key_rings/public/emails/_
+ * key_rings/public/like_email/_
+ * </pre>
+ */
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ + KeychainContract.PATH_PUBLIC, PUBLIC_KEY_RING);
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ + KeychainContract.PATH_PUBLIC + "/#", PUBLIC_KEY_RING_BY_ROW_ID);
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ + KeychainContract.PATH_PUBLIC + "/" + KeychainContract.PATH_BY_MASTER_KEY_ID
+ + "/*", PUBLIC_KEY_RING_BY_MASTER_KEY_ID);
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ + KeychainContract.PATH_PUBLIC + "/" + KeychainContract.PATH_BY_KEY_ID + "/*",
+ PUBLIC_KEY_RING_BY_KEY_ID);
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ + KeychainContract.PATH_PUBLIC + "/" + KeychainContract.PATH_BY_EMAILS + "/*",
+ PUBLIC_KEY_RING_BY_EMAILS);
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ + KeychainContract.PATH_PUBLIC + "/" + KeychainContract.PATH_BY_EMAILS,
+ PUBLIC_KEY_RING_BY_EMAILS); // without emails specified
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ + KeychainContract.PATH_PUBLIC + "/" + KeychainContract.PATH_BY_LIKE_EMAIL + "/*",
+ PUBLIC_KEY_RING_BY_LIKE_EMAIL);
+
+ /**
+ * public keys
+ *
+ * <pre>
+ * key_rings/public/#/keys
+ * key_rings/public/#/keys/#
+ * </pre>
+ */
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ + KeychainContract.PATH_PUBLIC + "/#/" + KeychainContract.PATH_KEYS,
+ PUBLIC_KEY_RING_KEY);
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ + KeychainContract.PATH_PUBLIC + "/#/" + KeychainContract.PATH_KEYS + "/#",
+ PUBLIC_KEY_RING_KEY_BY_ROW_ID);
+
+ /**
+ * public user ids
+ *
+ * <pre>
+ * key_rings/public/#/user_ids
+ * key_rings/public/#/user_ids/#
+ * </pre>
+ */
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ + KeychainContract.PATH_PUBLIC + "/#/" + KeychainContract.PATH_USER_IDS,
+ PUBLIC_KEY_RING_USER_ID);
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ + KeychainContract.PATH_PUBLIC + "/#/" + KeychainContract.PATH_USER_IDS + "/#",
+ PUBLIC_KEY_RING_USER_ID_BY_ROW_ID);
+
+ /**
+ * secret key rings
+ *
+ * <pre>
+ * key_rings/secret
+ * key_rings/secret/#
+ * key_rings/secret/master_key_id/_
+ * key_rings/secret/key_id/_
+ * key_rings/secret/emails/_
+ * key_rings/secret/like_email/_
+ * </pre>
+ */
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ + KeychainContract.PATH_SECRET, SECRET_KEY_RING);
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ + KeychainContract.PATH_SECRET + "/#", SECRET_KEY_RING_BY_ROW_ID);
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ + KeychainContract.PATH_SECRET + "/" + KeychainContract.PATH_BY_MASTER_KEY_ID
+ + "/*", SECRET_KEY_RING_BY_MASTER_KEY_ID);
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ + KeychainContract.PATH_SECRET + "/" + KeychainContract.PATH_BY_KEY_ID + "/*",
+ SECRET_KEY_RING_BY_KEY_ID);
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ + KeychainContract.PATH_SECRET + "/" + KeychainContract.PATH_BY_EMAILS + "/*",
+ SECRET_KEY_RING_BY_EMAILS);
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ + KeychainContract.PATH_SECRET + "/" + KeychainContract.PATH_BY_EMAILS,
+ SECRET_KEY_RING_BY_EMAILS); // without emails specified
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ + KeychainContract.PATH_SECRET + "/" + KeychainContract.PATH_BY_LIKE_EMAIL + "/*",
+ SECRET_KEY_RING_BY_LIKE_EMAIL);
+
+ /**
+ * secret keys
+ *
+ * <pre>
+ * key_rings/secret/#/keys
+ * key_rings/secret/#/keys/#
+ * </pre>
+ */
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ + KeychainContract.PATH_SECRET + "/#/" + KeychainContract.PATH_KEYS,
+ SECRET_KEY_RING_KEY);
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ + KeychainContract.PATH_SECRET + "/#/" + KeychainContract.PATH_KEYS + "/#",
+ SECRET_KEY_RING_KEY_BY_ROW_ID);
+
+ /**
+ * secret user ids
+ *
+ * <pre>
+ * key_rings/secret/#/user_ids
+ * key_rings/secret/#/user_ids/#
+ * </pre>
+ */
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ + KeychainContract.PATH_SECRET + "/#/" + KeychainContract.PATH_USER_IDS,
+ SECRET_KEY_RING_USER_ID);
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ + KeychainContract.PATH_SECRET + "/#/" + KeychainContract.PATH_USER_IDS + "/#",
+ SECRET_KEY_RING_USER_ID_BY_ROW_ID);
+
+ /**
+ * API apps
+ */
+ matcher.addURI(authority, KeychainContract.BASE_API_APPS, API_APPS);
+ matcher.addURI(authority, KeychainContract.BASE_API_APPS + "/#", API_APPS_BY_ROW_ID);
+ matcher.addURI(authority, KeychainContract.BASE_API_APPS + "/"
+ + KeychainContract.PATH_BY_PACKAGE_NAME + "/*", API_APPS_BY_PACKAGE_NAME);
+
+ /**
+ * data stream
+ *
+ * <pre>
+ * data / _
+ * </pre>
+ */
+ // matcher.addURI(authority, KeychainContract.BASE_DATA + "/*", DATA_STREAM);
+
+ return matcher;
+ }
+
+ private KeychainDatabase mApgDatabase;
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean onCreate() {
+ mUriMatcher = buildUriMatcher();
+ mApgDatabase = new KeychainDatabase(getContext());
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String getType(Uri uri) {
+ final int match = mUriMatcher.match(uri);
+ switch (match) {
+ case PUBLIC_KEY_RING:
+ case PUBLIC_KEY_RING_BY_EMAILS:
+ case PUBLIC_KEY_RING_BY_LIKE_EMAIL:
+ case SECRET_KEY_RING:
+ case SECRET_KEY_RING_BY_EMAILS:
+ case SECRET_KEY_RING_BY_LIKE_EMAIL:
+ return KeyRings.CONTENT_TYPE;
+
+ case PUBLIC_KEY_RING_BY_ROW_ID:
+ case PUBLIC_KEY_RING_BY_MASTER_KEY_ID:
+ case PUBLIC_KEY_RING_BY_KEY_ID:
+ case SECRET_KEY_RING_BY_ROW_ID:
+ case SECRET_KEY_RING_BY_MASTER_KEY_ID:
+ case SECRET_KEY_RING_BY_KEY_ID:
+ return KeyRings.CONTENT_ITEM_TYPE;
+
+ case PUBLIC_KEY_RING_KEY:
+ case SECRET_KEY_RING_KEY:
+ return Keys.CONTENT_TYPE;
+
+ case PUBLIC_KEY_RING_KEY_BY_ROW_ID:
+ case SECRET_KEY_RING_KEY_BY_ROW_ID:
+ return Keys.CONTENT_ITEM_TYPE;
+
+ case PUBLIC_KEY_RING_USER_ID:
+ case SECRET_KEY_RING_USER_ID:
+ return UserIds.CONTENT_TYPE;
+
+ case PUBLIC_KEY_RING_USER_ID_BY_ROW_ID:
+ case SECRET_KEY_RING_USER_ID_BY_ROW_ID:
+ return UserIds.CONTENT_ITEM_TYPE;
+
+ case API_APPS:
+ return ApiApps.CONTENT_TYPE;
+
+ case API_APPS_BY_ROW_ID:
+ case API_APPS_BY_PACKAGE_NAME:
+ return ApiApps.CONTENT_ITEM_TYPE;
+
+ default:
+ throw new UnsupportedOperationException("Unknown uri: " + uri);
+ }
+ }
+
+ /**
+ * Returns type of the query (secret/public)
+ *
+ * @param uri
+ * @return
+ */
+ private int getKeyType(int match) {
+ int type;
+ switch (match) {
+ case PUBLIC_KEY_RING:
+ case PUBLIC_KEY_RING_BY_ROW_ID:
+ case PUBLIC_KEY_RING_BY_MASTER_KEY_ID:
+ case PUBLIC_KEY_RING_BY_KEY_ID:
+ case PUBLIC_KEY_RING_BY_EMAILS:
+ case PUBLIC_KEY_RING_BY_LIKE_EMAIL:
+ case PUBLIC_KEY_RING_KEY:
+ case PUBLIC_KEY_RING_KEY_BY_ROW_ID:
+ case PUBLIC_KEY_RING_USER_ID:
+ case PUBLIC_KEY_RING_USER_ID_BY_ROW_ID:
+ type = KeyTypes.PUBLIC;
+ break;
+
+ case SECRET_KEY_RING:
+ case SECRET_KEY_RING_BY_ROW_ID:
+ case SECRET_KEY_RING_BY_MASTER_KEY_ID:
+ case SECRET_KEY_RING_BY_KEY_ID:
+ case SECRET_KEY_RING_BY_EMAILS:
+ case SECRET_KEY_RING_BY_LIKE_EMAIL:
+ case SECRET_KEY_RING_KEY:
+ case SECRET_KEY_RING_KEY_BY_ROW_ID:
+ case SECRET_KEY_RING_USER_ID:
+ case SECRET_KEY_RING_USER_ID_BY_ROW_ID:
+ type = KeyTypes.SECRET;
+ break;
+
+ default:
+ Log.e(Constants.TAG, "Unknown match " + match);
+ type = -1;
+ break;
+ }
+
+ return type;
+ }
+
+ /**
+ * Set result of query to specific columns, don't show blob column for external content provider
+ *
+ * @return
+ */
+ private HashMap<String, String> getProjectionMapForKeyRings() {
+ HashMap<String, String> projectionMap = new HashMap<String, String>();
+
+ projectionMap.put(BaseColumns._ID, Tables.KEY_RINGS + "." + BaseColumns._ID);
+ projectionMap.put(KeyRingsColumns.MASTER_KEY_ID, Tables.KEY_RINGS + "."
+ + KeyRingsColumns.MASTER_KEY_ID);
+ projectionMap.put(KeyRingsColumns.KEY_RING_DATA, Tables.KEY_RINGS + "."
+ + KeyRingsColumns.KEY_RING_DATA);
+ projectionMap.put(UserIdsColumns.USER_ID, Tables.USER_IDS + "." + UserIdsColumns.USER_ID);
+
+ return projectionMap;
+ }
+
+ /**
+ * Set result of query to specific columns, don't show blob column for external content provider
+ *
+ * @return
+ */
+ private HashMap<String, String> getProjectionMapForKeys() {
+ HashMap<String, String> projectionMap = new HashMap<String, String>();
+
+ projectionMap.put(BaseColumns._ID, BaseColumns._ID);
+ projectionMap.put(KeysColumns.KEY_ID, KeysColumns.KEY_ID);
+ projectionMap.put(KeysColumns.IS_MASTER_KEY, KeysColumns.IS_MASTER_KEY);
+ projectionMap.put(KeysColumns.ALGORITHM, KeysColumns.ALGORITHM);
+ projectionMap.put(KeysColumns.KEY_SIZE, KeysColumns.KEY_SIZE);
+ projectionMap.put(KeysColumns.CAN_CERTIFY, KeysColumns.CAN_CERTIFY);
+ projectionMap.put(KeysColumns.CAN_SIGN, KeysColumns.CAN_SIGN);
+ projectionMap.put(KeysColumns.CAN_ENCRYPT, KeysColumns.CAN_ENCRYPT);
+ projectionMap.put(KeysColumns.IS_REVOKED, KeysColumns.IS_REVOKED);
+ projectionMap.put(KeysColumns.CREATION, KeysColumns.CREATION);
+ projectionMap.put(KeysColumns.EXPIRY, KeysColumns.EXPIRY);
+ projectionMap.put(KeysColumns.KEY_RING_ROW_ID, KeysColumns.KEY_RING_ROW_ID);
+ projectionMap.put(KeysColumns.KEY_DATA, KeysColumns.KEY_DATA);
+ projectionMap.put(KeysColumns.RANK, KeysColumns.RANK);
+
+ return projectionMap;
+ }
+
+ /**
+ * Builds default query for keyRings: KeyRings table is joined with UserIds
+ *
+ * @param qb
+ * @param match
+ * @param isMasterKey
+ * @param sortOrder
+ * @return
+ */
+ private SQLiteQueryBuilder buildKeyRingQuery(SQLiteQueryBuilder qb, int match, String sortOrder) {
+ // public or secret keyring
+ qb.appendWhere(Tables.KEY_RINGS + "." + KeyRingsColumns.TYPE + " = ");
+ qb.appendWhereEscapeString(Integer.toString(getKeyType(match)));
+
+ // join keyrings with userIds to every keyring
+ qb.setTables(Tables.KEY_RINGS + " INNER JOIN " + Tables.USER_IDS + " ON " + "("
+ + Tables.KEY_RINGS + "." + BaseColumns._ID + " = " + Tables.USER_IDS + "."
+ + UserIdsColumns.KEY_RING_ROW_ID + " AND " + Tables.USER_IDS + "."
+ + UserIdsColumns.RANK + " = '0')");
+
+ qb.setProjectionMap(getProjectionMapForKeyRings());
+
+ return qb;
+ }
+
+ /**
+ * Builds default query for keyRings: KeyRings table is joined with Keys and UserIds
+ *
+ * @param qb
+ * @param match
+ * @param isMasterKey
+ * @param sortOrder
+ * @return
+ */
+ private SQLiteQueryBuilder buildKeyRingQueryWithKeys(SQLiteQueryBuilder qb, int match,
+ String sortOrder) {
+ // public or secret keyring
+ qb.appendWhere(Tables.KEY_RINGS + "." + KeyRingsColumns.TYPE + " = ");
+ qb.appendWhereEscapeString(Integer.toString(getKeyType(match)));
+
+ // join keyrings with keys and userIds to every keyring
+ qb.setTables(Tables.KEY_RINGS + " INNER JOIN " + Tables.KEYS + " ON " + "("
+ + Tables.KEY_RINGS + "." + BaseColumns._ID + " = " + Tables.KEYS + "."
+ + KeysColumns.KEY_RING_ROW_ID + ") " + " INNER JOIN " + Tables.USER_IDS + " ON "
+ + "(" + Tables.KEY_RINGS + "." + BaseColumns._ID + " = " + Tables.USER_IDS + "."
+ + UserIdsColumns.KEY_RING_ROW_ID + " AND " + Tables.USER_IDS + "."
+ + UserIdsColumns.RANK + " = '0')");
+
+ qb.setProjectionMap(getProjectionMapForKeyRings());
+
+ return qb;
+ }
+
+ /** {@inheritDoc} */
+ @SuppressWarnings("deprecation")
+ @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();
+ SQLiteDatabase db = mApgDatabase.getReadableDatabase();
+
+ int match = mUriMatcher.match(uri);
+
+ switch (match) {
+ case PUBLIC_KEY_RING:
+ case SECRET_KEY_RING:
+ qb = buildKeyRingQuery(qb, match, sortOrder);
+
+ if (TextUtils.isEmpty(sortOrder)) {
+ sortOrder = Tables.USER_IDS + "." + UserIdsColumns.USER_ID + " ASC";
+ }
+
+ break;
+
+ case PUBLIC_KEY_RING_BY_ROW_ID:
+ case SECRET_KEY_RING_BY_ROW_ID:
+ qb = buildKeyRingQuery(qb, match, sortOrder);
+
+ qb.appendWhere(" AND " + Tables.KEY_RINGS + "." + BaseColumns._ID + " = ");
+ qb.appendWhereEscapeString(uri.getLastPathSegment());
+
+ if (TextUtils.isEmpty(sortOrder)) {
+ sortOrder = Tables.USER_IDS + "." + UserIdsColumns.USER_ID + " ASC";
+ }
+
+ break;
+
+ case PUBLIC_KEY_RING_BY_MASTER_KEY_ID:
+ case SECRET_KEY_RING_BY_MASTER_KEY_ID:
+ qb = buildKeyRingQuery(qb, match, sortOrder);
+
+ qb.appendWhere(" AND " + Tables.KEY_RINGS + "." + KeyRingsColumns.MASTER_KEY_ID + " = ");
+ qb.appendWhereEscapeString(uri.getLastPathSegment());
+
+ if (TextUtils.isEmpty(sortOrder)) {
+ sortOrder = Tables.USER_IDS + "." + UserIdsColumns.USER_ID + " ASC";
+ }
+
+ break;
+
+ case SECRET_KEY_RING_BY_KEY_ID:
+ case PUBLIC_KEY_RING_BY_KEY_ID:
+ qb = buildKeyRingQueryWithKeys(qb, match, sortOrder);
+
+ qb.appendWhere(" AND " + Tables.KEYS + "." + KeysColumns.KEY_ID + " = ");
+ qb.appendWhereEscapeString(uri.getLastPathSegment());
+
+ if (TextUtils.isEmpty(sortOrder)) {
+ sortOrder = Tables.USER_IDS + "." + UserIdsColumns.USER_ID + " ASC";
+ }
+
+ break;
+
+ case SECRET_KEY_RING_BY_EMAILS:
+ case PUBLIC_KEY_RING_BY_EMAILS:
+ qb = buildKeyRingQuery(qb, match, sortOrder);
+
+ String emails = uri.getLastPathSegment();
+ String chunks[] = emails.split(" *, *");
+ boolean gotCondition = false;
+ String emailWhere = "";
+ for (int i = 0; i < chunks.length; ++i) {
+ if (chunks[i].length() == 0) {
+ continue;
+ }
+ if (i != 0) {
+ emailWhere += " OR ";
+ }
+ emailWhere += "tmp." + UserIdsColumns.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 tmp." + BaseColumns._ID + " FROM "
+ + Tables.USER_IDS + " AS tmp WHERE tmp." + UserIdsColumns.KEY_RING_ROW_ID
+ + " = " + Tables.KEY_RINGS + "." + BaseColumns._ID + " AND (" + emailWhere
+ + "))");
+ }
+
+ break;
+
+ case SECRET_KEY_RING_BY_LIKE_EMAIL:
+ case PUBLIC_KEY_RING_BY_LIKE_EMAIL:
+ qb = buildKeyRingQuery(qb, match, sortOrder);
+
+ String likeEmail = uri.getLastPathSegment();
+
+ String likeEmailWhere = "tmp." + UserIdsColumns.USER_ID + " LIKE "
+ + DatabaseUtils.sqlEscapeString("%<%" + likeEmail + "%>");
+
+ qb.appendWhere(" AND EXISTS (SELECT tmp." + BaseColumns._ID + " FROM "
+ + Tables.USER_IDS + " AS tmp WHERE tmp." + UserIdsColumns.KEY_RING_ROW_ID
+ + " = " + Tables.KEY_RINGS + "." + BaseColumns._ID + " AND (" + likeEmailWhere
+ + "))");
+
+ break;
+
+ case PUBLIC_KEY_RING_KEY:
+ case SECRET_KEY_RING_KEY:
+ qb.setTables(Tables.KEYS);
+ qb.appendWhere(KeysColumns.TYPE + " = ");
+ qb.appendWhereEscapeString(Integer.toString(getKeyType(match)));
+
+ qb.appendWhere(" AND " + KeysColumns.KEY_RING_ROW_ID + " = ");
+ qb.appendWhereEscapeString(uri.getPathSegments().get(2));
+
+ qb.setProjectionMap(getProjectionMapForKeys());
+
+ break;
+
+ case PUBLIC_KEY_RING_KEY_BY_ROW_ID:
+ case SECRET_KEY_RING_KEY_BY_ROW_ID:
+ qb.setTables(Tables.KEYS);
+ qb.appendWhere(KeysColumns.TYPE + " = ");
+ qb.appendWhereEscapeString(Integer.toString(getKeyType(match)));
+
+ qb.appendWhere(" AND " + KeysColumns.KEY_RING_ROW_ID + " = ");
+ qb.appendWhereEscapeString(uri.getPathSegments().get(2));
+
+ qb.appendWhere(" AND " + BaseColumns._ID + " = ");
+ qb.appendWhereEscapeString(uri.getLastPathSegment());
+
+ qb.setProjectionMap(getProjectionMapForKeys());
+
+ break;
+
+ case PUBLIC_KEY_RING_USER_ID:
+ case SECRET_KEY_RING_USER_ID:
+ qb.setTables(Tables.USER_IDS);
+ qb.appendWhere(UserIdsColumns.KEY_RING_ROW_ID + " = ");
+ qb.appendWhereEscapeString(uri.getPathSegments().get(2));
+
+ break;
+
+ case PUBLIC_KEY_RING_USER_ID_BY_ROW_ID:
+ case SECRET_KEY_RING_USER_ID_BY_ROW_ID:
+ qb.setTables(Tables.USER_IDS);
+ qb.appendWhere(UserIdsColumns.KEY_RING_ROW_ID + " = ");
+ qb.appendWhereEscapeString(uri.getPathSegments().get(2));
+
+ qb.appendWhere(" AND " + BaseColumns._ID + " = ");
+ qb.appendWhereEscapeString(uri.getLastPathSegment());
+
+ break;
+
+ case API_APPS:
+ qb.setTables(Tables.API_APPS);
+
+ break;
+ case API_APPS_BY_ROW_ID:
+ qb.setTables(Tables.API_APPS);
+
+ qb.appendWhere(BaseColumns._ID + " = ");
+ qb.appendWhereEscapeString(uri.getLastPathSegment());
+
+ break;
+ case API_APPS_BY_PACKAGE_NAME:
+ qb.setTables(Tables.API_APPS);
+ qb.appendWhere(ApiApps.PACKAGE_NAME + " = ");
+ qb.appendWhereEscapeString(uri.getPathSegments().get(2));
+
+ break;
+
+ default:
+ throw new IllegalArgumentException("Unknown URI " + uri);
+
+ }
+
+ // If no sort order is specified use the default
+ String orderBy;
+ if (TextUtils.isEmpty(sortOrder)) {
+ orderBy = null;
+ } else {
+ orderBy = sortOrder;
+ }
+
+ Cursor c = qb.query(db, projection, selection, selectionArgs, null, null, 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 = mApgDatabase.getWritableDatabase();
+
+ Uri rowUri = null;
+ long rowId = -1;
+ try {
+ final int match = mUriMatcher.match(uri);
+
+ switch (match) {
+ case PUBLIC_KEY_RING:
+ values.put(KeyRings.TYPE, KeyTypes.PUBLIC);
+
+ rowId = db.insertOrThrow(Tables.KEY_RINGS, null, values);
+ rowUri = KeyRings.buildPublicKeyRingsUri(Long.toString(rowId));
+ sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
+
+ break;
+ case PUBLIC_KEY_RING_KEY:
+ values.put(Keys.TYPE, KeyTypes.PUBLIC);
+
+ rowId = db.insertOrThrow(Tables.KEYS, null, values);
+ rowUri = Keys.buildPublicKeysUri(Long.toString(rowId));
+ sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
+
+ break;
+ case PUBLIC_KEY_RING_USER_ID:
+ rowId = db.insertOrThrow(Tables.USER_IDS, null, values);
+ rowUri = UserIds.buildPublicUserIdsUri(Long.toString(rowId));
+ sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
+
+ break;
+ case SECRET_KEY_RING:
+ values.put(KeyRings.TYPE, KeyTypes.SECRET);
+
+ rowId = db.insertOrThrow(Tables.KEY_RINGS, null, values);
+ rowUri = KeyRings.buildSecretKeyRingsUri(Long.toString(rowId));
+ sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
+
+ break;
+ case SECRET_KEY_RING_KEY:
+ values.put(Keys.TYPE, KeyTypes.SECRET);
+
+ rowId = db.insertOrThrow(Tables.KEYS, null, values);
+ rowUri = Keys.buildSecretKeysUri(Long.toString(rowId));
+ sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
+
+ break;
+ case SECRET_KEY_RING_USER_ID:
+ rowId = db.insertOrThrow(Tables.USER_IDS, null, values);
+ rowUri = UserIds.buildSecretUserIdsUri(Long.toString(rowId));
+
+ break;
+ case API_APPS:
+ rowId = db.insertOrThrow(Tables.API_APPS, null, values);
+ rowUri = ApiApps.buildIdUri(Long.toString(rowId));
+
+ 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 insert! Entry already existing?");
+ }
+
+ return rowUri;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ Log.v(Constants.TAG, "delete(uri=" + uri + ")");
+
+ final SQLiteDatabase db = mApgDatabase.getWritableDatabase();
+
+ int count;
+ final int match = mUriMatcher.match(uri);
+
+ String defaultSelection = null;
+ switch (match) {
+ case PUBLIC_KEY_RING_BY_ROW_ID:
+ case SECRET_KEY_RING_BY_ROW_ID:
+ defaultSelection = BaseColumns._ID + "=" + uri.getLastPathSegment();
+ // corresponding keys and userIds are deleted by ON DELETE CASCADE
+ count = db.delete(Tables.KEY_RINGS,
+ buildDefaultKeyRingsSelection(defaultSelection, getKeyType(match), selection),
+ selectionArgs);
+ sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
+ break;
+ case PUBLIC_KEY_RING_BY_MASTER_KEY_ID:
+ case SECRET_KEY_RING_BY_MASTER_KEY_ID:
+ defaultSelection = KeyRings.MASTER_KEY_ID + "=" + uri.getLastPathSegment();
+ // corresponding keys and userIds are deleted by ON DELETE CASCADE
+ count = db.delete(Tables.KEY_RINGS,
+ buildDefaultKeyRingsSelection(defaultSelection, getKeyType(match), selection),
+ selectionArgs);
+ sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
+ break;
+ case PUBLIC_KEY_RING_KEY_BY_ROW_ID:
+ case SECRET_KEY_RING_KEY_BY_ROW_ID:
+ count = db.delete(Tables.KEYS,
+ buildDefaultKeysSelection(uri, getKeyType(match), selection), selectionArgs);
+ sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
+ break;
+ case PUBLIC_KEY_RING_USER_ID_BY_ROW_ID:
+ case SECRET_KEY_RING_USER_ID_BY_ROW_ID:
+ count = db.delete(Tables.KEYS, buildDefaultUserIdsSelection(uri, selection),
+ selectionArgs);
+ break;
+ case API_APPS_BY_ROW_ID:
+ count = db.delete(Tables.API_APPS, buildDefaultApiAppsSelection(uri, false, selection),
+ selectionArgs);
+ break;
+ case API_APPS_BY_PACKAGE_NAME:
+ count = db.delete(Tables.API_APPS, buildDefaultApiAppsSelection(uri, true, selection),
+ 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 = mApgDatabase.getWritableDatabase();
+
+ String defaultSelection = null;
+ int count = 0;
+ try {
+ final int match = mUriMatcher.match(uri);
+ switch (match) {
+ case PUBLIC_KEY_RING_BY_ROW_ID:
+ case SECRET_KEY_RING_BY_ROW_ID:
+ defaultSelection = BaseColumns._ID + "=" + uri.getLastPathSegment();
+
+ count = db.update(
+ Tables.KEY_RINGS,
+ values,
+ buildDefaultKeyRingsSelection(defaultSelection, getKeyType(match),
+ selection), selectionArgs);
+ sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
+
+ break;
+ case PUBLIC_KEY_RING_BY_MASTER_KEY_ID:
+ case SECRET_KEY_RING_BY_MASTER_KEY_ID:
+ defaultSelection = KeyRings.MASTER_KEY_ID + "=" + uri.getLastPathSegment();
+
+ count = db.update(
+ Tables.KEY_RINGS,
+ values,
+ buildDefaultKeyRingsSelection(defaultSelection, getKeyType(match),
+ selection), selectionArgs);
+ sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
+
+ break;
+ case PUBLIC_KEY_RING_KEY_BY_ROW_ID:
+ case SECRET_KEY_RING_KEY_BY_ROW_ID:
+ count = db
+ .update(Tables.KEYS, values,
+ buildDefaultKeysSelection(uri, getKeyType(match), selection),
+ selectionArgs);
+ sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
+
+ break;
+ case PUBLIC_KEY_RING_USER_ID_BY_ROW_ID:
+ case SECRET_KEY_RING_USER_ID_BY_ROW_ID:
+ count = db.update(Tables.USER_IDS, values,
+ buildDefaultUserIdsSelection(uri, selection), selectionArgs);
+ break;
+ case API_APPS_BY_ROW_ID:
+ count = db.update(Tables.API_APPS, values,
+ buildDefaultApiAppsSelection(uri, false, selection), selectionArgs);
+ break;
+ case API_APPS_BY_PACKAGE_NAME:
+ count = db.update(Tables.API_APPS, values,
+ buildDefaultApiAppsSelection(uri, true, 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 KeyRings. If no extra selection is specified only build
+ * where clause with rowId
+ *
+ * @param uri
+ * @param selection
+ * @return
+ */
+ private String buildDefaultKeyRingsSelection(String defaultSelection, Integer keyType,
+ String selection) {
+ String andType = "";
+ if (keyType != null) {
+ andType = " AND " + KeyRingsColumns.TYPE + "=" + keyType;
+ }
+
+ String andSelection = "";
+ if (!TextUtils.isEmpty(selection)) {
+ andSelection = " AND (" + selection + ")";
+ }
+
+ return defaultSelection + andType + andSelection;
+ }
+
+ /**
+ * Build default selection statement for Keys. If no extra selection is specified only build
+ * where clause with rowId
+ *
+ * @param uri
+ * @param selection
+ * @return
+ */
+ private String buildDefaultKeysSelection(Uri uri, Integer keyType, String selection) {
+ String rowId = uri.getLastPathSegment();
+
+ String foreignKeyRingRowId = uri.getPathSegments().get(2);
+ String andForeignKeyRing = " AND " + KeysColumns.KEY_RING_ROW_ID + " = "
+ + foreignKeyRingRowId;
+
+ String andType = "";
+ if (keyType != null) {
+ andType = " AND " + KeysColumns.TYPE + "=" + keyType;
+ }
+
+ String andSelection = "";
+ if (!TextUtils.isEmpty(selection)) {
+ andSelection = " AND (" + selection + ")";
+ }
+
+ return BaseColumns._ID + "=" + rowId + andForeignKeyRing + andType + andSelection;
+ }
+
+ /**
+ * Build default selection statement for UserIds. If no extra selection is specified only build
+ * where clause with rowId
+ *
+ * @param uri
+ * @param selection
+ * @return
+ */
+ private String buildDefaultUserIdsSelection(Uri uri, String selection) {
+ String rowId = uri.getLastPathSegment();
+
+ String foreignKeyRingRowId = uri.getPathSegments().get(2);
+ String andForeignKeyRing = " AND " + KeysColumns.KEY_RING_ROW_ID + " = "
+ + foreignKeyRingRowId;
+
+ String andSelection = "";
+ if (!TextUtils.isEmpty(selection)) {
+ andSelection = " AND (" + selection + ")";
+ }
+
+ return BaseColumns._ID + "=" + rowId + andForeignKeyRing + andSelection;
+ }
+
+ /**
+ * 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, boolean packageSelection, String selection) {
+ String lastPathSegment = uri.getLastPathSegment();
+
+ String andSelection = "";
+ if (!TextUtils.isEmpty(selection)) {
+ andSelection = " AND (" + selection + ")";
+ }
+
+ if (packageSelection) {
+ return ApiApps.PACKAGE_NAME + "=" + lastPathSegment + andSelection;
+ } else {
+ return BaseColumns._ID + "=" + lastPathSegment + andSelection;
+ }
+ }
+
+ // @Override
+ // public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
+ // int match = mUriMatcher.match(uri);
+ // if (match != DATA_STREAM) {
+ // throw new FileNotFoundException();
+ // }
+ // String fileName = uri.getLastPathSegment();
+ // File file = new File(getContext().getFilesDir().getAbsolutePath(), fileName);
+ // return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
+ // }
+
+ /**
+ * This broadcast is send system wide to inform other application that a keyring was inserted,
+ * updated, or deleted
+ */
+ private void sendBroadcastDatabaseChange(int keyType, String contentItemType) {
+ // TODO: Disabled, old API
+ // Intent intent = new Intent();
+ // intent.setAction(ACTION_BROADCAST_DATABASE_CHANGE);
+ // intent.putExtra(EXTRA_BROADCAST_KEY_TYPE, keyType);
+ // intent.putExtra(EXTRA_BROADCAST_CONTENT_ITEM_TYPE, contentItemType);
+ //
+ // getContext().sendBroadcast(intent, Constants.PERMISSION_ACCESS_API);
+ }
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainServiceBlobContract.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainServiceBlobContract.java
new file mode 100644
index 000000000..a879d60a8
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainServiceBlobContract.java
@@ -0,0 +1,41 @@
+/*
+ * 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 org.sufficientlysecure.keychain.Constants;
+
+import android.net.Uri;
+import android.provider.BaseColumns;
+
+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/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainServiceBlobDatabase.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainServiceBlobDatabase.java
new file mode 100644
index 000000000..fcee76fd7
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainServiceBlobDatabase.java
@@ -0,0 +1,48 @@
+/*
+ * 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 = "apg_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/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainServiceBlobProvider.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainServiceBlobProvider.java
new file mode 100644
index 000000000..5693e6de2
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainServiceBlobProvider.java
@@ -0,0 +1,152 @@
+/*
+ * 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 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 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 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 + "/ApgBlobs";
+
+ 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/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java
new file mode 100644
index 000000000..1683c7c0e
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java
@@ -0,0 +1,809 @@
+/*
+ * 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 java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Date;
+
+import org.spongycastle.bcpg.ArmoredOutputStream;
+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.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.KeyRings;
+import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
+import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
+import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
+import org.sufficientlysecure.keychain.service.remote.AppSettings;
+import org.sufficientlysecure.keychain.util.IterableIterator;
+import org.sufficientlysecure.keychain.util.Log;
+
+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;
+
+public class ProviderHelper {
+
+ /**
+ * Private helper method to get PGPKeyRing from database
+ *
+ * @param context
+ * @param queryUri
+ * @return
+ */
+ public static PGPKeyRing getPGPKeyRing(Context context, Uri queryUri) {
+ Cursor cursor = context.getContentResolver().query(queryUri,
+ new String[] { KeyRings._ID, KeyRings.KEY_RING_DATA }, null, null, null);
+
+ PGPKeyRing keyRing = null;
+ if (cursor != null && cursor.moveToFirst()) {
+ int keyRingDataCol = cursor.getColumnIndex(KeyRings.KEY_RING_DATA);
+
+ byte[] data = cursor.getBlob(keyRingDataCol);
+ if (data != null) {
+ keyRing = PgpConversionHelper.BytesToPGPKeyRing(data);
+ }
+ }
+
+ if (cursor != null) {
+ cursor.close();
+ }
+
+ return keyRing;
+ }
+
+ /**
+ * Retrieves the actual PGPPublicKeyRing object from the database blob based on the rowId
+ *
+ * @param context
+ * @param rowId
+ * @return
+ */
+ public static PGPPublicKeyRing getPGPPublicKeyRingByRowId(Context context, long rowId) {
+ Uri queryUri = KeyRings.buildPublicKeyRingsUri(Long.toString(rowId));
+ return (PGPPublicKeyRing) getPGPKeyRing(context, queryUri);
+ }
+
+ /**
+ * Retrieves the actual PGPPublicKeyRing object from the database blob based on the maserKeyId
+ *
+ * @param context
+ * @param masterKeyId
+ * @return
+ */
+ public static PGPPublicKeyRing getPGPPublicKeyRingByMasterKeyId(Context context,
+ long masterKeyId) {
+ Uri queryUri = KeyRings.buildPublicKeyRingsByMasterKeyIdUri(Long.toString(masterKeyId));
+ return (PGPPublicKeyRing) getPGPKeyRing(context, queryUri);
+ }
+
+ /**
+ * Retrieves the actual PGPPublicKeyRing object from the database blob associated with a key
+ * with this keyId
+ *
+ * @param context
+ * @param keyId
+ * @return
+ */
+ public static PGPPublicKeyRing getPGPPublicKeyRingByKeyId(Context context, long keyId) {
+ Uri queryUri = KeyRings.buildPublicKeyRingsByKeyIdUri(Long.toString(keyId));
+ return (PGPPublicKeyRing) getPGPKeyRing(context, queryUri);
+ }
+
+ /**
+ * Retrieves the actual PGPPublicKey object from the database blob associated with a key with
+ * this keyId
+ *
+ * @param context
+ * @param keyId
+ * @return
+ */
+ public static PGPPublicKey getPGPPublicKeyByKeyId(Context context, long keyId) {
+ PGPPublicKeyRing keyRing = getPGPPublicKeyRingByKeyId(context, keyId);
+ if (keyRing == null) {
+ return null;
+ }
+
+ return keyRing.getPublicKey(keyId);
+ }
+
+ /**
+ * Retrieves the actual PGPSecretKeyRing object from the database blob based on the rowId
+ *
+ * @param context
+ * @param rowId
+ * @return
+ */
+ public static PGPSecretKeyRing getPGPSecretKeyRingByRowId(Context context, long rowId) {
+ Uri queryUri = KeyRings.buildSecretKeyRingsUri(Long.toString(rowId));
+ return (PGPSecretKeyRing) getPGPKeyRing(context, queryUri);
+ }
+
+ /**
+ * Retrieves the actual PGPSecretKeyRing object from the database blob based on the maserKeyId
+ *
+ * @param context
+ * @param masterKeyId
+ * @return
+ */
+ public static PGPSecretKeyRing getPGPSecretKeyRingByMasterKeyId(Context context,
+ long masterKeyId) {
+ Uri queryUri = KeyRings.buildSecretKeyRingsByMasterKeyIdUri(Long.toString(masterKeyId));
+ return (PGPSecretKeyRing) getPGPKeyRing(context, queryUri);
+ }
+
+ /**
+ * Retrieves the actual PGPSecretKeyRing object from the database blob associated with a key
+ * with this keyId
+ *
+ * @param context
+ * @param keyId
+ * @return
+ */
+ public static PGPSecretKeyRing getPGPSecretKeyRingByKeyId(Context context, long keyId) {
+ Uri queryUri = KeyRings.buildSecretKeyRingsByKeyIdUri(Long.toString(keyId));
+ return (PGPSecretKeyRing) getPGPKeyRing(context, queryUri);
+ }
+
+ /**
+ * Retrieves the actual PGPSecretKey object from the database blob associated with a key with
+ * this keyId
+ *
+ * @param context
+ * @param keyId
+ * @return
+ */
+ public static PGPSecretKey getPGPSecretKeyByKeyId(Context context, long keyId) {
+ PGPSecretKeyRing keyRing = getPGPSecretKeyRingByKeyId(context, keyId);
+ if (keyRing == null) {
+ return null;
+ }
+
+ return keyRing.getSecretKey(keyId);
+ }
+
+ /**
+ * Saves PGPPublicKeyRing with its keys and userIds in DB
+ *
+ * @param context
+ * @param keyRing
+ * @return
+ * @throws IOException
+ * @throws GeneralException
+ */
+ @SuppressWarnings("unchecked")
+ public static void saveKeyRing(Context context, PGPPublicKeyRing keyRing) throws IOException {
+ PGPPublicKey masterKey = keyRing.getPublicKey();
+ long masterKeyId = masterKey.getKeyID();
+
+ // delete old version of this keyRing, which also deletes all keys and userIds on cascade
+ Uri deleteUri = KeyRings.buildPublicKeyRingsByMasterKeyIdUri(Long.toString(masterKeyId));
+
+ try {
+ context.getContentResolver().delete(deleteUri, 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();
+ values.put(KeyRings.MASTER_KEY_ID, masterKeyId);
+ values.put(KeyRings.KEY_RING_DATA, keyRing.getEncoded());
+
+ // insert new version of this keyRing
+ Uri uri = KeyRings.buildPublicKeyRingsUri();
+ Uri insertedUri = context.getContentResolver().insert(uri, values);
+ long keyRingRowId = Long.valueOf(insertedUri.getLastPathSegment());
+
+ // 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, keyRingRowId, key, rank));
+ ++rank;
+ }
+
+ int userIdRank = 0;
+ for (String userId : new IterableIterator<String>(masterKey.getUserIDs())) {
+ operations.add(buildPublicUserIdOperations(context, keyRingRowId, userId, userIdRank));
+ ++userIdRank;
+ }
+
+ 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);
+ }
+ }
+
+ /**
+ * Saves PGPSecretKeyRing with its keys and userIds in DB
+ *
+ * @param context
+ * @param keyRing
+ * @return
+ * @throws IOException
+ * @throws GeneralException
+ */
+ @SuppressWarnings("unchecked")
+ public static void saveKeyRing(Context context, PGPSecretKeyRing keyRing) throws IOException {
+ PGPSecretKey masterKey = keyRing.getSecretKey();
+ long masterKeyId = masterKey.getKeyID();
+
+ // delete old version of this keyRing, which also deletes all keys and userIds on cascade
+ Uri deleteUri = KeyRings.buildSecretKeyRingsByMasterKeyIdUri(Long.toString(masterKeyId));
+
+ try {
+ context.getContentResolver().delete(deleteUri, 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();
+ values.put(KeyRings.MASTER_KEY_ID, masterKeyId);
+ values.put(KeyRings.KEY_RING_DATA, keyRing.getEncoded());
+
+ // insert new version of this keyRing
+ Uri uri = KeyRings.buildSecretKeyRingsUri();
+ Uri insertedUri = context.getContentResolver().insert(uri, values);
+ long keyRingRowId = Long.valueOf(insertedUri.getLastPathSegment());
+
+ // save all keys and userIds included in keyRing object in database
+ ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();
+
+ int rank = 0;
+ for (PGPSecretKey key : new IterableIterator<PGPSecretKey>(keyRing.getSecretKeys())) {
+ operations.add(buildSecretKeyOperations(context, keyRingRowId, key, rank));
+ ++rank;
+ }
+
+ int userIdRank = 0;
+ for (String userId : new IterableIterator<String>(masterKey.getUserIDs())) {
+ operations.add(buildSecretUserIdOperations(context, keyRingRowId, userId, userIdRank));
+ ++userIdRank;
+ }
+
+ 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);
+ }
+ }
+
+ /**
+ * Build ContentProviderOperation to add PGPPublicKey to database corresponding to a keyRing
+ *
+ * @param context
+ * @param keyRingRowId
+ * @param key
+ * @param rank
+ * @return
+ * @throws IOException
+ */
+ private static ContentProviderOperation buildPublicKeyOperations(Context context,
+ long keyRingRowId, PGPPublicKey key, int rank) throws IOException {
+ ContentValues values = new ContentValues();
+ values.put(Keys.KEY_ID, key.getKeyID());
+ values.put(Keys.IS_MASTER_KEY, key.isMasterKey());
+ values.put(Keys.ALGORITHM, key.getAlgorithm());
+ values.put(Keys.KEY_SIZE, key.getBitStrength());
+ 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);
+ }
+ values.put(Keys.KEY_RING_ROW_ID, keyRingRowId);
+ values.put(Keys.KEY_DATA, key.getEncoded());
+ values.put(Keys.RANK, rank);
+
+ Uri uri = Keys.buildPublicKeysUri(Long.toString(keyRingRowId));
+
+ return ContentProviderOperation.newInsert(uri).withValues(values).build();
+ }
+
+ /**
+ * Build ContentProviderOperation to add PublicUserIds to database corresponding to a keyRing
+ *
+ * @param context
+ * @param keyRingRowId
+ * @param key
+ * @param rank
+ * @return
+ * @throws IOException
+ */
+ private static ContentProviderOperation buildPublicUserIdOperations(Context context,
+ long keyRingRowId, String userId, int rank) {
+ ContentValues values = new ContentValues();
+ values.put(UserIds.KEY_RING_ROW_ID, keyRingRowId);
+ values.put(UserIds.USER_ID, userId);
+ values.put(UserIds.RANK, rank);
+
+ Uri uri = UserIds.buildPublicUserIdsUri(Long.toString(keyRingRowId));
+
+ return ContentProviderOperation.newInsert(uri).withValues(values).build();
+ }
+
+ /**
+ * Build ContentProviderOperation to add PGPSecretKey to database corresponding to a keyRing
+ *
+ * @param context
+ * @param keyRingRowId
+ * @param key
+ * @param rank
+ * @return
+ * @throws IOException
+ */
+ private static ContentProviderOperation buildSecretKeyOperations(Context context,
+ long keyRingRowId, PGPSecretKey key, int rank) throws IOException {
+ ContentValues values = new ContentValues();
+
+ boolean has_private = true;
+ if (key.isMasterKey()) {
+ if (PgpKeyHelper.isSecretKeyPrivateEmpty(key)) {
+ has_private = false;
+ }
+ }
+
+ values.put(Keys.KEY_ID, key.getKeyID());
+ values.put(Keys.IS_MASTER_KEY, key.isMasterKey());
+ values.put(Keys.ALGORITHM, key.getPublicKey().getAlgorithm());
+ values.put(Keys.KEY_SIZE, key.getPublicKey().getBitStrength());
+ values.put(Keys.CAN_CERTIFY, (PgpKeyHelper.isCertificationKey(key) && has_private));
+ values.put(Keys.CAN_SIGN, (PgpKeyHelper.isSigningKey(key) && has_private));
+ values.put(Keys.CAN_ENCRYPT, PgpKeyHelper.isEncryptionKey(key));
+ values.put(Keys.IS_REVOKED, key.getPublicKey().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);
+ }
+ values.put(Keys.KEY_RING_ROW_ID, keyRingRowId);
+ values.put(Keys.KEY_DATA, key.getEncoded());
+ values.put(Keys.RANK, rank);
+
+ Uri uri = Keys.buildSecretKeysUri(Long.toString(keyRingRowId));
+
+ return ContentProviderOperation.newInsert(uri).withValues(values).build();
+ }
+
+ /**
+ * Build ContentProviderOperation to add SecretUserIds to database corresponding to a keyRing
+ *
+ * @param context
+ * @param keyRingRowId
+ * @param key
+ * @param rank
+ * @return
+ * @throws IOException
+ */
+ private static ContentProviderOperation buildSecretUserIdOperations(Context context,
+ long keyRingRowId, String userId, int rank) {
+ ContentValues values = new ContentValues();
+ values.put(UserIds.KEY_RING_ROW_ID, keyRingRowId);
+ values.put(UserIds.USER_ID, userId);
+ values.put(UserIds.RANK, rank);
+
+ Uri uri = UserIds.buildSecretUserIdsUri(Long.toString(keyRingRowId));
+
+ return ContentProviderOperation.newInsert(uri).withValues(values).build();
+ }
+
+ /**
+ * Private helper method
+ *
+ * @param context
+ * @param queryUri
+ * @return
+ */
+ private static ArrayList<Long> getKeyRingsMasterKeyIds(Context context, Uri queryUri) {
+ Cursor cursor = context.getContentResolver().query(queryUri,
+ new String[] { KeyRings.MASTER_KEY_ID }, null, null, null);
+
+ ArrayList<Long> masterKeyIds = new ArrayList<Long>();
+ if (cursor != null) {
+ int masterKeyIdCol = cursor.getColumnIndex(KeyRings.MASTER_KEY_ID);
+ if (cursor.moveToFirst()) {
+ do {
+ masterKeyIds.add(cursor.getLong(masterKeyIdCol));
+ } while (cursor.moveToNext());
+ }
+ }
+
+ if (cursor != null) {
+ cursor.close();
+ }
+
+ return masterKeyIds;
+ }
+
+ /**
+ * Retrieves ids of all SecretKeyRings
+ *
+ * @param context
+ * @return
+ */
+ public static ArrayList<Long> getSecretKeyRingsMasterKeyIds(Context context) {
+ Uri queryUri = KeyRings.buildSecretKeyRingsUri();
+ return getKeyRingsMasterKeyIds(context, queryUri);
+ }
+
+ /**
+ * Retrieves ids of all PublicKeyRings
+ *
+ * @param context
+ * @return
+ */
+ public static ArrayList<Long> getPublicKeyRingsMasterKeyIds(Context context) {
+ Uri queryUri = KeyRings.buildPublicKeyRingsUri();
+ return getKeyRingsMasterKeyIds(context, queryUri);
+ }
+
+ public static void deletePublicKeyRing(Context context, long rowId) {
+ ContentResolver cr = context.getContentResolver();
+ cr.delete(KeyRings.buildPublicKeyRingsUri(Long.toString(rowId)), null, null);
+ }
+
+ public static void deleteSecretKeyRing(Context context, long rowId) {
+ ContentResolver cr = context.getContentResolver();
+ cr.delete(KeyRings.buildSecretKeyRingsUri(Long.toString(rowId)), null, null);
+ }
+
+ /**
+ * Get master key id of keyring by its row id
+ *
+ * @param context
+ * @param keyRingRowId
+ * @return
+ */
+ public static long getPublicMasterKeyId(Context context, long keyRingRowId) {
+ Uri queryUri = KeyRings.buildPublicKeyRingsUri(String.valueOf(keyRingRowId));
+ return getMasterKeyId(context, queryUri);
+ }
+
+ /**
+ * Get empty status of master key of keyring by its row id
+ *
+ * @param context
+ * @param keyRingRowId
+ * @return
+ */
+ public static boolean getSecretMasterKeyCanSign(Context context, long keyRingRowId) {
+ Uri queryUri = KeyRings.buildSecretKeyRingsUri(String.valueOf(keyRingRowId));
+ return getMasterKeyCanSign(context, queryUri, keyRingRowId);
+ }
+
+ /**
+ * Private helper method to get master key private empty status of keyring by its row id
+ *
+ * @param context
+ * @param queryUri
+ * @param keyRingRowId
+ * @return
+ */
+ private static boolean getMasterKeyCanSign(Context context, Uri queryUri, long keyRingRowId) {
+ String[] projection = new String[] {
+ KeyRings.MASTER_KEY_ID,
+ "(SELECT COUNT(sign_keys." + Keys._ID + ") FROM " + Tables.KEYS
+ + " AS sign_keys WHERE sign_keys." + Keys.KEY_RING_ROW_ID + " = "
+ + KeychainDatabase.Tables.KEY_RINGS + "." + KeyRings._ID
+ + " AND sign_keys." + Keys.CAN_SIGN + " = '1' AND " + Keys.IS_MASTER_KEY
+ + " = 1) AS sign", };
+
+ ContentResolver cr = context.getContentResolver();
+ Cursor cursor = cr.query(queryUri, projection, null, null, null);
+
+ long masterKeyId = -1;
+ if (cursor != null && cursor.moveToFirst()) {
+ int masterKeyIdCol = cursor.getColumnIndex("sign");
+
+ masterKeyId = cursor.getLong(masterKeyIdCol);
+ }
+
+ if (cursor != null) {
+ cursor.close();
+ }
+
+ return (masterKeyId > 0);
+ }
+
+ /**
+ * Get master key id of keyring by its row id
+ *
+ * @param context
+ * @param keyRingRowId
+ * @return
+ */
+ public static long getSecretMasterKeyId(Context context, long keyRingRowId) {
+ Uri queryUri = KeyRings.buildSecretKeyRingsUri(String.valueOf(keyRingRowId));
+ return getMasterKeyId(context, queryUri);
+ }
+
+ /**
+ * Private helper method to get master key id of keyring by its row id
+ *
+ * @param context
+ * @param queryUri
+ * @param keyRingRowId
+ * @return
+ */
+ public static long getMasterKeyId(Context context, Uri queryUri) {
+ String[] projection = new String[] { KeyRings.MASTER_KEY_ID };
+
+ ContentResolver cr = context.getContentResolver();
+ Cursor cursor = cr.query(queryUri, projection, null, null, null);
+
+ long masterKeyId = -1;
+ if (cursor != null && cursor.moveToFirst()) {
+ int masterKeyIdCol = cursor.getColumnIndex(KeyRings.MASTER_KEY_ID);
+
+ masterKeyId = cursor.getLong(masterKeyIdCol);
+ }
+
+ if (cursor != null) {
+ cursor.close();
+ }
+
+ return masterKeyId;
+ }
+
+ public static ArrayList<String> getPublicKeyRingsAsArmoredString(Context context,
+ long[] masterKeyIds) {
+ return getKeyRingsAsArmoredString(context, KeyRings.buildPublicKeyRingsUri(), masterKeyIds);
+ }
+
+ public static ArrayList<String> getSecretKeyRingsAsArmoredString(Context context,
+ long[] masterKeyIds) {
+ return getKeyRingsAsArmoredString(context, KeyRings.buildSecretKeyRingsUri(), masterKeyIds);
+ }
+
+ public static ArrayList<String> getKeyRingsAsArmoredString(Context context, Uri uri,
+ long[] masterKeyIds) {
+ ArrayList<String> output = new ArrayList<String>();
+
+ if (masterKeyIds != null && masterKeyIds.length > 0) {
+
+ Cursor cursor = getCursorWithSelectedKeyringMasterKeyIds(context, uri, masterKeyIds);
+
+ if (cursor != null) {
+ int masterIdCol = cursor.getColumnIndex(KeyRings.MASTER_KEY_ID);
+ int dataCol = cursor.getColumnIndex(KeyRings.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, "armouredKey:" + 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;
+ }
+ }
+
+ public static byte[] getPublicKeyRingsAsByteArray(Context context, long[] masterKeyIds) {
+ return getKeyRingsAsByteArray(context, KeyRings.buildPublicKeyRingsUri(), masterKeyIds);
+ }
+
+ public static byte[] getSecretKeyRingsAsByteArray(Context context, long[] masterKeyIds) {
+ return getKeyRingsAsByteArray(context, KeyRings.buildSecretKeyRingsUri(), masterKeyIds);
+ }
+
+ public static byte[] getKeyRingsAsByteArray(Context context, Uri uri, long[] masterKeyIds) {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+
+ if (masterKeyIds != null && masterKeyIds.length > 0) {
+
+ Cursor cursor = getCursorWithSelectedKeyringMasterKeyIds(context, uri, masterKeyIds);
+
+ if (cursor != null) {
+ int masterIdCol = cursor.getColumnIndex(KeyRings.MASTER_KEY_ID);
+ int dataCol = cursor.getColumnIndex(KeyRings.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 {
+ bos.write(cursor.getBlob(dataCol));
+ } 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!");
+ }
+
+ return bos.toByteArray();
+ }
+
+ private static Cursor getCursorWithSelectedKeyringMasterKeyIds(Context context, Uri baseUri,
+ long[] masterKeyIds) {
+ Cursor cursor = null;
+ if (masterKeyIds != null && masterKeyIds.length > 0) {
+
+ String inMasterKeyList = KeyRings.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(baseUri,
+ new String[] { KeyRings._ID, KeyRings.MASTER_KEY_ID, KeyRings.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());
+ values.put(ApiApps.KEY_ID, appSettings.getKeyId());
+ values.put(ApiApps.COMPRESSION, appSettings.getCompression());
+ values.put(ApiApps.ENCRYPTION_ALGORITHM, appSettings.getEncryptionAlgorithm());
+ values.put(ApiApps.HASH_ALORITHM, appSettings.getHashAlgorithm());
+
+ return values;
+ }
+
+ public static void insertApiApp(Context context, AppSettings appSettings) {
+ context.getContentResolver().insert(ApiApps.CONTENT_URI,
+ contentValueForApiApps(appSettings));
+ }
+
+ 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 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)));
+ settings.setKeyId(cur.getLong(cur.getColumnIndex(KeychainContract.ApiApps.KEY_ID)));
+ settings.setCompression(cur.getInt(cur
+ .getColumnIndexOrThrow(KeychainContract.ApiApps.COMPRESSION)));
+ settings.setHashAlgorithm(cur.getInt(cur
+ .getColumnIndexOrThrow(KeychainContract.ApiApps.HASH_ALORITHM)));
+ settings.setEncryptionAlgorithm(cur.getInt(cur
+ .getColumnIndexOrThrow(KeychainContract.ApiApps.ENCRYPTION_ALGORITHM)));
+ }
+
+ return settings;
+ }
+
+ public static byte[] getApiAppSignature(Context context, String packageName) {
+ Uri queryUri = KeychainContract.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/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java
new file mode 100644
index 000000000..ba3169b18
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java
@@ -0,0 +1,869 @@
+/*
+ * 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 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;
+
+import org.spongycastle.openpgp.PGPKeyRing;
+import org.spongycastle.openpgp.PGPPublicKeyRing;
+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.FileHelper;
+import org.sufficientlysecure.keychain.helper.OtherHelper;
+import org.sufficientlysecure.keychain.helper.Preferences;
+import org.sufficientlysecure.keychain.pgp.PgpConversionHelper;
+import org.sufficientlysecure.keychain.pgp.PgpHelper;
+import org.sufficientlysecure.keychain.pgp.PgpImportExport;
+import org.sufficientlysecure.keychain.pgp.PgpKeyOperation;
+import org.sufficientlysecure.keychain.pgp.PgpOperation;
+import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
+import org.sufficientlysecure.keychain.provider.KeychainContract.DataStream;
+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.KeyServer.KeyInfo;
+import org.sufficientlysecure.keychain.util.Log;
+import org.sufficientlysecure.keychain.util.ProgressDialogUpdater;
+
+import android.app.IntentService;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+
+/**
+ * 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 {
+
+ /* 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_QUERY_KEYRING = Constants.INTENT_PREFIX + "QUERY_KEYRING";
+
+ public static final String ACTION_SIGN_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_FILE = 2;
+ public static final int TARGET_STREAM = 3;
+
+ // encrypt
+ public static final String ENCRYPT_SECRET_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_GENERATE_SIGNATURE = "generate_signature";
+ public static final String ENCRYPT_SIGN_ONLY = "sign_only";
+ 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_PROVIDER_URI = "provider_uri";
+
+ // decrypt/verify
+ public static final String DECRYPT_SIGNED_ONLY = "signed_only";
+ public static final String DECRYPT_RETURN_BYTES = "return_binary";
+ public static final String DECRYPT_CIPHERTEXT_BYTES = "ciphertext_bytes";
+ public static final String DECRYPT_ASSUME_SYMMETRIC = "assume_symmetric";
+ public static final String DECRYPT_LOOKUP_UNKNOWN_KEY = "lookup_unknownKey";
+
+ // save keyring
+ public static final String SAVE_KEYRING_NEW_PASSPHRASE = "new_passphrase";
+ public static final String SAVE_KEYRING_CURRENT_PASSPHRASE = "current_passphrase";
+ public static final String SAVE_KEYRING_USER_IDS = "user_ids";
+ public static final String SAVE_KEYRING_KEYS = "keys";
+ public static final String SAVE_KEYRING_KEYS_USAGES = "keys_usages";
+ public static final String SAVE_KEYRING_MASTER_KEY_ID = "master_key_id";
+ 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_INPUT_STREAM = "import_input_stream";
+ public static final String IMPORT_FILENAME = "import_filename";
+ public static final String IMPORT_BYTES = "import_bytes";
+ 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_KEY_TYPE = "export_key_type";
+ 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";
+ public static final String UPLOAD_KEY_KEYRING_ROW_ID = "upload_key_ring_id";
+
+ // query key
+ public static final String QUERY_KEY_SERVER = "query_key_server";
+ public static final String QUERY_KEY_TYPE = "query_key_type";
+ public static final String QUERY_KEY_STRING = "query_key_string";
+ public static final String QUERY_KEY_ID = "query_key_id";
+
+ // sign key
+ public static final String SIGN_KEY_MASTER_KEY_ID = "sign_key_master_key_id";
+ public static final String SIGN_KEY_PUB_KEY_ID = "sign_key_pub_key_id";
+
+ /*
+ * possible data keys as result send over messenger
+ */
+ // keys
+ public static final String RESULT_NEW_KEY = "new_key";
+ public static final String RESULT_NEW_KEY2 = "new_key2";
+
+ // encrypt
+ public static final String RESULT_SIGNATURE_BYTES = "signature_data";
+ public static final String RESULT_SIGNATURE_STRING = "signature_text";
+ public static final String RESULT_ENCRYPTED_STRING = "encrypted_message";
+ public static final String RESULT_ENCRYPTED_BYTES = "encrypted_data";
+ public static final String RESULT_URI = "result_uri";
+
+ // decrypt/verify
+ public static final String RESULT_DECRYPTED_STRING = "decrypted_message";
+ public static final String RESULT_DECRYPTED_BYTES = "decrypted_data";
+ public static final String RESULT_SIGNATURE = "signature";
+ public static final String RESULT_SIGNATURE_KEY_ID = "signature_key_id";
+ public static final String RESULT_SIGNATURE_USER_ID = "signature_user_id";
+
+ public static final String RESULT_SIGNATURE_SUCCESS = "signature_success";
+ public static final String RESULT_SIGNATURE_UNKNOWN = "signature_unknown";
+ public static final String RESULT_SIGNATURE_LOOKUP_KEY = "lookup_key";
+
+ // 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";
+
+ // query
+ public static final String RESULT_QUERY_KEY_DATA = "query_key_data";
+ public static final String RESULT_QUERY_KEY_SEARCH_RESULT = "query_key_search_result";
+
+ Messenger mMessenger;
+
+ public KeychainIntentService() {
+ super("ApgService");
+ }
+
+ /**
+ * 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;
+ }
+
+ mMessenger = (Messenger) extras.get(EXTRA_MESSENGER);
+ Bundle data = extras.getBundle(EXTRA_DATA);
+
+ OtherHelper.logDebugBundle(data, "EXTRA_DATA");
+
+ String action = intent.getAction();
+
+ // execute action from extra bundle
+ if (ACTION_ENCRYPT_SIGN.equals(action)) {
+ try {
+ /* Input */
+ int target = data.getInt(TARGET);
+
+ long secretKeyId = data.getLong(ENCRYPT_SECRET_KEY_ID);
+ String encryptionPassphrase = data.getString(GENERATE_KEY_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);
+ boolean generateSignature = data.getBoolean(ENCRYPT_GENERATE_SIGNATURE);
+ boolean signOnly = data.getBoolean(ENCRYPT_SIGN_ONLY);
+
+ InputStream inStream = null;
+ long inLength = -1;
+ InputData inputData = null;
+ OutputStream outStream = null;
+ 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_FILE: /* 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;
+
+ 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 */
+ PgpOperation operation = new PgpOperation(this, this, inputData, outStream);
+ if (generateSignature) {
+ Log.d(Constants.TAG, "generating signature...");
+ operation.generateSignature(useAsciiArmor, false, secretKeyId,
+ PassphraseCacheService.getCachedPassphrase(this, secretKeyId),
+ Preferences.getPreferences(this).getDefaultHashAlgorithm(), Preferences
+ .getPreferences(this).getForceV3Signatures());
+ } else if (signOnly) {
+ Log.d(Constants.TAG, "sign only...");
+ operation.signText(secretKeyId, PassphraseCacheService.getCachedPassphrase(
+ this, secretKeyId), Preferences.getPreferences(this)
+ .getDefaultHashAlgorithm(), Preferences.getPreferences(this)
+ .getForceV3Signatures());
+ } else {
+ Log.d(Constants.TAG, "encrypt...");
+ operation.signAndEncrypt(useAsciiArmor, compressionId, encryptionKeyIds,
+ encryptionPassphrase, Preferences.getPreferences(this)
+ .getDefaultEncryptionAlgorithm(), secretKeyId, Preferences
+ .getPreferences(this).getDefaultHashAlgorithm(), Preferences
+ .getPreferences(this).getForceV3Signatures(),
+ PassphraseCacheService.getCachedPassphrase(this, secretKeyId));
+ }
+
+ outStream.close();
+
+ /* Output */
+
+ Bundle resultData = new Bundle();
+
+ switch (target) {
+ case TARGET_BYTES:
+ if (useAsciiArmor) {
+ String output = new String(
+ ((ByteArrayOutputStream) outStream).toByteArray());
+ if (generateSignature) {
+ resultData.putString(RESULT_SIGNATURE_STRING, output);
+ } else {
+ resultData.putString(RESULT_ENCRYPTED_STRING, output);
+ }
+ } else {
+ byte output[] = ((ByteArrayOutputStream) outStream).toByteArray();
+ if (generateSignature) {
+ resultData.putByteArray(RESULT_SIGNATURE_BYTES, output);
+ } else {
+ resultData.putByteArray(RESULT_ENCRYPTED_BYTES, output);
+ }
+ }
+
+ break;
+ case TARGET_FILE:
+ // 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);
+
+ long secretKeyId = data.getLong(ENCRYPT_SECRET_KEY_ID);
+ byte[] bytes = data.getByteArray(DECRYPT_CIPHERTEXT_BYTES);
+ boolean signedOnly = data.getBoolean(DECRYPT_SIGNED_ONLY);
+ boolean returnBytes = data.getBoolean(DECRYPT_RETURN_BYTES);
+ boolean assumeSymmetricEncryption = data.getBoolean(DECRYPT_ASSUME_SYMMETRIC);
+
+ boolean lookupUnknownKey = data.getBoolean(DECRYPT_LOOKUP_UNKNOWN_KEY);
+
+ InputStream inStream = null;
+ long inLength = -1;
+ InputData inputData = null;
+ OutputStream outStream = null;
+ 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_FILE: /* 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;
+
+ 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
+ PgpOperation operation = new PgpOperation(this, this, inputData, outStream);
+ if (signedOnly) {
+ resultData = operation.verifyText(lookupUnknownKey);
+ } else {
+ resultData = operation.decryptAndVerify(
+ PassphraseCacheService.getCachedPassphrase(this, secretKeyId),
+ assumeSymmetricEncryption);
+ }
+
+ outStream.close();
+
+ /* Output */
+
+ switch (target) {
+ case TARGET_BYTES:
+ if (returnBytes) {
+ byte output[] = ((ByteArrayOutputStream) outStream).toByteArray();
+ resultData.putByteArray(RESULT_DECRYPTED_BYTES, output);
+ } else {
+ String output = new String(
+ ((ByteArrayOutputStream) outStream).toByteArray());
+ resultData.putString(RESULT_DECRYPTED_STRING, output);
+ }
+
+ break;
+ case TARGET_FILE:
+ // 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 */
+ String oldPassPhrase = data.getString(SAVE_KEYRING_CURRENT_PASSPHRASE);
+ String newPassPhrase = data.getString(SAVE_KEYRING_NEW_PASSPHRASE);
+ boolean canSign = true;
+
+ if (data.containsKey(SAVE_KEYRING_CAN_SIGN)) {
+ canSign = data.getBoolean(SAVE_KEYRING_CAN_SIGN);
+ }
+
+ if (newPassPhrase == null) {
+ newPassPhrase = oldPassPhrase;
+ }
+ ArrayList<String> userIds = data.getStringArrayList(SAVE_KEYRING_USER_IDS);
+ ArrayList<PGPSecretKey> keys = PgpConversionHelper.BytesToPGPSecretKeyList(data
+ .getByteArray(SAVE_KEYRING_KEYS));
+ ArrayList<Integer> keysUsages = data.getIntegerArrayList(SAVE_KEYRING_KEYS_USAGES);
+ long masterKeyId = data.getLong(SAVE_KEYRING_MASTER_KEY_ID);
+
+ PgpKeyOperation keyOperations = new PgpKeyOperation(this, this);
+ /* Operation */
+ if (!canSign) {
+ keyOperations.changeSecretKeyPassphrase(
+ ProviderHelper.getPGPSecretKeyRingByKeyId(this, masterKeyId),
+ oldPassPhrase, newPassPhrase);
+ } else {
+ keyOperations.buildSecretKey(userIds, keys, keysUsages, masterKeyId,
+ oldPassPhrase, newPassPhrase);
+ }
+ 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);
+ PGPSecretKey masterKey = null;
+ if (data.containsKey(GENERATE_KEY_MASTER_KEY)) {
+ masterKey = PgpConversionHelper.BytesToPGPSecretKey(data
+ .getByteArray(GENERATE_KEY_MASTER_KEY));
+ }
+
+ /* Operation */
+ PgpKeyOperation keyOperations = new PgpKeyOperation(this, this);
+ PGPSecretKeyRing newKeyRing = keyOperations.createKey(algorithm, keysize,
+ passphrase, masterKey);
+
+ /* Output */
+ Bundle resultData = new Bundle();
+ resultData.putByteArray(RESULT_NEW_KEY,
+ PgpConversionHelper.PGPSecretKeyRingToBytes(newKeyRing));
+
+ 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);
+
+ /* Operation */
+ PgpKeyOperation keyOperations = new PgpKeyOperation(this, this);
+
+ PGPSecretKeyRing masterKeyRing = keyOperations.createKey(Id.choice.algorithm.rsa,
+ 4096, passphrase, null);
+
+ PGPSecretKeyRing subKeyRing = keyOperations.createKey(Id.choice.algorithm.rsa,
+ 4096, passphrase, masterKeyRing.getSecretKey());
+
+ /* Output */
+ Bundle resultData = new Bundle();
+ resultData.putByteArray(RESULT_NEW_KEY,
+ PgpConversionHelper.PGPSecretKeyRingToBytes(masterKeyRing));
+ resultData.putByteArray(RESULT_NEW_KEY2,
+ PgpConversionHelper.PGPSecretKeyRingToBytes(subKeyRing));
+
+ 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 {
+
+ /* Input */
+ int target = data.getInt(TARGET);
+
+ /* Operation */
+ InputStream inStream = null;
+ long inLength = -1;
+ InputData inputData = null;
+ switch (target) {
+ case TARGET_BYTES: /* import key from bytes directly */
+ byte[] bytes = data.getByteArray(IMPORT_BYTES);
+
+ inStream = new ByteArrayInputStream(bytes);
+ inLength = bytes.length;
+
+ inputData = new InputData(inStream, inLength);
+
+ break;
+ case TARGET_FILE: /* import key from file */
+ String inputFile = data.getString(IMPORT_FILENAME);
+
+ inStream = new FileInputStream(inputFile);
+ File file = new File(inputFile);
+ inLength = file.length();
+ inputData = new InputData(inStream, inLength);
+
+ break;
+
+ case TARGET_STREAM:
+ // TODO: not implemented
+ break;
+ }
+
+ Bundle resultData = new Bundle();
+
+ ArrayList<Long> keyIds = (ArrayList<Long>) data.getSerializable(IMPORT_KEY_LIST);
+
+ PgpImportExport pgpImportExport = new PgpImportExport(this, this);
+ resultData = pgpImportExport.importKeyRings(inputData, keyIds);
+
+ sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData);
+ } catch (Exception e) {
+ sendErrorToHandler(e);
+ }
+ } else if (ACTION_EXPORT_KEYRING.equals(action)) {
+ try {
+
+ /* Input */
+ int keyType = Id.type.public_key;
+ if (data.containsKey(EXPORT_KEY_TYPE)) {
+ keyType = data.getInt(EXPORT_KEY_TYPE);
+ }
+
+ String outputFile = data.getString(EXPORT_FILENAME);
+
+ boolean exportAll = data.getBoolean(EXPORT_ALL);
+ long keyRingMasterKeyId = -1;
+ if (!exportAll) {
+ keyRingMasterKeyId = data.getLong(EXPORT_KEY_RING_MASTER_KEY_ID);
+ }
+
+ /* Operation */
+
+ // check if storage is ready
+ if (!FileHelper.isStorageMounted(outputFile)) {
+ throw new PgpGeneralException(getString(R.string.error_external_storage_not_ready));
+ }
+
+ // OutputStream
+ FileOutputStream outStream = new FileOutputStream(outputFile);
+
+ ArrayList<Long> keyRingMasterKeyIds = new ArrayList<Long>();
+ if (exportAll) {
+ // get all key ring row ids based on export type
+
+ if (keyType == Id.type.public_key) {
+ keyRingMasterKeyIds = ProviderHelper.getPublicKeyRingsMasterKeyIds(this);
+ } else {
+ keyRingMasterKeyIds = ProviderHelper.getSecretKeyRingsMasterKeyIds(this);
+ }
+ } else {
+ keyRingMasterKeyIds.add(keyRingMasterKeyId);
+ }
+
+ Bundle resultData = new Bundle();
+
+ PgpImportExport pgpImportExport = new PgpImportExport(this, this);
+ resultData = pgpImportExport
+ .exportKeyRings(keyRingMasterKeyIds, keyType, outStream);
+
+ sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData);
+ } catch (Exception e) {
+ sendErrorToHandler(e);
+ }
+ } else if (ACTION_UPLOAD_KEYRING.equals(action)) {
+ try {
+
+ /* Input */
+ int keyRingRowId = data.getInt(UPLOAD_KEY_KEYRING_ROW_ID);
+ String keyServer = data.getString(UPLOAD_KEY_SERVER);
+
+ /* Operation */
+ HkpKeyServer server = new HkpKeyServer(keyServer);
+
+ PGPPublicKeyRing keyring = ProviderHelper.getPGPPublicKeyRingByRowId(this,
+ keyRingRowId);
+ 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_QUERY_KEYRING.equals(action)) {
+ try {
+
+ /* Input */
+ int queryType = data.getInt(QUERY_KEY_TYPE);
+ String keyServer = data.getString(QUERY_KEY_SERVER);
+
+ String queryString = data.getString(QUERY_KEY_STRING);
+ long keyId = data.getLong(QUERY_KEY_ID);
+
+ /* Operation */
+ Bundle resultData = new Bundle();
+
+ HkpKeyServer server = new HkpKeyServer(keyServer);
+ if (queryType == Id.keyserver.search) {
+ ArrayList<KeyInfo> searchResult = server.search(queryString);
+
+ resultData.putParcelableArrayList(RESULT_QUERY_KEY_SEARCH_RESULT, searchResult);
+ } else if (queryType == Id.keyserver.get) {
+ String keyData = server.get(keyId);
+
+ resultData.putString(RESULT_QUERY_KEY_DATA, keyData);
+ }
+
+ sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData);
+ } catch (Exception e) {
+ sendErrorToHandler(e);
+ }
+ } else if (ACTION_SIGN_KEYRING.equals(action)) {
+ try {
+
+ /* Input */
+ long masterKeyId = data.getLong(SIGN_KEY_MASTER_KEY_ID);
+ long pubKeyId = data.getLong(SIGN_KEY_PUB_KEY_ID);
+
+ /* Operation */
+ String signaturePassPhrase = PassphraseCacheService.getCachedPassphrase(this,
+ masterKeyId);
+
+ PgpKeyOperation keyOperation = new PgpKeyOperation(this, this);
+ PGPPublicKeyRing signedPubKeyRing = keyOperation.signKey(masterKeyId, pubKeyId,
+ signaturePassPhrase);
+
+ // store the signed key in our local cache
+ PgpImportExport pgpImportExport = new PgpImportExport(this, null);
+ int retval = pgpImportExport.storeKeyRingInCache(signedPubKeyRing);
+ 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) {
+ 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) {
+ 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 progress of ProgressDialog by sending message to handler on UI thread
+ */
+ public void setProgress(String message, int progress, int max) {
+ Log.d(Constants.TAG, "Send message by setProgress with progress=" + progress + ", max="
+ + max);
+
+ Bundle data = new Bundle();
+ if (message != null) {
+ data.putString(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);
+ }
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentServiceHandler.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentServiceHandler.java
new file mode 100644
index 000000000..170b946d2
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentServiceHandler.java
@@ -0,0 +1,110 @@
+/*
+ * 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 org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
+import org.sufficientlysecure.keychain.R;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.support.v4.app.FragmentActivity;
+import android.widget.Toast;
+
+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, int progressDialogMessageId, int progressDialogStyle) {
+ this.mActivity = activity;
+ this.mProgressDialogFragment = ProgressDialogFragment.newInstance(progressDialogMessageId,
+ progressDialogStyle);
+ }
+
+ public void showProgressDialog(FragmentActivity activity) {
+ mProgressDialogFragment.show(activity.getSupportFragmentManager(), "progressDialog");
+ }
+
+ @Override
+ public void handleMessage(Message message) {
+ Bundle data = message.getData();
+
+ switch (message.arg1) {
+ case MESSAGE_OKAY:
+ mProgressDialogFragment.dismiss();
+
+ break;
+
+ case MESSAGE_EXCEPTION:
+ mProgressDialogFragment.dismiss();
+
+ // 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/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java
new file mode 100644
index 000000000..08b9c26e6
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java
@@ -0,0 +1,370 @@
+/*
+ * 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 java.util.Date;
+import java.util.HashMap;
+
+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.ProviderHelper;
+
+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.util.Log;
+
+/**
+ * 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 HashMap<Long, String> mPassphraseCache = new HashMap<Long, 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) {
+ PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRingByKeyId(this, keyId);
+ if (keyRing == null) {
+ return null;
+ }
+ PGPSecretKey masterKey = PgpKeyHelper.getMasterKey(keyRing);
+ if (masterKey == null) {
+ return null;
+ }
+ masterKeyId = masterKey.getKeyID();
+ }
+ 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 {
+ PGPSecretKey secretKey = PgpKeyHelper.getMasterKey(ProviderHelper
+ .getPGPSecretKeyRingByKeyId(context, secretKeyId));
+ 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.isEmpty()) {
+ 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/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/exception/NoUserIdsException.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/exception/NoUserIdsException.java
new file mode 100644
index 000000000..555303238
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/exception/NoUserIdsException.java
@@ -0,0 +1,10 @@
+package org.sufficientlysecure.keychain.service.exception;
+
+public class NoUserIdsException extends Exception {
+
+ private static final long serialVersionUID = 7009311527126696207L;
+
+ public NoUserIdsException(String message) {
+ super(message);
+ }
+} \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/exception/UserInteractionRequiredException.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/exception/UserInteractionRequiredException.java
new file mode 100644
index 000000000..1152d6796
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/exception/UserInteractionRequiredException.java
@@ -0,0 +1,10 @@
+package org.sufficientlysecure.keychain.service.exception;
+
+public class UserInteractionRequiredException extends Exception {
+
+ private static final long serialVersionUID = -60128148603511936L;
+
+ public UserInteractionRequiredException(String message) {
+ super(message);
+ }
+} \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/exception/WrongPackageSignatureException.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/exception/WrongPackageSignatureException.java
new file mode 100644
index 000000000..cef002265
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/exception/WrongPackageSignatureException.java
@@ -0,0 +1,10 @@
+package org.sufficientlysecure.keychain.service.exception;
+
+public class WrongPackageSignatureException extends Exception {
+
+ private static final long serialVersionUID = -8294642703122196028L;
+
+ public WrongPackageSignatureException(String message) {
+ super(message);
+ }
+} \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/exception/WrongPassphraseException.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/exception/WrongPassphraseException.java
new file mode 100644
index 000000000..14b774eb5
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/exception/WrongPassphraseException.java
@@ -0,0 +1,10 @@
+package org.sufficientlysecure.keychain.service.exception;
+
+public class WrongPassphraseException extends Exception {
+
+ private static final long serialVersionUID = -5309689232853485740L;
+
+ public WrongPassphraseException(String message) {
+ super(message);
+ }
+} \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/AppSettings.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/AppSettings.java
new file mode 100644
index 000000000..9da4c8392
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/AppSettings.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.service.remote;
+
+import org.spongycastle.bcpg.HashAlgorithmTags;
+import org.spongycastle.openpgp.PGPEncryptedData;
+import org.sufficientlysecure.keychain.Id;
+
+public class AppSettings {
+ private String packageName;
+ private byte[] packageSignature;
+ private long keyId = Id.key.none;
+ private int encryptionAlgorithm;
+ private int hashAlgorithm;
+ private int compression;
+
+ public AppSettings() {
+
+ }
+
+ public AppSettings(String packageName, byte[] packageSignature) {
+ super();
+ this.packageName = packageName;
+ this.packageSignature = packageSignature;
+ // defaults:
+ this.encryptionAlgorithm = PGPEncryptedData.AES_256;
+ this.hashAlgorithm = HashAlgorithmTags.SHA512;
+ this.compression = Id.choice.compression.zlib;
+ }
+
+ public String getPackageName() {
+ return packageName;
+ }
+
+ public void setPackageName(String packageName) {
+ this.packageName = packageName;
+ }
+
+ public byte[] getPackageSignature() {
+ return packageSignature;
+ }
+
+ public void setPackageSignature(byte[] packageSignature) {
+ this.packageSignature = packageSignature;
+ }
+
+ public long getKeyId() {
+ return keyId;
+ }
+
+ public void setKeyId(long scretKeyId) {
+ this.keyId = scretKeyId;
+ }
+
+ public int getEncryptionAlgorithm() {
+ return encryptionAlgorithm;
+ }
+
+ public void setEncryptionAlgorithm(int encryptionAlgorithm) {
+ this.encryptionAlgorithm = encryptionAlgorithm;
+ }
+
+ public int getHashAlgorithm() {
+ return hashAlgorithm;
+ }
+
+ public void setHashAlgorithm(int hashAlgorithm) {
+ this.hashAlgorithm = hashAlgorithm;
+ }
+
+ public int getCompression() {
+ return compression;
+ }
+
+ public void setCompression(int compression) {
+ this.compression = compression;
+ }
+
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/AppSettingsActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/AppSettingsActivity.java
new file mode 100644
index 000000000..8d56fc593
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/AppSettingsActivity.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.service.remote;
+
+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.util.Log;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.View;
+
+import com.actionbarsherlock.app.SherlockFragmentActivity;
+import com.actionbarsherlock.view.Menu;
+import com.actionbarsherlock.view.MenuItem;
+
+public class AppSettingsActivity extends SherlockFragmentActivity {
+ private Uri mAppUri;
+
+ private AppSettingsFragment mSettingsFragment;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Inflate a "Done" custom action bar
+ ActionBarHelper.setDoneView(getSupportActionBar(), R.string.api_settings_save,
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // "Done"
+ save();
+ }
+ });
+
+ 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(mAppUri);
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ getSupportMenuInflater().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;
+ case R.id.menu_api_settings_cancel:
+ finish();
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ private void loadData(Uri appUri) {
+ AppSettings settings = ProviderHelper.getApiAppSettings(this, appUri);
+ mSettingsFragment.setAppSettings(settings);
+ }
+
+ private void revokeAccess() {
+ if (getContentResolver().delete(mAppUri, null, null) <= 0) {
+ throw new RuntimeException();
+ }
+ finish();
+ }
+
+ private void save() {
+ ProviderHelper.updateApiApp(this, mSettingsFragment.getAppSettings(), mAppUri);
+
+ finish();
+ }
+
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/AppSettingsFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/AppSettingsFragment.java
new file mode 100644
index 000000000..025929cfa
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/AppSettingsFragment.java
@@ -0,0 +1,234 @@
+/*
+ * 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.service.remote;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+import org.spongycastle.util.encoders.Hex;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.ui.SelectSecretKeyLayoutFragment;
+import org.sufficientlysecure.keychain.ui.adapter.KeyValueSpinnerAdapter;
+import org.sufficientlysecure.keychain.util.AlgorithmNames;
+import org.sufficientlysecure.keychain.util.Log;
+
+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.View.OnClickListener;
+import android.view.ViewGroup;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+import com.beardedhen.androidbootstrap.BootstrapButton;
+
+public class AppSettingsFragment extends Fragment implements
+ SelectSecretKeyLayoutFragment.SelectSecretKeyCallback {
+
+ // model
+ private AppSettings appSettings;
+
+ // view
+ private LinearLayout mAdvancedSettingsContainer;
+ private BootstrapButton mAdvancedSettingsButton;
+ private TextView mAppNameView;
+ private ImageView mAppIconView;
+ private Spinner mEncryptionAlgorithm;
+ private Spinner mHashAlgorithm;
+ private Spinner mCompression;
+ private TextView mPackageName;
+ private TextView mPackageSignature;
+
+ private SelectSecretKeyLayoutFragment mSelectKeyFragment;
+
+ KeyValueSpinnerAdapter encryptionAdapter;
+ KeyValueSpinnerAdapter hashAdapter;
+ KeyValueSpinnerAdapter compressionAdapter;
+
+ public AppSettings getAppSettings() {
+ return appSettings;
+ }
+
+ public void setAppSettings(AppSettings appSettings) {
+ this.appSettings = appSettings;
+ setPackage(appSettings.getPackageName());
+ mPackageName.setText(appSettings.getPackageName());
+
+ 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);
+ }
+
+ mSelectKeyFragment.selectKey(appSettings.getKeyId());
+ mEncryptionAlgorithm.setSelection(encryptionAdapter.getPosition(appSettings
+ .getEncryptionAlgorithm()));
+ mHashAlgorithm.setSelection(hashAdapter.getPosition(appSettings.getHashAlgorithm()));
+ mCompression.setSelection(compressionAdapter.getPosition(appSettings.getCompression()));
+ }
+
+ /**
+ * 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);
+ initView(view);
+ return view;
+ }
+
+ private void initView(View view) {
+ mSelectKeyFragment = (SelectSecretKeyLayoutFragment) getFragmentManager().findFragmentById(
+ R.id.api_app_settings_select_key_fragment);
+ mSelectKeyFragment.setCallback(this);
+
+ mAdvancedSettingsButton = (BootstrapButton) view
+ .findViewById(R.id.api_app_settings_advanced_button);
+ mAdvancedSettingsContainer = (LinearLayout) view
+ .findViewById(R.id.api_app_settings_advanced);
+
+ mAppNameView = (TextView) view.findViewById(R.id.api_app_settings_app_name);
+ mAppIconView = (ImageView) view.findViewById(R.id.api_app_settings_app_icon);
+ mEncryptionAlgorithm = (Spinner) view
+ .findViewById(R.id.api_app_settings_encryption_algorithm);
+ mHashAlgorithm = (Spinner) view.findViewById(R.id.api_app_settings_hash_algorithm);
+ mCompression = (Spinner) view.findViewById(R.id.api_app_settings_compression);
+ mPackageName = (TextView) view.findViewById(R.id.api_app_settings_package_name);
+ mPackageSignature = (TextView) view.findViewById(R.id.api_app_settings_package_signature);
+
+ AlgorithmNames algorithmNames = new AlgorithmNames(getActivity());
+
+ encryptionAdapter = new KeyValueSpinnerAdapter(getActivity(),
+ algorithmNames.getEncryptionNames());
+ mEncryptionAlgorithm.setAdapter(encryptionAdapter);
+ mEncryptionAlgorithm.setOnItemSelectedListener(new OnItemSelectedListener() {
+
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ appSettings.setEncryptionAlgorithm((int) id);
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+ }
+ });
+
+ hashAdapter = new KeyValueSpinnerAdapter(getActivity(), algorithmNames.getHashNames());
+ mHashAlgorithm.setAdapter(hashAdapter);
+ mHashAlgorithm.setOnItemSelectedListener(new OnItemSelectedListener() {
+
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ appSettings.setHashAlgorithm((int) id);
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+ }
+ });
+
+ compressionAdapter = new KeyValueSpinnerAdapter(getActivity(),
+ algorithmNames.getCompressionNames());
+ mCompression.setAdapter(compressionAdapter);
+ mCompression.setOnItemSelectedListener(new OnItemSelectedListener() {
+
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ appSettings.setCompression((int) id);
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+ }
+ });
+
+ final Animation visibleAnimation = new AlphaAnimation(0.0f, 1.0f);
+ visibleAnimation.setDuration(250);
+ final Animation invisibleAnimation = new AlphaAnimation(1.0f, 0.0f);
+ invisibleAnimation.setDuration(250);
+
+ // TODO: Better: collapse/expand animation
+ // final Animation animation2 = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0.0f,
+ // Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, -1.0f,
+ // Animation.RELATIVE_TO_SELF, 0.0f);
+ // animation2.setDuration(150);
+
+ mAdvancedSettingsButton.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ if (mAdvancedSettingsContainer.getVisibility() == View.VISIBLE) {
+ mAdvancedSettingsContainer.startAnimation(invisibleAnimation);
+ mAdvancedSettingsContainer.setVisibility(View.GONE);
+ mAdvancedSettingsButton.setText(getString(R.string.api_settings_show_advanced));
+ mAdvancedSettingsButton.setLeftIcon("fa-caret-up");
+ } else {
+ mAdvancedSettingsContainer.startAnimation(visibleAnimation);
+ mAdvancedSettingsContainer.setVisibility(View.VISIBLE);
+ mAdvancedSettingsButton.setText(getString(R.string.api_settings_hide_advanced));
+ mAdvancedSettingsButton.setLeftIcon("fa-caret-down");
+ }
+ }
+ });
+ }
+
+ private void setPackage(String packageName) {
+ PackageManager pm = getActivity().getApplicationContext().getPackageManager();
+
+ // get application name and icon from package manager
+ String appName = null;
+ Drawable appIcon = null;
+ try {
+ ApplicationInfo ai = pm.getApplicationInfo(packageName, 0);
+
+ appName = (String) pm.getApplicationLabel(ai);
+ appIcon = pm.getApplicationIcon(ai);
+ } catch (final NameNotFoundException e) {
+ // fallback
+ appName = packageName;
+ }
+ mAppNameView.setText(appName);
+ mAppIconView.setImageDrawable(appIcon);
+ }
+
+ /**
+ * callback from select secret key fragment
+ */
+ @Override
+ public void onKeySelected(long secretKeyId) {
+ appSettings.setKeyId(secretKeyId);
+ }
+
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/ExtendedApiService.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/ExtendedApiService.java
new file mode 100644
index 000000000..427e6bb8f
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/ExtendedApiService.java
@@ -0,0 +1,122 @@
+/*
+ * 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.service.remote;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintWriter;
+import java.security.cert.X509Certificate;
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.PasswordCallback;
+
+import org.spongycastle.openpgp.PGPPrivateKey;
+import org.spongycastle.openpgp.PGPSecretKey;
+import org.spongycastle.openssl.PEMWriter;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
+import org.sufficientlysecure.keychain.pgp.PgpToX509;
+import org.sufficientlysecure.keychain.util.Log;
+
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+public class ExtendedApiService extends RemoteService {
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mBinder;
+ }
+
+ private void selfSignedX509CertSafe(String subjAltNameURI, IExtendedApiCallback callback,
+ AppSettings appSettings) {
+
+ // TODO: for pgp keyrings with password
+ CallbackHandler pgpPwdCallbackHandler = new PgpToX509.PredefinedPasswordCallbackHandler("");
+
+ try {
+ long keyId = appSettings.getKeyId();
+ PGPSecretKey pgpSecretKey = PgpKeyHelper.getSigningKey(this, keyId);
+
+ PasswordCallback pgpSecKeyPasswordCallBack = new PasswordCallback("pgp passphrase?",
+ false);
+ pgpPwdCallbackHandler.handle(new Callback[] { pgpSecKeyPasswordCallBack });
+ PGPPrivateKey pgpPrivKey = pgpSecretKey.extractPrivateKey(
+ pgpSecKeyPasswordCallBack.getPassword(), Constants.BOUNCY_CASTLE_PROVIDER_NAME);
+ pgpSecKeyPasswordCallBack.clearPassword();
+
+ X509Certificate selfSignedCert = PgpToX509.createSelfSignedCert(pgpSecretKey,
+ pgpPrivKey, subjAltNameURI);
+
+ // Write x509cert and privKey into files
+ // FileOutputStream fosCert = context.openFileOutput(CERT_FILENAME,
+ // Context.MODE_PRIVATE);
+ ByteArrayOutputStream outStream = new ByteArrayOutputStream();
+ PEMWriter pemWriterCert = new PEMWriter(new PrintWriter(outStream));
+ pemWriterCert.writeObject(selfSignedCert);
+ pemWriterCert.close();
+
+ byte[] outputBytes = outStream.toByteArray();
+
+ callback.onSuccess(outputBytes);
+ } catch (Exception e) {
+ Log.e(Constants.TAG, "ExtendedApiService", e);
+ try {
+ callback.onError(e.getMessage());
+ } catch (RemoteException e1) {
+ Log.e(Constants.TAG, "ExtendedApiService", e);
+ }
+ }
+
+ // TODO: no private key at the moment! Don't give it to others
+ // PrivateKey privKey = pgpPrivKey.getKey();
+ // FileOutputStream fosKey = context.openFileOutput(PRIV_KEY_FILENAME,
+ // Context.MODE_PRIVATE);
+ // PEMWriter pemWriterKey = new PEMWriter(new PrintWriter(fosKey));
+ // pemWriterKey.writeObject(privKey);
+ // pemWriterKey.close();
+ }
+
+ private final IExtendedApiService.Stub mBinder = new IExtendedApiService.Stub() {
+
+ @Override
+ public void encrypt(byte[] inputBytes, String passphrase, IExtendedApiCallback callback)
+ throws RemoteException {
+ // TODO : implement
+
+ }
+
+ @Override
+ public void selfSignedX509Cert(final String subjAltNameURI,
+ final IExtendedApiCallback callback) throws RemoteException {
+ final AppSettings settings = getAppSettings();
+
+ Runnable r = new Runnable() {
+ @Override
+ public void run() {
+ selfSignedX509CertSafe(subjAltNameURI, callback, settings);
+ }
+ };
+
+ checkAndEnqueue(r);
+ }
+
+ };
+
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/OpenPgpService.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/OpenPgpService.java
new file mode 100644
index 000000000..575f76a22
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/OpenPgpService.java
@@ -0,0 +1,602 @@
+/*
+ * 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.service.remote;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.regex.Matcher;
+
+import org.openintents.openpgp.IOpenPgpCallback;
+import org.openintents.openpgp.IOpenPgpKeyIdsCallback;
+import org.openintents.openpgp.IOpenPgpService;
+import org.openintents.openpgp.OpenPgpData;
+import org.openintents.openpgp.OpenPgpError;
+import org.openintents.openpgp.OpenPgpSignatureResult;
+import org.spongycastle.util.Arrays;
+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.pgp.PgpHelper;
+import org.sufficientlysecure.keychain.pgp.PgpOperation;
+import org.sufficientlysecure.keychain.pgp.exception.NoAsymmetricEncryptionException;
+import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
+import org.sufficientlysecure.keychain.provider.KeychainContract;
+import org.sufficientlysecure.keychain.service.KeychainIntentService;
+import org.sufficientlysecure.keychain.service.PassphraseCacheService;
+import org.sufficientlysecure.keychain.service.exception.NoUserIdsException;
+import org.sufficientlysecure.keychain.service.exception.UserInteractionRequiredException;
+import org.sufficientlysecure.keychain.service.exception.WrongPassphraseException;
+import org.sufficientlysecure.keychain.util.InputData;
+import org.sufficientlysecure.keychain.util.Log;
+
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteException;
+
+public class OpenPgpService extends RemoteService {
+
+ private String getCachedPassphrase(long keyId, boolean allowUserInteraction)
+ throws UserInteractionRequiredException {
+ String passphrase = PassphraseCacheService.getCachedPassphrase(getContext(), keyId);
+
+ if (passphrase == null) {
+ if (!allowUserInteraction) {
+ throw new UserInteractionRequiredException(
+ "Passphrase not found in cache, please enter your passphrase!");
+ }
+
+ Log.d(Constants.TAG, "No passphrase! Activity required!");
+
+ // start passphrase dialog
+ PassphraseActivityCallback callback = new PassphraseActivityCallback();
+ Bundle extras = new Bundle();
+ extras.putLong(RemoteServiceActivity.EXTRA_SECRET_KEY_ID, keyId);
+ pauseAndStartUserInteraction(RemoteServiceActivity.ACTION_CACHE_PASSPHRASE, callback,
+ extras);
+
+ if (callback.isSuccess()) {
+ Log.d(Constants.TAG, "New passphrase entered!");
+
+ // get again after it was entered
+ passphrase = PassphraseCacheService.getCachedPassphrase(getContext(), keyId);
+ } else {
+ Log.d(Constants.TAG, "Passphrase dialog canceled!");
+
+ return null;
+ }
+
+ }
+
+ return passphrase;
+ }
+
+ public class PassphraseActivityCallback extends UserInputCallback {
+
+ private boolean success = false;
+
+ public boolean isSuccess() {
+ return success;
+ }
+
+ @Override
+ public void handleUserInput(Message msg) {
+ if (msg.arg1 == OKAY) {
+ success = true;
+ } else {
+ success = false;
+ }
+ }
+ };
+
+ /**
+ * Search database for key ids based on emails.
+ *
+ * @param encryptionUserIds
+ * @return
+ */
+ private long[] getKeyIdsFromEmails(String[] encryptionUserIds, boolean allowUserInteraction)
+ throws UserInteractionRequiredException {
+ // find key ids to given emails in database
+ ArrayList<Long> keyIds = new ArrayList<Long>();
+
+ boolean missingUserIdsCheck = false;
+ boolean dublicateUserIdsCheck = false;
+ ArrayList<String> missingUserIds = new ArrayList<String>();
+ ArrayList<String> dublicateUserIds = new ArrayList<String>();
+
+ for (String email : encryptionUserIds) {
+ Uri uri = KeychainContract.KeyRings.buildPublicKeyRingsByEmailsUri(email);
+ Cursor cur = getContentResolver().query(uri, null, null, null, null);
+ if (cur.moveToFirst()) {
+ long id = cur.getLong(cur.getColumnIndex(KeychainContract.KeyRings.MASTER_KEY_ID));
+ keyIds.add(id);
+ } else {
+ missingUserIdsCheck = true;
+ missingUserIds.add(email);
+ Log.d(Constants.TAG, "user id missing");
+ }
+ if (cur.moveToNext()) {
+ dublicateUserIdsCheck = true;
+ dublicateUserIds.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 (allowUserInteraction && (missingUserIdsCheck || dublicateUserIdsCheck)) {
+ SelectPubKeysActivityCallback callback = new SelectPubKeysActivityCallback();
+
+ Bundle extras = new Bundle();
+ extras.putLongArray(RemoteServiceActivity.EXTRA_SELECTED_MASTER_KEY_IDS, keyIdsArray);
+ extras.putStringArrayList(RemoteServiceActivity.EXTRA_MISSING_USER_IDS, missingUserIds);
+ extras.putStringArrayList(RemoteServiceActivity.EXTRA_DUBLICATE_USER_IDS,
+ dublicateUserIds);
+
+ pauseAndStartUserInteraction(RemoteServiceActivity.ACTION_SELECT_PUB_KEYS, callback,
+ extras);
+
+ if (callback.isSuccess()) {
+ Log.d(Constants.TAG, "New selection of pub keys!");
+ keyIdsArray = callback.getPubKeyIds();
+ } else {
+ Log.d(Constants.TAG, "Pub key selection canceled!");
+ return null;
+ }
+ }
+
+ // if no user interaction is allow throw exceptions on duplicate or missing pub keys
+ if (!allowUserInteraction) {
+ if (missingUserIdsCheck)
+ throw new UserInteractionRequiredException(
+ "Pub keys for these user ids are missing:" + missingUserIds.toString());
+ if (dublicateUserIdsCheck)
+ throw new UserInteractionRequiredException(
+ "More than one pub key with these user ids exist:"
+ + dublicateUserIds.toString());
+ }
+
+ if (keyIdsArray.length == 0) {
+ return null;
+ }
+ return keyIdsArray;
+ }
+
+ public class SelectPubKeysActivityCallback extends UserInputCallback {
+ public static final String PUB_KEY_IDS = "pub_key_ids";
+
+ private boolean success = false;
+ private long[] pubKeyIds;
+
+ public boolean isSuccess() {
+ return success;
+ }
+
+ public long[] getPubKeyIds() {
+ return pubKeyIds;
+ }
+
+ @Override
+ public void handleUserInput(Message msg) {
+ if (msg.arg1 == OKAY) {
+ success = true;
+ pubKeyIds = msg.getData().getLongArray(PUB_KEY_IDS);
+ } else {
+ success = false;
+ }
+ }
+ };
+
+ private synchronized void getKeyIdsSafe(String[] userIds, boolean allowUserInteraction,
+ IOpenPgpKeyIdsCallback callback, AppSettings appSettings) {
+ try {
+ long[] keyIds = getKeyIdsFromEmails(userIds, allowUserInteraction);
+ if (keyIds == null) {
+ throw new NoUserIdsException("No user ids!");
+ }
+
+ callback.onSuccess(keyIds);
+ } catch (UserInteractionRequiredException e) {
+ callbackOpenPgpError(callback, OpenPgpError.USER_INTERACTION_REQUIRED, e.getMessage());
+ } catch (NoUserIdsException e) {
+ callbackOpenPgpError(callback, OpenPgpError.NO_USER_IDS, e.getMessage());
+ } catch (Exception e) {
+ callbackOpenPgpError(callback, OpenPgpError.GENERIC_ERROR, e.getMessage());
+ }
+ }
+
+ private synchronized void encryptAndSignSafe(OpenPgpData inputData,
+ final OpenPgpData outputData, long[] keyIds, boolean allowUserInteraction,
+ IOpenPgpCallback callback, AppSettings appSettings, boolean sign) {
+ try {
+ // TODO: other options of OpenPgpData!
+ byte[] inputBytes = getInput(inputData);
+ boolean asciiArmor = false;
+ if (outputData.getType() == OpenPgpData.TYPE_STRING) {
+ asciiArmor = true;
+ }
+
+ // add own key for encryption
+ keyIds = Arrays.copyOf(keyIds, keyIds.length + 1);
+ keyIds[keyIds.length - 1] = appSettings.getKeyId();
+
+ // build InputData and write into OutputStream
+ InputStream inputStream = new ByteArrayInputStream(inputBytes);
+ long inputLength = inputBytes.length;
+ InputData inputDt = new InputData(inputStream, inputLength);
+
+ OutputStream outputStream = new ByteArrayOutputStream();
+
+ PgpOperation operation = new PgpOperation(getContext(), null, inputDt, outputStream);
+ if (sign) {
+ String passphrase = getCachedPassphrase(appSettings.getKeyId(),
+ allowUserInteraction);
+ if (passphrase == null) {
+ throw new WrongPassphraseException("No or wrong passphrase!");
+ }
+
+ operation.signAndEncrypt(asciiArmor, appSettings.getCompression(), keyIds, null,
+ appSettings.getEncryptionAlgorithm(), appSettings.getKeyId(),
+ appSettings.getHashAlgorithm(), true, passphrase);
+ } else {
+ operation.signAndEncrypt(asciiArmor, appSettings.getCompression(), keyIds, null,
+ appSettings.getEncryptionAlgorithm(), Id.key.none,
+ appSettings.getHashAlgorithm(), true, null);
+ }
+
+ outputStream.close();
+
+ byte[] outputBytes = ((ByteArrayOutputStream) outputStream).toByteArray();
+
+ OpenPgpData output = null;
+ if (asciiArmor) {
+ output = new OpenPgpData(new String(outputBytes));
+ } else {
+ output = new OpenPgpData(outputBytes);
+ }
+
+ // return over handler on client side
+ callback.onSuccess(output, null);
+ } catch (UserInteractionRequiredException e) {
+ callbackOpenPgpError(callback, OpenPgpError.USER_INTERACTION_REQUIRED, e.getMessage());
+ } catch (WrongPassphraseException e) {
+ callbackOpenPgpError(callback, OpenPgpError.NO_OR_WRONG_PASSPHRASE, e.getMessage());
+ } catch (Exception e) {
+ callbackOpenPgpError(callback, OpenPgpError.GENERIC_ERROR, e.getMessage());
+ }
+ }
+
+ // TODO: asciiArmor?!
+ private void signSafe(byte[] inputBytes, boolean allowUserInteraction,
+ IOpenPgpCallback callback, AppSettings appSettings) {
+ try {
+ // build InputData and write into OutputStream
+ InputStream inputStream = new ByteArrayInputStream(inputBytes);
+ long inputLength = inputBytes.length;
+ InputData inputData = new InputData(inputStream, inputLength);
+
+ OutputStream outputStream = new ByteArrayOutputStream();
+
+ String passphrase = getCachedPassphrase(appSettings.getKeyId(), allowUserInteraction);
+ if (passphrase == null) {
+ throw new WrongPassphraseException("No or wrong passphrase!");
+ }
+
+ PgpOperation operation = new PgpOperation(getContext(), null, inputData, outputStream);
+ operation.signText(appSettings.getKeyId(), passphrase, appSettings.getHashAlgorithm(),
+ Preferences.getPreferences(this).getForceV3Signatures());
+
+ outputStream.close();
+
+ byte[] outputBytes = ((ByteArrayOutputStream) outputStream).toByteArray();
+ OpenPgpData output = new OpenPgpData(new String(outputBytes));
+
+ // return over handler on client side
+ callback.onSuccess(output, null);
+ } catch (UserInteractionRequiredException e) {
+ callbackOpenPgpError(callback, OpenPgpError.USER_INTERACTION_REQUIRED, e.getMessage());
+ } catch (WrongPassphraseException e) {
+ callbackOpenPgpError(callback, OpenPgpError.NO_OR_WRONG_PASSPHRASE, e.getMessage());
+ } catch (Exception e) {
+ callbackOpenPgpError(callback, OpenPgpError.GENERIC_ERROR, e.getMessage());
+ }
+ }
+
+ private synchronized void decryptAndVerifySafe(byte[] inputBytes, boolean allowUserInteraction,
+ IOpenPgpCallback callback, AppSettings appSettings) {
+ try {
+ // TODO: this is not really needed
+ // checked if it is text with BEGIN and END tags
+ String message = new String(inputBytes);
+ Log.d(Constants.TAG, "in: " + message);
+ boolean signedOnly = false;
+ Matcher matcher = PgpHelper.PGP_MESSAGE.matcher(message);
+ if (matcher.matches()) {
+ Log.d(Constants.TAG, "PGP_MESSAGE matched");
+ message = matcher.group(1);
+ // replace non breakable spaces
+ message = message.replaceAll("\\xa0", " ");
+
+ // overwrite inputBytes
+ inputBytes = message.getBytes();
+ } else {
+ matcher = PgpHelper.PGP_SIGNED_MESSAGE.matcher(message);
+ if (matcher.matches()) {
+ signedOnly = true;
+ Log.d(Constants.TAG, "PGP_SIGNED_MESSAGE matched");
+ message = matcher.group(1);
+ // replace non breakable spaces
+ message = message.replaceAll("\\xa0", " ");
+
+ // overwrite inputBytes
+ inputBytes = message.getBytes();
+ } else {
+ Log.d(Constants.TAG, "Nothing matched! Binary?");
+ }
+ }
+ // END TODO
+
+ Log.d(Constants.TAG, "in: " + new String(inputBytes));
+
+ // TODO: This allows to decrypt messages with ALL secret keys, not only the one for the
+ // app, Fix this?
+
+ String passphrase = null;
+ if (!signedOnly) {
+ // BEGIN Get key
+ // TODO: this input stream is consumed after PgpMain.getDecryptionKeyId()... do it
+ // better!
+ InputStream inputStream2 = new ByteArrayInputStream(inputBytes);
+
+ // TODO: duplicates functions from DecryptActivity!
+ long secretKeyId;
+ try {
+ if (inputStream2.markSupported()) {
+ // should probably set this to the max size of two
+ // pgpF objects, if it even needs to be anything other
+ // than 0.
+ inputStream2.mark(200);
+ }
+ secretKeyId = PgpHelper.getDecryptionKeyId(this, inputStream2);
+ if (secretKeyId == Id.key.none) {
+ throw new PgpGeneralException(getString(R.string.error_no_secret_key_found));
+ }
+ } catch (NoAsymmetricEncryptionException e) {
+ if (inputStream2.markSupported()) {
+ inputStream2.reset();
+ }
+ secretKeyId = Id.key.symmetric;
+ if (!PgpOperation.hasSymmetricEncryption(this, inputStream2)) {
+ throw new PgpGeneralException(
+ getString(R.string.error_no_known_encryption_found));
+ }
+ // we do not support symmetric decryption from the API!
+ throw new Exception("Symmetric decryption is not supported!");
+ }
+
+ Log.d(Constants.TAG, "secretKeyId " + secretKeyId);
+
+ passphrase = getCachedPassphrase(secretKeyId, allowUserInteraction);
+ if (passphrase == null) {
+ throw new WrongPassphraseException("No or wrong passphrase!");
+ }
+ }
+
+ // build InputData and write into OutputStream
+ InputStream inputStream = new ByteArrayInputStream(inputBytes);
+ long inputLength = inputBytes.length;
+ InputData inputData = new InputData(inputStream, inputLength);
+
+ OutputStream outputStream = new ByteArrayOutputStream();
+
+ Bundle outputBundle;
+ PgpOperation operation = new PgpOperation(getContext(), null, inputData, outputStream);
+ if (signedOnly) {
+ // TODO: download missing keys from keyserver?
+ outputBundle = operation.verifyText(false);
+ } else {
+ outputBundle = operation.decryptAndVerify(passphrase, false);
+ }
+
+ outputStream.close();
+
+ byte[] outputBytes = ((ByteArrayOutputStream) outputStream).toByteArray();
+
+ // get signature informations from bundle
+ boolean signature = outputBundle.getBoolean(KeychainIntentService.RESULT_SIGNATURE);
+
+ OpenPgpSignatureResult sigResult = null;
+ if (signature) {
+ long signatureKeyId = outputBundle
+ .getLong(KeychainIntentService.RESULT_SIGNATURE_KEY_ID);
+ String signatureUserId = outputBundle
+ .getString(KeychainIntentService.RESULT_SIGNATURE_USER_ID);
+ boolean signatureSuccess = outputBundle
+ .getBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS);
+ boolean signatureUnknown = outputBundle
+ .getBoolean(KeychainIntentService.RESULT_SIGNATURE_UNKNOWN);
+
+ int signatureStatus = OpenPgpSignatureResult.SIGNATURE_ERROR;
+ if (signatureSuccess) {
+ signatureStatus = OpenPgpSignatureResult.SIGNATURE_SUCCESS_TRUSTED;
+ } else if (signatureUnknown) {
+ signatureStatus = OpenPgpSignatureResult.SIGNATURE_UNKNOWN_PUB_KEY;
+ }
+
+ sigResult = new OpenPgpSignatureResult(signatureStatus, signatureUserId,
+ signedOnly, signatureKeyId);
+ }
+ OpenPgpData output = new OpenPgpData(new String(outputBytes));
+
+ // return over handler on client side
+ callback.onSuccess(output, sigResult);
+ } catch (UserInteractionRequiredException e) {
+ callbackOpenPgpError(callback, OpenPgpError.USER_INTERACTION_REQUIRED, e.getMessage());
+ } catch (WrongPassphraseException e) {
+ callbackOpenPgpError(callback, OpenPgpError.NO_OR_WRONG_PASSPHRASE, e.getMessage());
+ } catch (Exception e) {
+ callbackOpenPgpError(callback, OpenPgpError.GENERIC_ERROR, e.getMessage());
+ }
+ }
+
+ /**
+ * Returns error to IOpenPgpCallback
+ *
+ * @param callback
+ * @param errorId
+ * @param message
+ */
+ private void callbackOpenPgpError(IOpenPgpCallback callback, int errorId, String message) {
+ try {
+ callback.onError(new OpenPgpError(0, message));
+ } catch (Exception t) {
+ Log.e(Constants.TAG,
+ "Exception while returning OpenPgpError to client via callback.onError()", t);
+ }
+ }
+
+ private void callbackOpenPgpError(IOpenPgpKeyIdsCallback callback, int errorId, String message) {
+ try {
+ callback.onError(new OpenPgpError(0, message));
+ } catch (Exception t) {
+ Log.e(Constants.TAG,
+ "Exception while returning OpenPgpError to client via callback.onError()", t);
+ }
+ }
+
+ private final IOpenPgpService.Stub mBinder = new IOpenPgpService.Stub() {
+
+ @Override
+ public void encrypt(final OpenPgpData input, final OpenPgpData output, final long[] keyIds,
+ final IOpenPgpCallback callback) throws RemoteException {
+ final AppSettings settings = getAppSettings();
+
+ Runnable r = new Runnable() {
+ @Override
+ public void run() {
+ encryptAndSignSafe(input, output, keyIds, true, callback, settings, false);
+ }
+ };
+
+ checkAndEnqueue(r);
+ }
+
+ @Override
+ public void signAndEncrypt(final OpenPgpData input, final OpenPgpData output,
+ final long[] keyIds, final IOpenPgpCallback callback) throws RemoteException {
+ final AppSettings settings = getAppSettings();
+
+ Runnable r = new Runnable() {
+ @Override
+ public void run() {
+ encryptAndSignSafe(input, output, keyIds, true, callback, settings, true);
+ }
+ };
+
+ checkAndEnqueue(r);
+ }
+
+ @Override
+ public void sign(final OpenPgpData input, final OpenPgpData output,
+ final IOpenPgpCallback callback) throws RemoteException {
+ final AppSettings settings = getAppSettings();
+
+ Runnable r = new Runnable() {
+ @Override
+ public void run() {
+ signSafe(getInput(input), true, callback, settings);
+ }
+ };
+
+ checkAndEnqueue(r);
+ }
+
+ @Override
+ public void decryptAndVerify(final OpenPgpData input, final OpenPgpData output,
+ final IOpenPgpCallback callback) throws RemoteException {
+
+ final AppSettings settings = getAppSettings();
+
+ Runnable r = new Runnable() {
+ @Override
+ public void run() {
+ decryptAndVerifySafe(getInput(input), true, callback, settings);
+ }
+ };
+
+ checkAndEnqueue(r);
+ }
+
+ @Override
+ public void getKeyIds(final String[] userIds, final boolean allowUserInteraction,
+ final IOpenPgpKeyIdsCallback callback) throws RemoteException {
+
+ final AppSettings settings = getAppSettings();
+
+ Runnable r = new Runnable() {
+ @Override
+ public void run() {
+ getKeyIdsSafe(userIds, allowUserInteraction, callback, settings);
+ }
+ };
+
+ checkAndEnqueue(r);
+ }
+
+ };
+
+ private static byte[] getInput(OpenPgpData data) {
+ // TODO: support Uri and ParcelFileDescriptor
+
+ byte[] inBytes = null;
+ switch (data.getType()) {
+ case OpenPgpData.TYPE_STRING:
+ inBytes = data.getString().getBytes();
+ break;
+
+ case OpenPgpData.TYPE_BYTE_ARRAY:
+ inBytes = data.getBytes();
+ break;
+
+ default:
+ Log.e(Constants.TAG, "Uri and ParcelFileDescriptor not supported right now!");
+ break;
+ }
+
+ return inBytes;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mBinder;
+ }
+
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/RegisteredAppsAdapter.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/RegisteredAppsAdapter.java
new file mode 100644
index 000000000..477ee04d0
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/RegisteredAppsAdapter.java
@@ -0,0 +1,76 @@
+/*
+ * 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.service.remote;
+
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+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.ImageView;
+import android.widget.TextView;
+
+public class RegisteredAppsAdapter extends CursorAdapter {
+
+ private LayoutInflater mInflater;
+ private PackageManager pm;
+
+ public RegisteredAppsAdapter(Context context, Cursor c, int flags) {
+ super(context, c, flags);
+
+ mInflater = LayoutInflater.from(context);
+ pm = context.getApplicationContext().getPackageManager();
+ }
+
+ @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 = pm.getApplicationInfo(packageName, 0);
+
+ text.setText(pm.getApplicationLabel(ai));
+ icon.setImageDrawable(pm.getApplicationIcon(ai));
+ } catch (final 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/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/RegisteredAppsListActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/RegisteredAppsListActivity.java
new file mode 100644
index 000000000..3c553fff5
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/RegisteredAppsListActivity.java
@@ -0,0 +1,36 @@
+/*
+ * 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.service.remote;
+
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.ui.DrawerActivity;
+
+import android.os.Bundle;
+
+public class RegisteredAppsListActivity extends DrawerActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.api_apps_list_activity);
+
+ setupDrawerNavigation(savedInstanceState);
+ }
+
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/RegisteredAppsListFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/RegisteredAppsListFragment.java
new file mode 100644
index 000000000..4c9d553ad
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/RegisteredAppsListFragment.java
@@ -0,0 +1,103 @@
+/*
+ * 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.service.remote;
+
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.provider.KeychainContract;
+import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
+
+import android.content.ContentUris;
+import android.content.Intent;
+import android.database.Cursor;
+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.view.View;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+
+import com.actionbarsherlock.app.SherlockListFragment;
+
+public class RegisteredAppsListFragment extends SherlockListFragment 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) {
+ // edit app settings
+ Intent intent = new Intent(getActivity(), AppSettingsActivity.class);
+ intent.setData(ContentUris.withAppendedId(KeychainContract.ApiApps.CONTENT_URI, id));
+ 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, ApiApps.PACKAGE_NAME };
+
+ 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);
+ }
+
+} \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/RemoteService.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/RemoteService.java
new file mode 100644
index 000000000..bc513d532
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/RemoteService.java
@@ -0,0 +1,354 @@
+/*
+ * 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.service.remote;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+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.service.exception.WrongPackageSignatureException;
+import org.sufficientlysecure.keychain.util.Log;
+import org.sufficientlysecure.keychain.util.PausableThreadPoolExecutor;
+
+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 android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Messenger;
+
+/**
+ * Abstract service class for remote APIs that handle app registration and user input.
+ */
+public abstract class RemoteService extends Service {
+ Context mContext;
+
+ private final ArrayBlockingQueue<Runnable> mPoolQueue = new ArrayBlockingQueue<Runnable>(100);
+ // TODO: Are these parameters okay?
+ private PausableThreadPoolExecutor mThreadPool = new PausableThreadPoolExecutor(2, 4, 10,
+ TimeUnit.SECONDS, mPoolQueue);
+
+ private final Object userInputLock = new Object();
+
+ /**
+ * Override handleUserInput() to handle OKAY (1) and CANCEL (0). After handling the waiting
+ * threads will be notified and the queue resumed
+ */
+ protected class UserInputCallback extends BaseCallback {
+
+ public void handleUserInput(Message msg) {
+ }
+
+ @Override
+ public boolean handleMessage(Message msg) {
+ handleUserInput(msg);
+
+ // resume
+ synchronized (userInputLock) {
+ userInputLock.notifyAll();
+ }
+ mThreadPool.resume();
+ return true;
+ }
+
+ }
+
+ /**
+ * Extends Handler.Callback with OKAY (1), CANCEL (0) variables
+ */
+ private class BaseCallback implements Handler.Callback {
+ public static final int OKAY = 1;
+ public static final int CANCEL = 0;
+
+ @Override
+ public boolean handleMessage(Message msg) {
+ return false;
+ }
+
+ }
+
+ public Context getContext() {
+ return mContext;
+ }
+
+ /**
+ * Should be used from Stub implementations of AIDL interfaces to enqueue a runnable for
+ * execution
+ *
+ * @param r
+ */
+ protected void checkAndEnqueue(Runnable r) {
+ try {
+ if (isCallerAllowed(false)) {
+ mThreadPool.execute(r);
+
+ Log.d(Constants.TAG, "Enqueued runnable…");
+ } else {
+ String[] callingPackages = getPackageManager().getPackagesForUid(
+ Binder.getCallingUid());
+ // TODO: currently simply uses first entry
+ String packageName = callingPackages[0];
+
+ byte[] packageSignature;
+ try {
+ packageSignature = getPackageSignature(packageName);
+ } catch (NameNotFoundException e) {
+ Log.e(Constants.TAG, "Should not happen, returning!", e);
+ return;
+ }
+ Log.e(Constants.TAG,
+ "Not allowed to use service! Starting activity for registration!");
+ Bundle extras = new Bundle();
+ extras.putString(RemoteServiceActivity.EXTRA_PACKAGE_NAME, packageName);
+ extras.putByteArray(RemoteServiceActivity.EXTRA_PACKAGE_SIGNATURE, packageSignature);
+ RegisterActivityCallback callback = new RegisterActivityCallback();
+
+ pauseAndStartUserInteraction(RemoteServiceActivity.ACTION_REGISTER, callback,
+ extras);
+
+ if (callback.isAllowed()) {
+ mThreadPool.execute(r);
+ Log.d(Constants.TAG, "Enqueued runnable…");
+ } else {
+ Log.d(Constants.TAG, "User disallowed app!");
+ }
+ }
+ } catch (WrongPackageSignatureException e) {
+ Log.e(Constants.TAG, e.getMessage());
+
+ Bundle extras = new Bundle();
+ extras.putString(RemoteServiceActivity.EXTRA_ERROR_MESSAGE,
+ getString(R.string.api_error_wrong_signature));
+ pauseAndStartUserInteraction(RemoteServiceActivity.ACTION_ERROR_MESSAGE, null, extras);
+ }
+ }
+
+ 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;
+ }
+
+ /**
+ * Locks current thread and pauses execution of runnables and starts activity for user input
+ *
+ * @param action
+ * @param messenger
+ * @param extras
+ */
+ protected void pauseAndStartUserInteraction(String action, BaseCallback callback, Bundle extras) {
+ synchronized (userInputLock) {
+ mThreadPool.pause();
+
+ Log.d(Constants.TAG, "starting activity...");
+ Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.setAction(action);
+
+ Messenger messenger = new Messenger(new Handler(getMainLooper(), callback));
+
+ extras.putParcelable(RemoteServiceActivity.EXTRA_MESSENGER, messenger);
+ intent.putExtras(extras);
+
+ startActivity(intent);
+
+ // lock current thread for user input
+ try {
+ userInputLock.wait();
+ } catch (InterruptedException e) {
+ Log.e(Constants.TAG, "CryptoService", e);
+ }
+ }
+ }
+
+ /**
+ * Retrieves AppSettings from database for the application calling this remote service
+ *
+ * @return
+ */
+ protected AppSettings getAppSettings() {
+ String[] callingPackages = getPackageManager().getPackagesForUid(Binder.getCallingUid());
+
+ // get app settings for this package
+ for (int i = 0; i < callingPackages.length; i++) {
+ String currentPkg = callingPackages[i];
+
+ Uri uri = KeychainContract.ApiApps.buildByPackageNameUri(currentPkg);
+
+ AppSettings settings = ProviderHelper.getApiAppSettings(this, uri);
+
+ if (settings != null)
+ return settings;
+ }
+
+ return null;
+ }
+
+ class RegisterActivityCallback extends BaseCallback {
+ public static final String PACKAGE_NAME = "package_name";
+
+ private boolean allowed = false;
+ private String packageName;
+
+ public boolean isAllowed() {
+ return allowed;
+ }
+
+ public String getPackageName() {
+ return packageName;
+ }
+
+ @Override
+ public boolean handleMessage(Message msg) {
+ if (msg.arg1 == OKAY) {
+ allowed = true;
+ packageName = msg.getData().getString(PACKAGE_NAME);
+
+ // resume threads
+ try {
+ if (isPackageAllowed(packageName)) {
+ synchronized (userInputLock) {
+ userInputLock.notifyAll();
+ }
+ mThreadPool.resume();
+ } else {
+ // Should not happen!
+ Log.e(Constants.TAG, "Should not happen! Emergency shutdown!");
+ mThreadPool.shutdownNow();
+ }
+ } catch (WrongPackageSignatureException e) {
+ Log.e(Constants.TAG, e.getMessage());
+
+ Bundle extras = new Bundle();
+ extras.putString(RemoteServiceActivity.EXTRA_ERROR_MESSAGE,
+ getString(R.string.api_error_wrong_signature));
+ pauseAndStartUserInteraction(RemoteServiceActivity.ACTION_ERROR_MESSAGE, null,
+ extras);
+ }
+ } else {
+ allowed = false;
+
+ synchronized (userInputLock) {
+ userInputLock.notifyAll();
+ }
+ mThreadPool.resume();
+ }
+ return true;
+ }
+
+ }
+
+ /**
+ * 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, "Caller 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, "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)");
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mContext = this;
+ }
+
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/RemoteServiceActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/RemoteServiceActivity.java
new file mode 100644
index 000000000..de07989d8
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/RemoteServiceActivity.java
@@ -0,0 +1,352 @@
+/*
+ * 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.service.remote;
+
+import java.util.ArrayList;
+
+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.pgp.exception.PgpGeneralException;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.ui.SelectPublicKeyFragment;
+import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
+import org.sufficientlysecure.keychain.util.Log;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.view.View;
+import android.widget.Toast;
+
+import com.actionbarsherlock.app.SherlockFragmentActivity;
+
+public class RemoteServiceActivity extends SherlockFragmentActivity {
+
+ public static final String ACTION_REGISTER = Constants.INTENT_PREFIX + "API_ACTIVITY_REGISTER";
+ 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";
+
+ // 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";
+ // 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";
+
+ private Messenger mMessenger;
+
+ // register view
+ private AppSettingsFragment mSettingsFragment;
+ // select pub keys view
+ private SelectPublicKeyFragment mSelectFragment;
+
+ // has the user clicked one of the buttons
+ // or do we need to handle the callback in onStop()
+ private boolean finishHandled;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ handleActions(getIntent(), savedInstanceState);
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+
+ if (!finishHandled) {
+ Message msg = Message.obtain();
+ msg.arg1 = RemoteService.RegisterActivityCallback.CANCEL;
+ try {
+ mMessenger.send(msg);
+ } catch (RemoteException e) {
+ Log.e(Constants.TAG, "CryptoServiceActivity", e);
+ }
+ }
+ }
+
+ protected void handleActions(Intent intent, Bundle savedInstanceState) {
+ finishHandled = false;
+
+ String action = intent.getAction();
+ Bundle extras = intent.getExtras();
+
+ if (extras == null) {
+ extras = new Bundle();
+ }
+
+ mMessenger = extras.getParcelable(EXTRA_MESSENGER);
+
+ /**
+ * com.android.crypto actions
+ */
+ if (ACTION_REGISTER.equals(action)) {
+ final String packageName = extras.getString(EXTRA_PACKAGE_NAME);
+ final byte[] packageSignature = extras.getByteArray(EXTRA_PACKAGE_SIGNATURE);
+
+ // Inflate a "Done"/"Cancel" custom action bar view
+ ActionBarHelper.setDoneCancelView(getSupportActionBar(), R.string.api_register_allow,
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // Allow
+
+ // user needs to select a key!
+ if (mSettingsFragment.getAppSettings().getKeyId() == Id.key.none) {
+ Toast.makeText(RemoteServiceActivity.this,
+ R.string.api_register_error_select_key, Toast.LENGTH_LONG)
+ .show();
+ } else {
+ ProviderHelper.insertApiApp(RemoteServiceActivity.this,
+ mSettingsFragment.getAppSettings());
+
+ Message msg = Message.obtain();
+ msg.arg1 = RemoteService.RegisterActivityCallback.OKAY;
+ Bundle data = new Bundle();
+ data.putString(RemoteService.RegisterActivityCallback.PACKAGE_NAME,
+ packageName);
+ msg.setData(data);
+ try {
+ mMessenger.send(msg);
+ } catch (RemoteException e) {
+ Log.e(Constants.TAG, "CryptoServiceActivity", e);
+ }
+
+ finishHandled = true;
+ finish();
+ }
+ }
+ }, R.string.api_register_disallow, new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // Disallow
+
+ Message msg = Message.obtain();
+ msg.arg1 = RemoteService.RegisterActivityCallback.CANCEL;
+ try {
+ mMessenger.send(msg);
+ } catch (RemoteException e) {
+ Log.e(Constants.TAG, "CryptoServiceActivity", e);
+ }
+
+ finishHandled = true;
+ finish();
+ }
+ });
+
+ setContentView(R.layout.api_app_register_activity);
+
+ mSettingsFragment = (AppSettingsFragment) getSupportFragmentManager().findFragmentById(
+ R.id.api_app_settings_fragment);
+
+ AppSettings settings = new AppSettings(packageName, packageSignature);
+ mSettingsFragment.setAppSettings(settings);
+ } else if (ACTION_CACHE_PASSPHRASE.equals(action)) {
+ long secretKeyId = extras.getLong(EXTRA_SECRET_KEY_ID);
+
+ showPassphraseDialog(secretKeyId);
+ } 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);
+
+ String text = new 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.setDoneCancelView(getSupportActionBar(), R.string.btn_okay,
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // ok
+
+ Message msg = Message.obtain();
+ msg.arg1 = OpenPgpService.SelectPubKeysActivityCallback.OKAY;
+ Bundle data = new Bundle();
+ data.putLongArray(
+ OpenPgpService.SelectPubKeysActivityCallback.PUB_KEY_IDS,
+ mSelectFragment.getSelectedMasterKeyIds());
+ msg.setData(data);
+ try {
+ mMessenger.send(msg);
+ } catch (RemoteException e) {
+ Log.e(Constants.TAG, "CryptoServiceActivity", e);
+ }
+
+ finishHandled = true;
+ finish();
+ }
+ }, R.string.btn_do_not_save, new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // cancel
+
+ Message msg = Message.obtain();
+ msg.arg1 = OpenPgpService.SelectPubKeysActivityCallback.CANCEL;
+ ;
+ try {
+ mMessenger.send(msg);
+ } catch (RemoteException e) {
+ Log.e(Constants.TAG, "CryptoServiceActivity", e);
+ }
+
+ finishHandled = true;
+ finish();
+ }
+ });
+
+ setContentView(R.layout.api_app_select_pub_keys_activity);
+
+ // 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 = new String();
+ text += "<font color=\"red\">" + errorMessage + "</font>";
+
+ // Inflate a "Done" custom action bar view
+ ActionBarHelper.setDoneView(getSupportActionBar(), R.string.btn_okay,
+ new View.OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ finish();
+ }
+ });
+
+ setContentView(R.layout.api_app_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, "Wrong action!");
+ finish();
+ }
+ }
+
+ /**
+ * 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
+ */
+ private void showPassphraseDialog(long secretKeyId) {
+ // Message is received after passphrase is cached
+ Handler returnHandler = new Handler() {
+ @Override
+ public void handleMessage(Message message) {
+ if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) {
+ Message msg = Message.obtain();
+ msg.arg1 = OpenPgpService.PassphraseActivityCallback.OKAY;
+ try {
+ mMessenger.send(msg);
+ } catch (RemoteException e) {
+ Log.e(Constants.TAG, "CryptoServiceActivity", e);
+ }
+ } else {
+ Message msg = Message.obtain();
+ msg.arg1 = OpenPgpService.PassphraseActivityCallback.CANCEL;
+ try {
+ mMessenger.send(msg);
+ } catch (RemoteException e) {
+ Log.e(Constants.TAG, "CryptoServiceActivity", e);
+ }
+ }
+
+ finishHandled = true;
+ finish();
+ }
+ };
+
+ // Create a new Messenger for the communication back
+ Messenger messenger = new Messenger(returnHandler);
+
+ try {
+ PassphraseDialogFragment passphraseDialog = PassphraseDialogFragment.newInstance(this,
+ messenger, secretKeyId);
+
+ passphraseDialog.show(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);
+ }
+ }
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java
new file mode 100644
index 000000000..e91630316
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java
@@ -0,0 +1,846 @@
+/*
+ * 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 java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.util.regex.Matcher;
+
+import org.spongycastle.openpgp.PGPPublicKeyRing;
+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.ActionBarHelper;
+import org.sufficientlysecure.keychain.helper.FileHelper;
+import org.sufficientlysecure.keychain.pgp.PgpHelper;
+import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
+import org.sufficientlysecure.keychain.pgp.PgpOperation;
+import org.sufficientlysecure.keychain.pgp.exception.NoAsymmetricEncryptionException;
+import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
+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.dialog.DeleteFileDialogFragment;
+import org.sufficientlysecure.keychain.ui.dialog.FileDialogFragment;
+import org.sufficientlysecure.keychain.ui.dialog.LookupUnknownKeyDialogFragment;
+import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
+import org.sufficientlysecure.keychain.util.Log;
+
+import android.annotation.SuppressLint;
+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.view.View;
+import android.view.View.OnClickListener;
+import android.view.animation.AnimationUtils;
+import android.widget.CheckBox;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+import android.widget.ViewFlipper;
+
+import com.actionbarsherlock.view.Menu;
+import com.actionbarsherlock.view.MenuItem;
+import com.beardedhen.androidbootstrap.BootstrapButton;
+
+@SuppressLint("NewApi")
+public class DecryptActivity extends DrawerActivity {
+
+ /* Intents */
+ // without permission
+ public static final String ACTION_DECRYPT = Constants.INTENT_PREFIX + "DECRYPT";
+
+ /* EXTRA keys for input */
+ public static final String EXTRA_TEXT = "text";
+
+ private long mSignatureKeyId = 0;
+
+ private boolean mReturnResult = false;
+ private boolean mSignedOnly = false;
+ private boolean mAssumeSymmetricEncryption = false;
+
+ private EditText mMessage = null;
+ private LinearLayout mSignatureLayout = null;
+ private ImageView mSignatureStatusImage = null;
+ private TextView mUserId = null;
+ private TextView mUserIdRest = null;
+
+ private ViewFlipper mSource = null;
+ private TextView mSourceLabel = null;
+ private ImageView mSourcePrevious = null;
+ private ImageView mSourceNext = null;
+
+ private boolean mDecryptEnabled = true;
+ private String mDecryptString = "";
+ private boolean mReplyEnabled = true;
+ private String mReplyString = "";
+
+ private int mDecryptTarget;
+
+ private EditText mFilename = null;
+ private CheckBox mDeleteAfter = null;
+ private BootstrapButton mBrowse = null;
+
+ private String mInputFilename = null;
+ private String mOutputFilename = null;
+
+ private Uri mContentUri = null;
+ private boolean mReturnBinary = false;
+
+ private long mUnknownSignatureKeyId = 0;
+
+ private long mSecretKeyId = Id.key.none;
+
+ private FileDialogFragment mFileDialog;
+
+ private boolean mLookupUnknownKey = true;
+
+ private boolean mDecryptImmediately = false;
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+
+ if (mDecryptEnabled) {
+ menu.add(1, Id.menu.option.decrypt, 0, mDecryptString).setShowAsAction(
+ MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
+ }
+ if (mReplyEnabled) {
+ menu.add(1, Id.menu.option.reply, 1, mReplyString).setShowAsAction(
+ MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+
+ case Id.menu.option.decrypt: {
+ decryptClicked();
+
+ return true;
+ }
+ case Id.menu.option.reply: {
+ replyClicked();
+
+ return true;
+ }
+
+ default: {
+ return super.onOptionsItemSelected(item);
+ }
+ }
+ }
+
+ private void initView() {
+ mSource = (ViewFlipper) findViewById(R.id.source);
+ mSourceLabel = (TextView) findViewById(R.id.sourceLabel);
+ mSourcePrevious = (ImageView) findViewById(R.id.sourcePrevious);
+ mSourceNext = (ImageView) findViewById(R.id.sourceNext);
+
+ mSourcePrevious.setClickable(true);
+ mSourcePrevious.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ mSource.setInAnimation(AnimationUtils.loadAnimation(DecryptActivity.this,
+ R.anim.push_right_in));
+ mSource.setOutAnimation(AnimationUtils.loadAnimation(DecryptActivity.this,
+ R.anim.push_right_out));
+ mSource.showPrevious();
+ updateSource();
+ }
+ });
+
+ mSourceNext.setClickable(true);
+ OnClickListener nextSourceClickListener = new OnClickListener() {
+ public void onClick(View v) {
+ mSource.setInAnimation(AnimationUtils.loadAnimation(DecryptActivity.this,
+ R.anim.push_left_in));
+ mSource.setOutAnimation(AnimationUtils.loadAnimation(DecryptActivity.this,
+ R.anim.push_left_out));
+ mSource.showNext();
+ updateSource();
+ }
+ };
+ mSourceNext.setOnClickListener(nextSourceClickListener);
+
+ mSourceLabel.setClickable(true);
+ mSourceLabel.setOnClickListener(nextSourceClickListener);
+
+ mMessage = (EditText) findViewById(R.id.message);
+ mSignatureLayout = (LinearLayout) findViewById(R.id.signature);
+ mSignatureStatusImage = (ImageView) findViewById(R.id.ic_signature_status);
+ mUserId = (TextView) findViewById(R.id.mainUserId);
+ mUserIdRest = (TextView) findViewById(R.id.mainUserIdRest);
+
+ // measure the height of the source_file view and set the message view's min height to that,
+ // so it fills mSource fully... bit of a hack.
+ View tmp = findViewById(R.id.sourceFile);
+ tmp.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
+ int height = tmp.getMeasuredHeight();
+ mMessage.setMinimumHeight(height);
+
+ mFilename = (EditText) findViewById(R.id.filename);
+ mBrowse = (BootstrapButton) findViewById(R.id.btn_browse);
+ mBrowse.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ FileHelper.openFile(DecryptActivity.this, mFilename.getText().toString(), "*/*",
+ Id.request.filename);
+ }
+ });
+
+ mDeleteAfter = (CheckBox) findViewById(R.id.deleteAfterDecryption);
+
+ // default: message source
+ mSource.setInAnimation(null);
+ mSource.setOutAnimation(null);
+ while (mSource.getCurrentView().getId() != R.id.sourceMessage) {
+ mSource.showNext();
+ }
+ }
+
+ @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
+ handleActions(getIntent());
+
+ if (mSource.getCurrentView().getId() == R.id.sourceMessage
+ && mMessage.getText().length() == 0) {
+
+ CharSequence clipboardText = ClipboardReflection.getClipboardText(this);
+
+ String data = "";
+ if (clipboardText != null) {
+ Matcher matcher = PgpHelper.PGP_MESSAGE.matcher(clipboardText);
+ if (!matcher.matches()) {
+ matcher = PgpHelper.PGP_SIGNED_MESSAGE.matcher(clipboardText);
+ }
+ if (matcher.matches()) {
+ data = matcher.group(1);
+ mMessage.setText(data);
+ Toast.makeText(this, R.string.using_clipboard_content, Toast.LENGTH_SHORT)
+ .show();
+ }
+ }
+ }
+
+ mSignatureLayout.setVisibility(View.GONE);
+ mSignatureLayout.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ if (mSignatureKeyId == 0) {
+ return;
+ }
+ PGPPublicKeyRing key = ProviderHelper.getPGPPublicKeyRingByKeyId(
+ DecryptActivity.this, mSignatureKeyId);
+ if (key != null) {
+ Intent intent = new Intent(DecryptActivity.this, KeyServerQueryActivity.class);
+ intent.setAction(KeyServerQueryActivity.ACTION_LOOK_UP_KEY_ID);
+ intent.putExtra(KeyServerQueryActivity.EXTRA_KEY_ID, mSignatureKeyId);
+ startActivity(intent);
+ }
+ }
+ });
+
+ mReplyEnabled = false;
+
+ // build new actionbar
+ invalidateOptionsMenu();
+
+ if (mReturnResult) {
+ mSourcePrevious.setClickable(false);
+ mSourcePrevious.setEnabled(false);
+ mSourcePrevious.setVisibility(View.INVISIBLE);
+
+ mSourceNext.setClickable(false);
+ mSourceNext.setEnabled(false);
+ mSourceNext.setVisibility(View.INVISIBLE);
+
+ mSourceLabel.setClickable(false);
+ mSourceLabel.setEnabled(false);
+ }
+
+ updateSource();
+
+ if (mDecryptImmediately
+ || (mSource.getCurrentView().getId() == R.id.sourceMessage && (mMessage.getText()
+ .length() > 0 || mContentUri != null))) {
+ decryptClicked();
+ }
+ }
+
+ /**
+ * 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 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 decryption, override action and extras to later
+ // execute 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 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", " ");
+ mMessage.setText(textData);
+ } else {
+ matcher = PgpHelper.PGP_SIGNED_MESSAGE.matcher(textData);
+ if (matcher.matches()) {
+ Log.d(Constants.TAG, "PGP_SIGNED_MESSAGE matched");
+ textData = matcher.group(1);
+ // replace non breakable spaces
+ textData = textData.replaceAll("\\xa0", " ");
+ mMessage.setText(textData);
+
+ mDecryptString = getString(R.string.btn_verify);
+ // build new action bar
+ invalidateOptionsMenu();
+ } 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) {
+ mInputFilename = path;
+ mFilename.setText(mInputFilename);
+ guessOutputFilename();
+ mSource.setInAnimation(null);
+ mSource.setOutAnimation(null);
+ while (mSource.getCurrentView().getId() != R.id.sourceFile) {
+ mSource.showNext();
+ }
+ } 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!");
+ }
+ }
+
+ 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 updateSource() {
+ switch (mSource.getCurrentView().getId()) {
+ case R.id.sourceFile: {
+ mSourceLabel.setText(R.string.label_file);
+ mDecryptString = getString(R.string.btn_decrypt);
+
+ // build new action bar
+ invalidateOptionsMenu();
+ break;
+ }
+
+ case R.id.sourceMessage: {
+ mSourceLabel.setText(R.string.label_message);
+ mDecryptString = getString(R.string.btn_decrypt);
+
+ // build new action bar
+ invalidateOptionsMenu();
+ break;
+ }
+
+ default: {
+ break;
+ }
+ }
+ }
+
+ private void decryptClicked() {
+ if (mSource.getCurrentView().getId() == R.id.sourceFile) {
+ mDecryptTarget = Id.target.file;
+ } else {
+ mDecryptTarget = Id.target.message;
+ }
+ initiateDecryption();
+ }
+
+ private void initiateDecryption() {
+ if (mDecryptTarget == Id.target.file) {
+ String currentFilename = mFilename.getText().toString();
+ if (mInputFilename == null || !mInputFilename.equals(currentFilename)) {
+ guessOutputFilename();
+ }
+
+ if (mInputFilename.equals("")) {
+ Toast.makeText(this, R.string.no_file_selected, Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ if (mInputFilename.startsWith("file")) {
+ File file = new File(mInputFilename);
+ if (!file.exists() || !file.isFile()) {
+ Toast.makeText(
+ this,
+ getString(R.string.error_message,
+ getString(R.string.error_file_not_found)), Toast.LENGTH_SHORT)
+ .show();
+ return;
+ }
+ }
+ }
+
+ if (mDecryptTarget == Id.target.message) {
+ String messageData = mMessage.getText().toString();
+ Matcher matcher = PgpHelper.PGP_SIGNED_MESSAGE.matcher(messageData);
+ if (matcher.matches()) {
+ mSignedOnly = true;
+ decryptStart();
+ return;
+ }
+ }
+
+ // else treat it as an decrypted message/file
+ mSignedOnly = false;
+
+ getDecryptionKeyFromInputStream();
+
+ // if we need a symmetric passphrase or a passphrase to use a secret key ask for it
+ if (mSecretKeyId == Id.key.symmetric
+ || PassphraseCacheService.getCachedPassphrase(this, mSecretKeyId) == null) {
+ showPassphraseDialog();
+ } else {
+ if (mDecryptTarget == Id.target.file) {
+ askForOutputFilename();
+ } else {
+ decryptStart();
+ }
+ }
+ }
+
+ /**
+ * 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
+ */
+ private void showPassphraseDialog() {
+ // Message is received after passphrase is cached
+ Handler returnHandler = new Handler() {
+ @Override
+ public void handleMessage(Message message) {
+ if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) {
+ if (mDecryptTarget == Id.target.file) {
+ askForOutputFilename();
+ } else {
+ decryptStart();
+ }
+ }
+ }
+ };
+
+ // Create a new Messenger for the communication back
+ Messenger messenger = new Messenger(returnHandler);
+
+ try {
+ PassphraseDialogFragment passphraseDialog = PassphraseDialogFragment.newInstance(this,
+ messenger, mSecretKeyId);
+
+ passphraseDialog.show(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);
+ }
+ }
+
+ /**
+ * TODO: Rework function, remove global variables
+ */
+ private void getDecryptionKeyFromInputStream() {
+ InputStream inStream = null;
+ if (mContentUri != null) {
+ try {
+ inStream = getContentResolver().openInputStream(mContentUri);
+ } catch (FileNotFoundException e) {
+ Log.e(Constants.TAG, "File not found!", e);
+ Toast.makeText(this, getString(R.string.error_file_not_found, e.getMessage()),
+ Toast.LENGTH_SHORT).show();
+ }
+ } else if (mDecryptTarget == Id.target.file) {
+ // check if storage is ready
+ if (!FileHelper.isStorageMounted(mInputFilename)) {
+ Toast.makeText(this, getString(R.string.error_external_storage_not_ready),
+ Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ try {
+ inStream = new BufferedInputStream(new FileInputStream(mInputFilename));
+ } catch (FileNotFoundException e) {
+ Log.e(Constants.TAG, "File not found!", e);
+ Toast.makeText(this, getString(R.string.error_file_not_found, e.getMessage()),
+ Toast.LENGTH_SHORT).show();
+ }
+ } else {
+ inStream = new ByteArrayInputStream(mMessage.getText().toString().getBytes());
+ }
+
+ // get decryption key for this inStream
+ try {
+ try {
+ if (inStream.markSupported()) {
+ inStream.mark(200); // should probably set this to the max size of two pgpF
+ // objects, if it even needs to be anything other than 0.
+ }
+ mSecretKeyId = PgpHelper.getDecryptionKeyId(this, inStream);
+ if (mSecretKeyId == Id.key.none) {
+ throw new PgpGeneralException(getString(R.string.error_no_secret_key_found));
+ }
+ mAssumeSymmetricEncryption = false;
+ } catch (NoAsymmetricEncryptionException e) {
+ if (inStream.markSupported()) {
+ inStream.reset();
+ }
+ mSecretKeyId = Id.key.symmetric;
+ if (!PgpOperation.hasSymmetricEncryption(this, inStream)) {
+ throw new PgpGeneralException(
+ getString(R.string.error_no_known_encryption_found));
+ }
+ mAssumeSymmetricEncryption = true;
+ }
+ } catch (Exception e) {
+ Toast.makeText(this, getString(R.string.error_message, e.getMessage()),
+ Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ private void replyClicked() {
+ Intent intent = new Intent(this, EncryptActivity.class);
+ intent.setAction(EncryptActivity.ACTION_ENCRYPT);
+ String data = mMessage.getText().toString();
+ data = data.replaceAll("(?m)^", "> ");
+ data = "\n\n" + data;
+ intent.putExtra(EncryptActivity.EXTRA_TEXT, data);
+ intent.putExtra(EncryptActivity.EXTRA_SIGNATURE_KEY_ID, mSecretKeyId);
+ intent.putExtra(EncryptActivity.EXTRA_ENCRYPTION_KEY_IDS, new long[] { mSignatureKeyId });
+ startActivity(intent);
+ }
+
+ 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();
+ }
+ }
+ };
+
+ // 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(getSupportFragmentManager(), "fileDialog");
+ }
+
+ private void lookupUnknownKey(long unknownKeyId) {
+ // Message is received after passphrase is cached
+ Handler returnHandler = new Handler() {
+ @Override
+ public void handleMessage(Message message) {
+ if (message.what == LookupUnknownKeyDialogFragment.MESSAGE_OKAY) {
+ // the result is handled by onActivityResult() as LookupUnknownKeyDialogFragment
+ // starts a new Intent which then returns data
+ } else if (message.what == LookupUnknownKeyDialogFragment.MESSAGE_CANCEL) {
+ // decrypt again, but don't lookup unknown keys!
+ mLookupUnknownKey = false;
+ decryptStart();
+ }
+ }
+ };
+
+ // Create a new Messenger for the communication back
+ Messenger messenger = new Messenger(returnHandler);
+
+ LookupUnknownKeyDialogFragment lookupKeyDialog = LookupUnknownKeyDialogFragment
+ .newInstance(messenger, unknownKeyId);
+
+ lookupKeyDialog.show(getSupportFragmentManager(), "unknownKeyDialog");
+ }
+
+ private void decryptStart() {
+ Log.d(Constants.TAG, "decryptStart");
+
+ // Send all information needed to service to decrypt in other thread
+ Intent intent = new Intent(this, KeychainIntentService.class);
+
+ // fill values for this action
+ Bundle data = new Bundle();
+
+ intent.setAction(KeychainIntentService.ACTION_DECRYPT_VERIFY);
+
+ // choose action based on input: decrypt stream, file or bytes
+ if (mContentUri != null) {
+ data.putInt(KeychainIntentService.TARGET, KeychainIntentService.TARGET_STREAM);
+
+ data.putParcelable(KeychainIntentService.ENCRYPT_PROVIDER_URI, mContentUri);
+ } else if (mDecryptTarget == Id.target.file) {
+ data.putInt(KeychainIntentService.TARGET, KeychainIntentService.TARGET_FILE);
+
+ Log.d(Constants.TAG, "mInputFilename=" + mInputFilename + ", mOutputFilename="
+ + mOutputFilename);
+
+ data.putString(KeychainIntentService.ENCRYPT_INPUT_FILE, mInputFilename);
+ data.putString(KeychainIntentService.ENCRYPT_OUTPUT_FILE, mOutputFilename);
+ } else {
+ data.putInt(KeychainIntentService.TARGET, KeychainIntentService.TARGET_BYTES);
+
+ String message = mMessage.getText().toString();
+ data.putByteArray(KeychainIntentService.DECRYPT_CIPHERTEXT_BYTES, message.getBytes());
+ }
+
+ data.putLong(KeychainIntentService.ENCRYPT_SECRET_KEY_ID, mSecretKeyId);
+
+ data.putBoolean(KeychainIntentService.DECRYPT_SIGNED_ONLY, mSignedOnly);
+ data.putBoolean(KeychainIntentService.DECRYPT_LOOKUP_UNKNOWN_KEY, mLookupUnknownKey);
+ data.putBoolean(KeychainIntentService.DECRYPT_RETURN_BYTES, mReturnBinary);
+ data.putBoolean(KeychainIntentService.DECRYPT_ASSUME_SYMMETRIC, mAssumeSymmetricEncryption);
+
+ intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
+
+ // Message is received after encrypting is done in ApgService
+ KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this,
+ R.string.progress_decrypting, ProgressDialog.STYLE_HORIZONTAL) {
+ public void handleMessage(Message message) {
+ // handle messages by standard ApgHandler first
+ super.handleMessage(message);
+
+ if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
+ // get returned data bundle
+ Bundle returnData = message.getData();
+
+ // if key is unknown show lookup dialog
+ if (returnData.getBoolean(KeychainIntentService.RESULT_SIGNATURE_LOOKUP_KEY)
+ && mLookupUnknownKey) {
+ mUnknownSignatureKeyId = returnData
+ .getLong(KeychainIntentService.RESULT_SIGNATURE_KEY_ID);
+ lookupUnknownKey(mUnknownSignatureKeyId);
+ return;
+ }
+
+ mSignatureKeyId = 0;
+ mSignatureLayout.setVisibility(View.GONE);
+ mReplyEnabled = false;
+
+ // build new action bar
+ invalidateOptionsMenu();
+
+ Toast.makeText(DecryptActivity.this, R.string.decryption_successful,
+ Toast.LENGTH_SHORT).show();
+ if (mReturnResult) {
+ Intent intent = new Intent();
+ intent.putExtras(returnData);
+ setResult(RESULT_OK, intent);
+ finish();
+ return;
+ }
+
+ switch (mDecryptTarget) {
+ case Id.target.message:
+ String decryptedMessage = returnData
+ .getString(KeychainIntentService.RESULT_DECRYPTED_STRING);
+ mMessage.setText(decryptedMessage);
+ mMessage.setHorizontallyScrolling(false);
+ mReplyEnabled = false;
+
+ // build new action bar
+ invalidateOptionsMenu();
+ break;
+
+ case Id.target.file:
+ if (mDeleteAfter.isChecked()) {
+ // Create and show dialog to delete original file
+ DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment
+ .newInstance(mInputFilename);
+ deleteFileDialog.show(getSupportFragmentManager(), "deleteDialog");
+ }
+ break;
+
+ default:
+ // shouldn't happen
+ break;
+
+ }
+
+ if (returnData.getBoolean(KeychainIntentService.RESULT_SIGNATURE)) {
+ String userId = returnData
+ .getString(KeychainIntentService.RESULT_SIGNATURE_USER_ID);
+ mSignatureKeyId = returnData
+ .getLong(KeychainIntentService.RESULT_SIGNATURE_KEY_ID);
+ mUserIdRest.setText("id: "
+ + PgpKeyHelper.convertKeyIdToHex(mSignatureKeyId));
+ if (userId == null) {
+ userId = getResources().getString(R.string.unknown_user_id);
+ }
+ String chunks[] = userId.split(" <", 2);
+ userId = chunks[0];
+ if (chunks.length > 1) {
+ mUserIdRest.setText("<" + chunks[1]);
+ }
+ mUserId.setText(userId);
+
+ if (returnData.getBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS)) {
+ mSignatureStatusImage.setImageResource(R.drawable.overlay_ok);
+ } else if (returnData
+ .getBoolean(KeychainIntentService.RESULT_SIGNATURE_UNKNOWN)) {
+ mSignatureStatusImage.setImageResource(R.drawable.overlay_error);
+ Toast.makeText(DecryptActivity.this,
+ R.string.unknown_signature_key_touch_to_look_up,
+ Toast.LENGTH_LONG).show();
+ } else {
+ mSignatureStatusImage.setImageResource(R.drawable.overlay_error);
+ }
+ mSignatureLayout.setVisibility(View.VISIBLE);
+ }
+ }
+ };
+ };
+
+ // 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);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ switch (requestCode) {
+ case Id.request.filename: {
+ if (resultCode == RESULT_OK && data != null) {
+ try {
+ String path = FileHelper.getPath(this, data.getData());
+ Log.d(Constants.TAG, "path=" + path);
+
+ mFilename.setText(path);
+ } catch (NullPointerException e) {
+ Log.e(Constants.TAG, "Nullpointer while retrieving path!");
+ }
+ }
+ return;
+ }
+
+ // this request is returned after LookupUnknownKeyDialogFragment started
+ // KeyServerQueryActivity and user looked uo key
+ case Id.request.look_up_key_id: {
+ Log.d(Constants.TAG, "Returning from Lookup Key...");
+ // decrypt again without lookup
+ mLookupUnknownKey = false;
+ decryptStart();
+ return;
+ }
+
+ default: {
+ break;
+ }
+ }
+
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DrawerActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DrawerActivity.java
new file mode 100644
index 000000000..ee8a01432
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DrawerActivity.java
@@ -0,0 +1,485 @@
+/*
+ * 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 org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.service.remote.RegisteredAppsListActivity;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.support.v4.app.ActionBarDrawerToggle;
+import android.support.v4.view.GravityCompat;
+import android.support.v4.widget.DrawerLayout;
+import android.view.ActionProvider;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.LayoutInflater;
+import android.view.SubMenu;
+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.actionbarsherlock.app.SherlockFragmentActivity;
+import com.actionbarsherlock.view.Menu;
+import com.actionbarsherlock.view.MenuItem;
+import com.beardedhen.androidbootstrap.FontAwesomeText;
+
+/**
+ * some fundamental ideas from https://github.com/tobykurien/SherlockNavigationDrawer
+ *
+ *
+ */
+public class DrawerActivity extends SherlockFragmentActivity {
+ private DrawerLayout mDrawerLayout;
+ private ListView mDrawerList;
+ private ActionBarDrawerToggle mDrawerToggle;
+
+ private CharSequence mDrawerTitle;
+ private CharSequence mTitle;
+
+ private static Class[] mItemsClass = new Class[] { KeyListPublicActivity.class,
+ EncryptActivity.class, DecryptActivity.class, ImportKeysActivity.class,
+ KeyListSecretActivity.class, RegisteredAppsListActivity.class };
+
+ 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);
+
+ // set a custom shadow that overlays the main content when the drawer
+ // opens
+ mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);
+
+ 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-download", getString(R.string.nav_import)),
+ new NavItem("fa-key", getString(R.string.nav_secret_keys)),
+ 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
+ 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);
+ // creates call to onPrepareOptionsMenu()
+ supportInvalidateOptionsMenu();
+ }
+
+ public void onDrawerOpened(View drawerView) {
+ mTitle = getSupportActionBar().getTitle();
+ getSupportActionBar().setTitle(mDrawerTitle);
+ // creates call to onPrepareOptionsMenu()
+ supportInvalidateOptionsMenu();
+ }
+ };
+ mDrawerLayout.setDrawerListener(mDrawerToggle);
+
+ // if (savedInstanceState == null) {
+ // selectItem(0);
+ // }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu 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);
+ }
+
+ /* Called whenever we call invalidateOptionsMenu() */
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ // If the nav drawer is open, hide action items related to the content
+ // view
+ boolean drawerOpen = mDrawerLayout.isDrawerOpen(mDrawerList);
+ // menu.findItem(R.id.action_websearch).setVisible(!drawerOpen);
+ return super.onPrepareOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(final MenuItem item) {
+ // The action bar home/up action should open or close the drawer.
+ // ActionBarDrawerToggle will take care of this.
+ if (mDrawerToggle.onOptionsItemSelected(getMenuItem(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);
+ }
+
+ // Handle action buttons
+ // switch (item.getItemId()) {
+ // case R.id.action_websearch:
+ // // create intent to perform web search for this planet
+ // Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
+ // intent.putExtra(SearchManager.QUERY, getSupportActionBar().getTitle());
+ // // catch event that there's no activity to handle intent
+ // if (intent.resolveActivity(getPackageManager()) != null) {
+ // startActivity(intent);
+ // } else {
+ // Toast.makeText(this, R.string.app_not_available, Toast.LENGTH_LONG).show();
+ // }
+ // return true;
+ // default:
+ // return super.onOptionsItemSelected(item);
+ // }
+ }
+
+ private android.view.MenuItem getMenuItem(final MenuItem item) {
+ return new android.view.MenuItem() {
+ @Override
+ public int getItemId() {
+ return item.getItemId();
+ }
+
+ public boolean isEnabled() {
+ return true;
+ }
+
+ @Override
+ public boolean collapseActionView() {
+ return false;
+ }
+
+ @Override
+ public boolean expandActionView() {
+ return false;
+ }
+
+ @Override
+ public ActionProvider getActionProvider() {
+ return null;
+ }
+
+ @Override
+ public View getActionView() {
+ return null;
+ }
+
+ @Override
+ public char getAlphabeticShortcut() {
+ return 0;
+ }
+
+ @Override
+ public int getGroupId() {
+ return 0;
+ }
+
+ @Override
+ public Drawable getIcon() {
+ return null;
+ }
+
+ @Override
+ public Intent getIntent() {
+ return null;
+ }
+
+ @Override
+ public ContextMenuInfo getMenuInfo() {
+ return null;
+ }
+
+ @Override
+ public char getNumericShortcut() {
+ return 0;
+ }
+
+ @Override
+ public int getOrder() {
+ return 0;
+ }
+
+ @Override
+ public SubMenu getSubMenu() {
+ return null;
+ }
+
+ @Override
+ public CharSequence getTitle() {
+ return null;
+ }
+
+ @Override
+ public CharSequence getTitleCondensed() {
+ return null;
+ }
+
+ @Override
+ public boolean hasSubMenu() {
+ return false;
+ }
+
+ @Override
+ public boolean isActionViewExpanded() {
+ return false;
+ }
+
+ @Override
+ public boolean isCheckable() {
+ return false;
+ }
+
+ @Override
+ public boolean isChecked() {
+ return false;
+ }
+
+ @Override
+ public boolean isVisible() {
+ return false;
+ }
+
+ @Override
+ public android.view.MenuItem setActionProvider(ActionProvider actionProvider) {
+ return null;
+ }
+
+ @Override
+ public android.view.MenuItem setActionView(View view) {
+ return null;
+ }
+
+ @Override
+ public android.view.MenuItem setActionView(int resId) {
+ return null;
+ }
+
+ @Override
+ public android.view.MenuItem setAlphabeticShortcut(char alphaChar) {
+ return null;
+ }
+
+ @Override
+ public android.view.MenuItem setCheckable(boolean checkable) {
+ return null;
+ }
+
+ @Override
+ public android.view.MenuItem setChecked(boolean checked) {
+ return null;
+ }
+
+ @Override
+ public android.view.MenuItem setEnabled(boolean enabled) {
+ return null;
+ }
+
+ @Override
+ public android.view.MenuItem setIcon(Drawable icon) {
+ return null;
+ }
+
+ @Override
+ public android.view.MenuItem setIcon(int iconRes) {
+ return null;
+ }
+
+ @Override
+ public android.view.MenuItem setIntent(Intent intent) {
+ return null;
+ }
+
+ @Override
+ public android.view.MenuItem setNumericShortcut(char numericChar) {
+ return null;
+ }
+
+ @Override
+ public android.view.MenuItem setOnActionExpandListener(OnActionExpandListener listener) {
+ return null;
+ }
+
+ @Override
+ public android.view.MenuItem setOnMenuItemClickListener(
+ OnMenuItemClickListener menuItemClickListener) {
+ return null;
+ }
+
+ @Override
+ public android.view.MenuItem setShortcut(char numericChar, char alphaChar) {
+ return null;
+ }
+
+ @Override
+ public void setShowAsAction(int actionEnum) {
+ }
+
+ @Override
+ public android.view.MenuItem setShowAsActionFlags(int actionEnum) {
+ return null;
+ }
+
+ @Override
+ public android.view.MenuItem setTitle(CharSequence title) {
+ return null;
+ }
+
+ @Override
+ public android.view.MenuItem setTitle(int title) {
+ return null;
+ }
+
+ @Override
+ public android.view.MenuItem setTitleCondensed(CharSequence title) {
+ return null;
+ }
+
+ @Override
+ public android.view.MenuItem setVisible(boolean visible) {
+ return null;
+ }
+ };
+ }
+
+ /* 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);
+ // setTitle(mDrawerTitles[position]);
+ mDrawerLayout.closeDrawer(mDrawerList);
+
+ finish();
+ overridePendingTransition(0, 0);
+
+ Intent intent = new Intent(this, mItemsClass[position]);
+ startActivity(intent);
+ // disable animation of activity start
+ overridePendingTransition(0, 0);
+ }
+
+ /**
+ * 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.
+ mDrawerToggle.syncState();
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ // Pass any configuration change to the drawer toggles
+ 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 context;
+ int layoutResourceId;
+ NavItem data[] = null;
+
+ public NavigationDrawerAdapter(Context context, int layoutResourceId, NavItem[] data) {
+ super(context, layoutResourceId, data);
+ this.layoutResourceId = layoutResourceId;
+ this.context = context;
+ this.data = data;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View row = convertView;
+ NavItemHolder holder = null;
+
+ if (row == null) {
+ LayoutInflater inflater = ((Activity) context).getLayoutInflater();
+ row = inflater.inflate(layoutResourceId, parent, false);
+
+ holder = new NavItemHolder();
+ holder.img = (FontAwesomeText) row.findViewById(R.id.drawer_item_icon);
+ holder.txtTitle = (TextView) row.findViewById(R.id.drawer_item_text);
+
+ row.setTag(holder);
+ } else {
+ holder = (NavItemHolder) row.getTag();
+ }
+
+ NavItem item = data[position];
+ holder.txtTitle.setText(item.title);
+ holder.img.setIcon(item.icon);
+
+ return row;
+ }
+
+ }
+
+ static class NavItemHolder {
+ FontAwesomeText img;
+ TextView txtTitle;
+ }
+
+} \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java
new file mode 100644
index 000000000..631672c1f
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java
@@ -0,0 +1,666 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.Iterator;
+import java.util.Vector;
+
+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.ProviderHelper;
+import org.sufficientlysecure.keychain.service.KeychainIntentService;
+import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
+import org.sufficientlysecure.keychain.service.PassphraseCacheService;
+import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment;
+import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
+import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment;
+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 android.app.ProgressDialog;
+import android.content.Context;
+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.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 android.widget.Toast;
+
+import com.actionbarsherlock.app.SherlockFragmentActivity;
+import com.actionbarsherlock.view.Menu;
+import com.actionbarsherlock.view.MenuItem;
+import com.beardedhen.androidbootstrap.BootstrapButton;
+
+public class EditKeyActivity extends SherlockFragmentActivity {
+
+ // 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";
+
+ // results when saving key
+ public static final String RESULT_EXTRA_MASTER_KEY_ID = "master_key_id";
+ public static final String RESULT_EXTRA_USER_ID = "user_id";
+
+ // EDIT
+ private Uri mDataUri;
+
+ private PGPSecretKeyRing mKeyRing = null;
+
+ private SectionView mUserIdsView;
+ private SectionView mKeysView;
+
+ private String mCurrentPassPhrase = null;
+ private String mNewPassPhrase = null;
+
+ private BootstrapButton mChangePassPhrase;
+
+ private CheckBox mNoPassphrase;
+
+ Vector<String> mUserIds;
+ Vector<PGPSecretKey> mKeys;
+ Vector<Integer> mKeysUsages;
+ boolean masterCanSign = true;
+
+ ExportHelper mExportHelper;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mExportHelper = new ExportHelper(this);
+
+ 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) {
+ // Inflate a "Done"/"Cancel" custom action bar
+ ActionBarHelper.setDoneCancelView(getSupportActionBar(), R.string.btn_save,
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ saveClicked();
+ }
+ }, R.string.btn_do_not_save, new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ cancelClicked();
+ }
+ });
+
+ Bundle extras = intent.getExtras();
+
+ mCurrentPassPhrase = "";
+
+ 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
+ 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 ApgService
+ KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(
+ this, R.string.progress_generating, ProgressDialog.STYLE_SPINNER) {
+ public void handleMessage(Message message) {
+ // handle messages by standard ApgHandler first
+ super.handleMessage(message);
+
+ if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
+ // get new key from data bundle returned from service
+ Bundle data = message.getData();
+ PGPSecretKeyRing masterKeyRing = (PGPSecretKeyRing) PgpConversionHelper
+ .BytesToPGPKeyRing(data
+ .getByteArray(KeychainIntentService.RESULT_NEW_KEY));
+ PGPSecretKeyRing subKeyRing = (PGPSecretKeyRing) PgpConversionHelper
+ .BytesToPGPKeyRing(data
+ .getByteArray(KeychainIntentService.RESULT_NEW_KEY2));
+
+ // add master key
+ @SuppressWarnings("unchecked")
+ Iterator<PGPSecretKey> masterIt = masterKeyRing.getSecretKeys();
+ mKeys.add(masterIt.next());
+ mKeysUsages.add(Id.choice.usage.sign_only);
+
+ // add sub key
+ @SuppressWarnings("unchecked")
+ Iterator<PGPSecretKey> subIt = subKeyRing.getSecretKeys();
+ subIt.next(); // masterkey
+ mKeys.add(subIt.next());
+ mKeysUsages.add(Id.choice.usage.encrypt_only);
+
+ buildLayout();
+ }
+ };
+ };
+
+ // 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();
+ }
+ }
+
+ /**
+ * Handle intent action to edit existing key
+ *
+ * @param intent
+ */
+ private void handleActionEditKey(Intent intent) {
+ // Inflate a "Done"/"Cancel" custom action bar
+ ActionBarHelper.setDoneView(getSupportActionBar(), R.string.btn_save,
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ saveClicked();
+ }
+ });
+
+ mDataUri = intent.getData();
+ if (mDataUri == null) {
+ Log.e(Constants.TAG, "Intent data missing. Should be Uri of key!");
+ finish();
+ return;
+ } else {
+ Log.d(Constants.TAG, "uri: " + mDataUri);
+
+ long keyRingRowId = Long.valueOf(mDataUri.getLastPathSegment());
+
+ // get master key id using row id
+ long masterKeyId = ProviderHelper.getSecretMasterKeyId(this, keyRingRowId);
+
+ boolean masterCanSign = ProviderHelper.getSecretMasterKeyCanSign(this, keyRingRowId);
+
+ String passphrase = PassphraseCacheService.getCachedPassphrase(this, masterKeyId);
+ if (passphrase == null) {
+ showPassphraseDialog(masterKeyId, masterCanSign);
+ } else {
+ // PgpMain.setEditPassPhrase(passPhrase);
+ mCurrentPassPhrase = passphrase;
+
+ finallyEdit(masterKeyId, masterCanSign);
+ }
+ }
+ }
+
+ private void showPassphraseDialog(final long masterKeyId, final boolean masterCanSign) {
+ // Message is received after passphrase is cached
+ Handler returnHandler = new Handler() {
+ @Override
+ public void handleMessage(Message message) {
+ if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) {
+ String passPhrase = PassphraseCacheService.getCachedPassphrase(
+ EditKeyActivity.this, masterKeyId);
+ mCurrentPassPhrase = passPhrase;
+ finallyEdit(masterKeyId, masterCanSign);
+ } else {
+ finish();
+ }
+ }
+ };
+
+ // Create a new Messenger for the communication back
+ Messenger messenger = new Messenger(returnHandler);
+
+ try {
+ PassphraseDialogFragment passphraseDialog = PassphraseDialogFragment.newInstance(
+ EditKeyActivity.this, messenger, masterKeyId);
+
+ passphraseDialog.show(getSupportFragmentManager(), "passphraseDialog");
+ } catch (PgpGeneralException e) {
+ Log.d(Constants.TAG, "No passphrase for this secret key!");
+ // send message to handler to start encryption directly
+ returnHandler.sendEmptyMessage(PassphraseDialogFragment.MESSAGE_OKAY);
+ }
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ // show menu only on edit
+ if (mDataUri != null) {
+ return super.onPrepareOptionsMenu(menu);
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ getSupportMenuInflater().inflate(R.menu.key_edit, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.menu_key_edit_cancel:
+ cancelClicked();
+ return true;
+ case R.id.menu_key_edit_export_file:
+ mExportHelper.showExportKeysDialog(mDataUri, Id.type.secret_key, Constants.path.APP_DIR
+ + "/secexport.asc");
+ return true;
+ case R.id.menu_key_edit_delete: {
+ // Message is received after key is deleted
+ Handler returnHandler = new Handler() {
+ @Override
+ public void handleMessage(Message message) {
+ if (message.what == DeleteKeyDialogFragment.MESSAGE_OKAY) {
+ setResult(RESULT_CANCELED);
+ finish();
+ }
+ }
+ };
+
+ mExportHelper.deleteKey(mDataUri, Id.type.secret_key, returnHandler);
+ return true;
+ }
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @SuppressWarnings("unchecked")
+ private void finallyEdit(final long masterKeyId, final boolean masterCanSign) {
+ if (masterKeyId != 0) {
+ PGPSecretKey masterKey = null;
+ mKeyRing = ProviderHelper.getPGPSecretKeyRingByMasterKeyId(this, masterKeyId);
+ if (mKeyRing != null) {
+ masterKey = PgpKeyHelper.getMasterKey(mKeyRing);
+ 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);
+ Toast.makeText(this, R.string.error_no_secret_key_found, Toast.LENGTH_LONG).show();
+ }
+ if (masterKey != null) {
+ for (String userId : new IterableIterator<String>(masterKey.getUserIDs())) {
+ Log.d(Constants.TAG, "Added userId " + userId);
+ mUserIds.add(userId);
+ }
+ }
+ }
+
+ // TODO: ???
+ if (mCurrentPassPhrase == null) {
+ mCurrentPassPhrase = "";
+ }
+
+ buildLayout();
+
+ if (mCurrentPassPhrase.equals("")) {
+ // 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();
+ }
+ }
+ };
+
+ // Create a new Messenger for the communication back
+ Messenger messenger = new Messenger(returnHandler);
+
+ // set title based on isPassphraseSet()
+ int title = -1;
+ if (isPassphraseSet()) {
+ title = R.string.title_change_pass_phrase;
+ } 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.
+ */
+ private void buildLayout() {
+ setContentView(R.layout.edit_key_activity);
+
+ // find views
+ mChangePassPhrase = (BootstrapButton) findViewById(R.id.edit_key_btn_change_pass_phrase);
+ 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);
+ mUserIdsView = (SectionView) inflater.inflate(R.layout.edit_key_section, container, false);
+ mUserIdsView.setType(Id.type.user_id);
+ mUserIdsView.setCanEdit(masterCanSign);
+ mUserIdsView.setUserIds(mUserIds);
+ container.addView(mUserIdsView);
+ mKeysView = (SectionView) inflater.inflate(R.layout.edit_key_section, container, false);
+ mKeysView.setType(Id.type.key);
+ mKeysView.setCanEdit(masterCanSign);
+ mKeysView.setKeys(mKeys, mKeysUsages);
+ container.addView(mKeysView);
+
+ updatePassPhraseButtonText();
+
+ mChangePassPhrase.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ showSetPassphraseDialog();
+ }
+ });
+
+ // disable passphrase when no passphrase checkobox is checked!
+ mNoPassphrase.setOnCheckedChangeListener(new OnCheckedChangeListener() {
+
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ if (isChecked) {
+ // remove passphrase
+ mNewPassPhrase = null;
+
+ mChangePassPhrase.setVisibility(View.GONE);
+ } else {
+ mChangePassPhrase.setVisibility(View.VISIBLE);
+ }
+ }
+ });
+ }
+
+ 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 ((!mCurrentPassPhrase.equals(""))
+ || (mNewPassPhrase != null && !mNewPassPhrase.equals(""))) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private void saveClicked() {
+ try {
+ if (!isPassphraseSet()) {
+ throw new PgpGeneralException(this.getString(R.string.set_a_passphrase));
+ }
+
+ // 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);
+
+ // fill values for this action
+ Bundle data = new Bundle();
+ data.putString(KeychainIntentService.SAVE_KEYRING_CURRENT_PASSPHRASE,
+ mCurrentPassPhrase);
+ data.putString(KeychainIntentService.SAVE_KEYRING_NEW_PASSPHRASE, mNewPassPhrase);
+ data.putStringArrayList(KeychainIntentService.SAVE_KEYRING_USER_IDS,
+ getUserIds(mUserIdsView));
+ ArrayList<PGPSecretKey> keys = getKeys(mKeysView);
+ data.putByteArray(KeychainIntentService.SAVE_KEYRING_KEYS,
+ PgpConversionHelper.PGPSecretKeyArrayListToBytes(keys));
+ data.putIntegerArrayList(KeychainIntentService.SAVE_KEYRING_KEYS_USAGES,
+ getKeysUsages(mKeysView));
+ data.putLong(KeychainIntentService.SAVE_KEYRING_MASTER_KEY_ID, getMasterKeyId());
+ data.putBoolean(KeychainIntentService.SAVE_KEYRING_CAN_SIGN, masterCanSign);
+
+ intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
+
+ // Message is received after saving is done in ApgService
+ KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this,
+ R.string.progress_saving, ProgressDialog.STYLE_HORIZONTAL) {
+ public void handleMessage(Message message) {
+ // handle messages by standard ApgHandler first
+ super.handleMessage(message);
+
+ if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
+ Intent data = new Intent();
+ data.putExtra(RESULT_EXTRA_MASTER_KEY_ID, getMasterKeyId());
+ ArrayList<String> userIds = null;
+ try {
+ userIds = getUserIds(mUserIdsView);
+ } catch (PgpGeneralException e) {
+ Log.e(Constants.TAG, "exception while getting user ids", e);
+ }
+ data.putExtra(RESULT_EXTRA_USER_ID, userIds.get(0));
+ 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) {
+ Toast.makeText(this, getString(R.string.error_message, e.getMessage()),
+ Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ private void cancelClicked() {
+ 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 = null;
+ try {
+ userId = editor.getValue();
+ } catch (UserIdEditor.NoNameException e) {
+ throw new PgpGeneralException(this.getString(R.string.error_user_id_needs_a_name));
+ } catch (UserIdEditor.NoEmailException e) {
+ throw new PgpGeneralException(
+ this.getString(R.string.error_user_id_needs_an_email_address));
+ } catch (UserIdEditor.InvalidEmailException e) {
+ throw new PgpGeneralException(e.getMessage());
+ }
+
+ if (userId.equals("")) {
+ continue;
+ }
+
+ 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> getKeysUsages = 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);
+ getKeysUsages.add(editor.getUsage());
+ }
+
+ return getKeysUsages;
+ }
+
+ private void updatePassPhraseButtonText() {
+ mChangePassPhrase.setText(isPassphraseSet() ? getString(R.string.btn_change_passphrase)
+ : getString(R.string.btn_set_passphrase));
+ }
+
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java
new file mode 100644
index 000000000..9ede8238d
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java
@@ -0,0 +1,1003 @@
+/*
+ * 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 java.io.File;
+import java.util.Vector;
+
+import org.spongycastle.openpgp.PGPPublicKey;
+import org.spongycastle.openpgp.PGPPublicKeyRing;
+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.compatibility.ClipboardReflection;
+import org.sufficientlysecure.keychain.helper.ActionBarHelper;
+import org.sufficientlysecure.keychain.helper.FileHelper;
+import org.sufficientlysecure.keychain.helper.Preferences;
+import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
+import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
+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.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 android.annotation.SuppressLint;
+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.view.View;
+import android.view.View.OnClickListener;
+import android.view.animation.AnimationUtils;
+import android.widget.ArrayAdapter;
+import android.widget.CheckBox;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.Spinner;
+import android.widget.TextView;
+import android.widget.Toast;
+import android.widget.ViewFlipper;
+
+import com.actionbarsherlock.view.Menu;
+import com.actionbarsherlock.view.MenuItem;
+import com.beardedhen.androidbootstrap.BootstrapButton;
+
+public class EncryptActivity extends DrawerActivity {
+
+ /* 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";
+
+ private long mEncryptionKeyIds[] = null;
+
+ private EditText mMessage = null;
+ private BootstrapButton mSelectKeysButton = null;
+
+ private boolean mEncryptEnabled = false;
+ private String mEncryptString = "";
+ private boolean mEncryptToClipboardEnabled = false;
+ private String mEncryptToClipboardString = "";
+
+ private CheckBox mSign = null;
+ private TextView mMainUserId = null;
+ private TextView mMainUserIdRest = null;
+
+ private ViewFlipper mSource = null;
+ private TextView mSourceLabel = null;
+ private ImageView mSourcePrevious = null;
+ private ImageView mSourceNext = null;
+
+ private ViewFlipper mMode = null;
+ private TextView mModeLabel = null;
+ private ImageView mModePrevious = null;
+ private ImageView mModeNext = null;
+
+ private int mEncryptTarget;
+
+ private EditText mPassPhrase = null;
+ private EditText mPassPhraseAgain = null;
+ private CheckBox mAsciiArmor = null;
+ private Spinner mFileCompression = null;
+
+ private EditText mFilename = null;
+ private CheckBox mDeleteAfter = null;
+ private BootstrapButton mBrowse = null;
+
+ private String mInputFilename = null;
+ private String mOutputFilename = null;
+
+ private boolean mAsciiArmorDemand = false;
+ private boolean mOverrideAsciiArmor = false;
+
+ private boolean mGenerateSignature = false;
+
+ private long mSecretKeyId = Id.key.none;
+
+ private FileDialogFragment mFileDialog;
+
+ /**
+ * ActionBar menu is created based on class variables to change it at runtime
+ *
+ */
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ if (mEncryptToClipboardEnabled) {
+ menu.add(1, Id.menu.option.encrypt_to_clipboard, 0, mEncryptToClipboardString)
+ .setShowAsAction(
+ MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
+ }
+ if (mEncryptEnabled) {
+ menu.add(1, Id.menu.option.encrypt, 1, mEncryptString).setShowAsAction(
+ MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+
+ case Id.menu.option.encrypt_to_clipboard:
+ encryptToClipboardClicked();
+
+ return true;
+
+ case Id.menu.option.encrypt:
+ encryptClicked();
+
+ return true;
+
+ default:
+ return super.onOptionsItemSelected(item);
+
+ }
+ }
+
+ @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());
+
+ updateView();
+ updateSource();
+ updateMode();
+
+ updateActionBarButtons();
+ }
+
+ /**
+ * 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
+ // execute 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)) {
+ mAsciiArmorDemand = extras.getBoolean(EXTRA_ASCII_ARMOR, true);
+ mOverrideAsciiArmor = true;
+ mAsciiArmor.setChecked(mAsciiArmorDemand);
+ }
+
+ 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
+ preselectKeys(signatureKeyId, encryptionKeyIds);
+
+ /**
+ * Main Actions
+ */
+ if (ACTION_ENCRYPT.equals(action) && textData != null) {
+ // encrypt text based on given extra
+
+ mMessage.setText(textData);
+ mSource.setInAnimation(null);
+ mSource.setOutAnimation(null);
+ while (mSource.getCurrentView().getId() != R.id.sourceMessage) {
+ mSource.showNext();
+ }
+ } 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) {
+ mInputFilename = path;
+ mFilename.setText(mInputFilename);
+
+ mSource.setInAnimation(null);
+ mSource.setOutAnimation(null);
+ while (mSource.getCurrentView().getId() != R.id.sourceFile) {
+ mSource.showNext();
+ }
+ } 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!");
+ }
+ }
+
+ /**
+ * 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) {
+ PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRingByMasterKeyId(this,
+ preselectedSignatureKeyId);
+ PGPSecretKey masterKey = null;
+ if (keyRing != null) {
+ masterKey = PgpKeyHelper.getMasterKey(keyRing);
+ if (masterKey != null) {
+ Vector<PGPSecretKey> signKeys = PgpKeyHelper.getUsableSigningKeys(keyRing);
+ if (signKeys.size() > 0) {
+ mSecretKeyId = masterKey.getKeyID();
+ }
+ }
+ }
+ }
+
+ if (preselectedEncryptionKeyIds != null) {
+ Vector<Long> goodIds = new Vector<Long>();
+ for (int i = 0; i < preselectedEncryptionKeyIds.length; ++i) {
+ PGPPublicKeyRing keyRing = ProviderHelper.getPGPPublicKeyRingByMasterKeyId(this,
+ preselectedEncryptionKeyIds[i]);
+ PGPPublicKey masterKey = null;
+ if (keyRing == null) {
+ continue;
+ }
+ masterKey = PgpKeyHelper.getMasterKey(keyRing);
+ if (masterKey == null) {
+ continue;
+ }
+ Vector<PGPPublicKey> encryptKeys = PgpKeyHelper.getUsableEncryptKeys(keyRing);
+ if (encryptKeys.size() == 0) {
+ continue;
+ }
+ goodIds.add(masterKey.getKeyID());
+ }
+ if (goodIds.size() > 0) {
+ mEncryptionKeyIds = new long[goodIds.size()];
+ for (int i = 0; i < goodIds.size(); ++i) {
+ mEncryptionKeyIds[i] = goodIds.get(i);
+ }
+ }
+ }
+ }
+
+ /**
+ * 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 updateSource() {
+ switch (mSource.getCurrentView().getId()) {
+ case R.id.sourceFile: {
+ mSourceLabel.setText(R.string.label_file);
+ break;
+ }
+
+ case R.id.sourceMessage: {
+ mSourceLabel.setText(R.string.label_message);
+ break;
+ }
+
+ default: {
+ break;
+ }
+ }
+ updateActionBarButtons();
+ }
+
+ /**
+ * Set ActionBar buttons based on parameters
+ *
+ * @param encryptEnabled
+ * @param encryptStringRes
+ * @param encryptToClipboardEnabled
+ * @param encryptToClipboardStringRes
+ */
+ @SuppressLint("NewApi")
+ private void setActionbarButtons(boolean encryptEnabled, int encryptStringRes,
+ boolean encryptToClipboardEnabled, int encryptToClipboardStringRes) {
+ mEncryptEnabled = encryptEnabled;
+ if (encryptEnabled) {
+ mEncryptString = getString(encryptStringRes);
+ }
+ mEncryptToClipboardEnabled = encryptToClipboardEnabled;
+ if (encryptToClipboardEnabled) {
+ mEncryptToClipboardString = getString(encryptToClipboardStringRes);
+ }
+
+ // build new action bar based on these class variables
+ invalidateOptionsMenu();
+ }
+
+ /**
+ * Update ActionBar buttons based on current selection in view
+ */
+ private void updateActionBarButtons() {
+ switch (mSource.getCurrentView().getId()) {
+ case R.id.sourceFile: {
+ setActionbarButtons(true, R.string.btn_encrypt_file, false, 0);
+
+ break;
+ }
+
+ case R.id.sourceMessage: {
+ mSourceLabel.setText(R.string.label_message);
+
+ if (mMode.getCurrentView().getId() == R.id.modeSymmetric) {
+ setActionbarButtons(true, R.string.btn_encrypt_and_send, true,
+ R.string.btn_encrypt_to_clipboard);
+ } else {
+ if (mEncryptionKeyIds == null || mEncryptionKeyIds.length == 0) {
+ if (mSecretKeyId == 0) {
+ setActionbarButtons(false, 0, false, 0);
+ } else {
+ setActionbarButtons(true, R.string.btn_sign_and_send, true,
+ R.string.btn_sign_to_clipboard);
+ }
+ } else {
+ setActionbarButtons(true, R.string.btn_encrypt_and_send, true,
+ R.string.btn_encrypt_to_clipboard);
+ }
+ }
+ break;
+ }
+
+ default: {
+ break;
+ }
+ }
+
+ }
+
+ private void updateMode() {
+ switch (mMode.getCurrentView().getId()) {
+ case R.id.modeAsymmetric: {
+ mModeLabel.setText(R.string.label_asymmetric);
+ break;
+ }
+
+ case R.id.modeSymmetric: {
+ mModeLabel.setText(R.string.label_symmetric);
+ break;
+ }
+
+ default: {
+ break;
+ }
+ }
+ updateActionBarButtons();
+ }
+
+ private void encryptToClipboardClicked() {
+ mEncryptTarget = Id.target.clipboard;
+ initiateEncryption();
+ }
+
+ private void encryptClicked() {
+ Log.d(Constants.TAG, "encryptClicked invoked!");
+
+ if (mSource.getCurrentView().getId() == R.id.sourceFile) {
+ mEncryptTarget = Id.target.file;
+ } else {
+ mEncryptTarget = Id.target.email;
+ }
+ initiateEncryption();
+ }
+
+ private void initiateEncryption() {
+ if (mEncryptTarget == Id.target.file) {
+ String currentFilename = mFilename.getText().toString();
+ if (mInputFilename == null || !mInputFilename.equals(currentFilename)) {
+ mInputFilename = mFilename.getText().toString();
+ }
+
+ mOutputFilename = guessOutputFilename(mInputFilename);
+
+ if (mInputFilename.equals("")) {
+ Toast.makeText(this, R.string.no_file_selected, Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ if (!mInputFilename.startsWith("content")) {
+ File file = new File(mInputFilename);
+ if (!file.exists() || !file.isFile()) {
+ Toast.makeText(
+ this,
+ getString(R.string.error_message,
+ getString(R.string.error_file_not_found)), Toast.LENGTH_SHORT)
+ .show();
+ return;
+ }
+ }
+ }
+
+ // symmetric encryption
+ if (mMode.getCurrentView().getId() == R.id.modeSymmetric) {
+ boolean gotPassPhrase = false;
+ String passPhrase = mPassPhrase.getText().toString();
+ String passPhraseAgain = mPassPhraseAgain.getText().toString();
+ if (!passPhrase.equals(passPhraseAgain)) {
+ Toast.makeText(this, R.string.passphrases_do_not_match, Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ gotPassPhrase = (passPhrase.length() != 0);
+ if (!gotPassPhrase) {
+ Toast.makeText(this, R.string.passphrase_must_not_be_empty, Toast.LENGTH_SHORT)
+ .show();
+ return;
+ }
+ } else {
+ boolean encryptIt = (mEncryptionKeyIds != null && mEncryptionKeyIds.length > 0);
+ // for now require at least one form of encryption for files
+ if (!encryptIt && mEncryptTarget == Id.target.file) {
+ Toast.makeText(this, R.string.select_encryption_key, Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ if (!encryptIt && mSecretKeyId == 0) {
+ Toast.makeText(this, R.string.select_encryption_or_signature_key,
+ Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ if (mSecretKeyId != 0
+ && PassphraseCacheService.getCachedPassphrase(this, mSecretKeyId) == null) {
+ showPassphraseDialog();
+
+ return;
+ }
+ }
+
+ if (mEncryptTarget == Id.target.file) {
+ showOutputFileDialog();
+ } else {
+ encryptStart();
+ }
+ }
+
+ /**
+ * Shows passphrase dialog to cache a new passphrase the user enters for using it later for
+ * encryption
+ */
+ private void showPassphraseDialog() {
+ // Message is received after passphrase is cached
+ Handler returnHandler = new Handler() {
+ @Override
+ public void handleMessage(Message message) {
+ if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) {
+ if (mEncryptTarget == Id.target.file) {
+ showOutputFileDialog();
+ } else {
+ encryptStart();
+ }
+ }
+ }
+ };
+
+ // Create a new Messenger for the communication back
+ Messenger messenger = new Messenger(returnHandler);
+
+ try {
+ PassphraseDialogFragment passphraseDialog = PassphraseDialogFragment.newInstance(
+ EncryptActivity.this, messenger, mSecretKeyId);
+
+ passphraseDialog.show(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);
+ }
+ }
+
+ 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(getSupportFragmentManager(), "fileDialog");
+ }
+
+ private void encryptStart() {
+ // Send all information needed to service to edit key in other thread
+ Intent intent = new Intent(this, KeychainIntentService.class);
+
+ // fill values for this action
+ Bundle data = new Bundle();
+
+ boolean useAsciiArmor = true;
+ long encryptionKeyIds[] = null;
+ int compressionId = 0;
+ boolean signOnly = false;
+ long mSecretKeyIdToPass = 0;
+
+ if (mMode.getCurrentView().getId() == R.id.modeSymmetric) {
+ Log.d(Constants.TAG, "Symmetric encryption enabled!");
+ String passPhrase = mPassPhrase.getText().toString();
+ if (passPhrase.length() == 0) {
+ passPhrase = null;
+ }
+ data.putString(KeychainIntentService.GENERATE_KEY_SYMMETRIC_PASSPHRASE, passPhrase);
+ } else {
+ mSecretKeyIdToPass = mSecretKeyId;
+ encryptionKeyIds = mEncryptionKeyIds;
+ signOnly = (mEncryptionKeyIds == null || mEncryptionKeyIds.length == 0);
+ }
+
+ intent.setAction(KeychainIntentService.ACTION_ENCRYPT_SIGN);
+
+ // choose default settings, target and data bundle by target
+ if (mEncryptTarget == Id.target.file) {
+ useAsciiArmor = mAsciiArmor.isChecked();
+ compressionId = ((Choice) mFileCompression.getSelectedItem()).getId();
+
+ data.putInt(KeychainIntentService.TARGET, KeychainIntentService.TARGET_FILE);
+
+ Log.d(Constants.TAG, "mInputFilename=" + mInputFilename + ", mOutputFilename="
+ + mOutputFilename);
+
+ data.putString(KeychainIntentService.ENCRYPT_INPUT_FILE, mInputFilename);
+ data.putString(KeychainIntentService.ENCRYPT_OUTPUT_FILE, mOutputFilename);
+
+ } else {
+ useAsciiArmor = true;
+ compressionId = Preferences.getPreferences(this).getDefaultMessageCompression();
+
+ data.putInt(KeychainIntentService.TARGET, KeychainIntentService.TARGET_BYTES);
+
+ String message = mMessage.getText().toString();
+ if (signOnly) {
+ fixBadCharactersForGmail(message);
+ }
+ data.putByteArray(KeychainIntentService.ENCRYPT_MESSAGE_BYTES, message.getBytes());
+ }
+
+ if (mOverrideAsciiArmor) {
+ useAsciiArmor = mAsciiArmorDemand;
+ }
+
+ data.putLong(KeychainIntentService.ENCRYPT_SECRET_KEY_ID, mSecretKeyIdToPass);
+ data.putBoolean(KeychainIntentService.ENCRYPT_USE_ASCII_ARMOR, useAsciiArmor);
+ data.putLongArray(KeychainIntentService.ENCRYPT_ENCRYPTION_KEYS_IDS, encryptionKeyIds);
+ data.putInt(KeychainIntentService.ENCRYPT_COMPRESSION_ID, compressionId);
+ data.putBoolean(KeychainIntentService.ENCRYPT_GENERATE_SIGNATURE, mGenerateSignature);
+ data.putBoolean(KeychainIntentService.ENCRYPT_SIGN_ONLY, signOnly);
+
+ intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
+
+ // Message is received after encrypting is done in ApgService
+ KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this,
+ R.string.progress_encrypting, ProgressDialog.STYLE_HORIZONTAL) {
+ public void handleMessage(Message message) {
+ // handle messages by standard ApgHandler first
+ super.handleMessage(message);
+
+ if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
+ // get returned data bundle
+ Bundle data = message.getData();
+
+ String output;
+ switch (mEncryptTarget) {
+ case Id.target.clipboard:
+ output = data.getString(KeychainIntentService.RESULT_ENCRYPTED_STRING);
+ Log.d(Constants.TAG, "output: " + output);
+ ClipboardReflection.copyToClipboard(EncryptActivity.this, output);
+ Toast.makeText(EncryptActivity.this,
+ R.string.encryption_to_clipboard_successful, Toast.LENGTH_SHORT)
+ .show();
+ break;
+
+ case Id.target.email:
+
+ output = data.getString(KeychainIntentService.RESULT_ENCRYPTED_STRING);
+ Log.d(Constants.TAG, "output: " + output);
+
+ 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)));
+ break;
+
+ case Id.target.file:
+ Toast.makeText(EncryptActivity.this, R.string.encryption_successful,
+ Toast.LENGTH_SHORT).show();
+
+ if (mDeleteAfter.isChecked()) {
+ // Create and show dialog to delete original file
+ DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment
+ .newInstance(mInputFilename);
+ deleteFileDialog.show(getSupportFragmentManager(), "deleteDialog");
+ }
+ break;
+
+ default:
+ // shouldn't happen
+ break;
+
+ }
+ }
+ };
+ };
+
+ // 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);
+ }
+
+ /**
+ * 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 initView() {
+ mSource = (ViewFlipper) findViewById(R.id.source);
+ mSourceLabel = (TextView) findViewById(R.id.sourceLabel);
+ mSourcePrevious = (ImageView) findViewById(R.id.sourcePrevious);
+ mSourceNext = (ImageView) findViewById(R.id.sourceNext);
+
+ mSourcePrevious.setClickable(true);
+ mSourcePrevious.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ mSource.setInAnimation(AnimationUtils.loadAnimation(EncryptActivity.this,
+ R.anim.push_right_in));
+ mSource.setOutAnimation(AnimationUtils.loadAnimation(EncryptActivity.this,
+ R.anim.push_right_out));
+ mSource.showPrevious();
+ updateSource();
+ }
+ });
+
+ mSourceNext.setClickable(true);
+ OnClickListener nextSourceClickListener = new OnClickListener() {
+ public void onClick(View v) {
+ mSource.setInAnimation(AnimationUtils.loadAnimation(EncryptActivity.this,
+ R.anim.push_left_in));
+ mSource.setOutAnimation(AnimationUtils.loadAnimation(EncryptActivity.this,
+ R.anim.push_left_out));
+ mSource.showNext();
+ updateSource();
+ }
+ };
+ mSourceNext.setOnClickListener(nextSourceClickListener);
+
+ mSourceLabel.setClickable(true);
+ mSourceLabel.setOnClickListener(nextSourceClickListener);
+
+ mMode = (ViewFlipper) findViewById(R.id.mode);
+ mModeLabel = (TextView) findViewById(R.id.modeLabel);
+ mModePrevious = (ImageView) findViewById(R.id.modePrevious);
+ mModeNext = (ImageView) findViewById(R.id.modeNext);
+
+ mModePrevious.setClickable(true);
+ mModePrevious.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ mMode.setInAnimation(AnimationUtils.loadAnimation(EncryptActivity.this,
+ R.anim.push_right_in));
+ mMode.setOutAnimation(AnimationUtils.loadAnimation(EncryptActivity.this,
+ R.anim.push_right_out));
+ mMode.showPrevious();
+ updateMode();
+ }
+ });
+
+ OnClickListener nextModeClickListener = new OnClickListener() {
+ public void onClick(View v) {
+ mMode.setInAnimation(AnimationUtils.loadAnimation(EncryptActivity.this,
+ R.anim.push_left_in));
+ mMode.setOutAnimation(AnimationUtils.loadAnimation(EncryptActivity.this,
+ R.anim.push_left_out));
+ mMode.showNext();
+ updateMode();
+ }
+ };
+ mModeNext.setOnClickListener(nextModeClickListener);
+
+ mModeLabel.setClickable(true);
+ mModeLabel.setOnClickListener(nextModeClickListener);
+
+ mMessage = (EditText) findViewById(R.id.message);
+ mSelectKeysButton = (BootstrapButton) findViewById(R.id.btn_selectEncryptKeys);
+ mSign = (CheckBox) findViewById(R.id.sign);
+ mMainUserId = (TextView) findViewById(R.id.mainUserId);
+ mMainUserIdRest = (TextView) findViewById(R.id.mainUserIdRest);
+
+ mPassPhrase = (EditText) findViewById(R.id.passPhrase);
+ mPassPhraseAgain = (EditText) findViewById(R.id.passPhraseAgain);
+
+ // measure the height of the source_file view and set the message view's min height to that,
+ // so it fills mSource fully... bit of a hack.
+ View tmp = findViewById(R.id.sourceFile);
+ tmp.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
+ int height = tmp.getMeasuredHeight();
+ mMessage.setMinimumHeight(height);
+
+ mFilename = (EditText) findViewById(R.id.filename);
+ mBrowse = (BootstrapButton) findViewById(R.id.btn_browse);
+ mBrowse.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ FileHelper.openFile(EncryptActivity.this, mFilename.getText().toString(), "*/*",
+ Id.request.filename);
+ }
+ });
+
+ mFileCompression = (Spinner) 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>(this,
+ android.R.layout.simple_spinner_item, choices);
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ mFileCompression.setAdapter(adapter);
+
+ int defaultFileCompression = Preferences.getPreferences(this).getDefaultFileCompression();
+ for (int i = 0; i < choices.length; ++i) {
+ if (choices[i].getId() == defaultFileCompression) {
+ mFileCompression.setSelection(i);
+ break;
+ }
+ }
+
+ mDeleteAfter = (CheckBox) findViewById(R.id.deleteAfterEncryption);
+
+ mAsciiArmor = (CheckBox) findViewById(R.id.asciiArmour);
+ mAsciiArmor.setChecked(Preferences.getPreferences(this).getDefaultAsciiArmour());
+
+ mSelectKeysButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ selectPublicKeys();
+ }
+ });
+
+ mSign.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ CheckBox checkBox = (CheckBox) v;
+ if (checkBox.isChecked()) {
+ selectSecretKey();
+ } else {
+ mSecretKeyId = Id.key.none;
+ updateView();
+ }
+ }
+ });
+ }
+
+ 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 {
+ String uid = getResources().getString(R.string.unknown_user_id);
+ String uidExtra = "";
+ PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRingByMasterKeyId(this,
+ mSecretKeyId);
+ if (keyRing != null) {
+ PGPSecretKey key = PgpKeyHelper.getMasterKey(keyRing);
+ if (key != null) {
+ String userId = PgpKeyHelper.getMainUserIdSafe(this, key);
+ String chunks[] = userId.split(" <", 2);
+ uid = chunks[0];
+ if (chunks.length > 1) {
+ uidExtra = "<" + chunks[1];
+ }
+ }
+ }
+ mMainUserId.setText(uid);
+ mMainUserIdRest.setText(uidExtra);
+ mSign.setChecked(true);
+ }
+
+ updateActionBarButtons();
+ }
+
+ private void selectPublicKeys() {
+ Intent intent = new Intent(this, 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(this, SelectSecretKeyActivity.class);
+ startActivityForResult(intent, Id.request.secret_keys);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ switch (requestCode) {
+ case Id.request.filename: {
+ if (resultCode == RESULT_OK && data != null) {
+ try {
+ String path = FileHelper.getPath(this, data.getData());
+ Log.d(Constants.TAG, "path=" + path);
+
+ mFilename.setText(path);
+ } catch (NullPointerException e) {
+ Log.e(Constants.TAG, "Nullpointer while retrieving path!");
+ }
+ }
+ return;
+ }
+
+ case Id.request.public_keys: {
+ if (resultCode == RESULT_OK) {
+ Bundle bundle = data.getExtras();
+ mEncryptionKeyIds = bundle
+ .getLongArray(SelectPublicKeyActivity.RESULT_EXTRA_MASTER_KEY_IDS);
+ }
+ updateView();
+ break;
+ }
+
+ case Id.request.secret_keys: {
+ if (resultCode == RESULT_OK) {
+ Bundle bundle = data.getExtras();
+ mSecretKeyId = bundle.getLong(SelectSecretKeyActivity.RESULT_EXTRA_MASTER_KEY_ID);
+ } else {
+ mSecretKeyId = Id.key.none;
+ }
+ updateView();
+ break;
+ }
+
+ default: {
+ break;
+ }
+ }
+
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpActivity.java
new file mode 100644
index 000000000..d604c1c86
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpActivity.java
@@ -0,0 +1,157 @@
+/*
+ * 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 java.util.ArrayList;
+
+import org.sufficientlysecure.keychain.R;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentPagerAdapter;
+import android.support.v4.app.FragmentTransaction;
+import android.support.v4.view.ViewPager;
+import android.widget.TextView;
+
+import com.actionbarsherlock.app.ActionBar;
+import com.actionbarsherlock.app.ActionBar.Tab;
+import com.actionbarsherlock.app.SherlockFragmentActivity;
+
+public class HelpActivity extends SherlockFragmentActivity {
+ public static final String EXTRA_SELECTED_TAB = "selectedTab";
+
+ ViewPager mViewPager;
+ TabsAdapter mTabsAdapter;
+ TextView tabCenter;
+ TextView tabText;
+
+ @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(HelpFragmentHtml.ARG_HTML_FILE, R.raw.help_start);
+ mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_start)),
+ HelpFragmentHtml.class, startBundle, (selectedTab == 0 ? true : false));
+
+ Bundle nfcBundle = new Bundle();
+ nfcBundle.putInt(HelpFragmentHtml.ARG_HTML_FILE, R.raw.help_nfc_beam);
+ mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_nfc_beam)),
+ HelpFragmentHtml.class, nfcBundle, (selectedTab == 1 ? true : false));
+
+ Bundle changelogBundle = new Bundle();
+ changelogBundle.putInt(HelpFragmentHtml.ARG_HTML_FILE, R.raw.help_changelog);
+ mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_changelog)),
+ HelpFragmentHtml.class, changelogBundle, (selectedTab == 2 ? true : false));
+
+ mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_about)),
+ HelpFragmentAbout.class, null, (selectedTab == 3 ? true : false));
+ }
+
+ public static class TabsAdapter extends FragmentPagerAdapter 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 {
+ private final Class<?> clss;
+ private final Bundle args;
+
+ TabInfo(Class<?> _class, Bundle _args) {
+ clss = _class;
+ args = _args;
+ }
+ }
+
+ public TabsAdapter(SherlockFragmentActivity 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(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(Tab tab, FragmentTransaction ft) {
+ }
+
+ public void onTabReselected(Tab tab, FragmentTransaction ft) {
+ }
+ }
+} \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpFragmentAbout.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpFragmentAbout.java
new file mode 100644
index 000000000..840ebb650
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpFragmentAbout.java
@@ -0,0 +1,87 @@
+/*
+ * 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 org.sufficientlysecure.htmltextview.HtmlTextView;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.util.Log;
+
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.actionbarsherlock.app.SherlockFragment;
+
+public class HelpFragmentAbout extends SherlockFragment {
+
+ /**
+ * Workaround for Android Bug. See
+ * http://stackoverflow.com/questions/8748064/starting-activity-from
+ * -fragment-causes-nullpointerexception
+ */
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ setUserVisibleHint(true);
+ }
+
+ @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;
+ }
+
+} \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpFragmentHtml.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpFragmentHtml.java
new file mode 100644
index 000000000..ce932fd70
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpFragmentHtml.java
@@ -0,0 +1,88 @@
+/*
+ * 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 org.sufficientlysecure.htmltextview.HtmlTextView;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ScrollView;
+
+import com.actionbarsherlock.app.SherlockFragment;
+
+public class HelpFragmentHtml extends SherlockFragment {
+ private Activity mActivity;
+
+ private int htmlFile;
+
+ public static final String ARG_HTML_FILE = "htmlFile";
+
+ /**
+ * Create a new instance of HelpFragmentHtml, providing "htmlFile" as an argument.
+ */
+ static HelpFragmentHtml newInstance(int htmlFile) {
+ HelpFragmentHtml f = new HelpFragmentHtml();
+
+ // Supply html raw file input as an argument.
+ Bundle args = new Bundle();
+ args.putInt(ARG_HTML_FILE, htmlFile);
+ f.setArguments(args);
+
+ return f;
+ }
+
+ /**
+ * Workaround for Android Bug. See
+ * http://stackoverflow.com/questions/8748064/starting-activity-from
+ * -fragment-causes-nullpointerexception
+ */
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ setUserVisibleHint(true);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ mActivity = getActivity();
+
+ htmlFile = 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(), htmlFile);
+
+ // no flickering when clicking textview for Android < 4
+ text.setTextColor(getResources().getColor(android.R.color.black));
+
+ return scroller;
+ }
+} \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java
new file mode 100644
index 000000000..1be5b8548
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java
@@ -0,0 +1,468 @@
+/*
+ * Copyright (C) 2012 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 java.util.ArrayList;
+import java.util.List;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+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.DeleteFileDialogFragment;
+import org.sufficientlysecure.keychain.ui.dialog.FileDialogFragment;
+import org.sufficientlysecure.keychain.util.Log;
+
+import android.annotation.SuppressLint;
+import android.app.AlertDialog;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+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.view.View;
+import android.view.View.OnClickListener;
+import android.widget.ArrayAdapter;
+import android.widget.Toast;
+
+import com.actionbarsherlock.app.ActionBar;
+import com.actionbarsherlock.app.ActionBar.OnNavigationListener;
+import com.beardedhen.androidbootstrap.BootstrapButton;
+
+public class ImportKeysActivity extends DrawerActivity implements 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";
+
+ // 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 IMPORT
+ public static final String EXTRA_KEY_BYTES = "key_bytes";
+
+ // TODO: import keys from server
+ // public static final String EXTRA_KEY_ID = "keyId";
+
+ protected boolean mDeleteAfterImport = false;
+
+ FileDialogFragment mFileDialog;
+ ImportKeysListFragment mListFragment;
+ OnNavigationListener mOnNavigationListener;
+ String[] mNavigationStrings;
+
+ Fragment mCurrentFragment;
+
+ BootstrapButton mImportButton;
+
+ // BootstrapButton mImportSignUploadButton;
+
+ @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();
+ }
+ });
+ // mImportSignUploadButton = (BootstrapButton) findViewById(R.id.import_sign_and_upload);
+ // mImportSignUploadButton.setOnClickListener(new OnClickListener() {
+ // @Override
+ // public void onClick(View v) {
+ // signAndUploadOnClick();
+ // }
+ // });
+
+ getSupportActionBar().setDisplayShowTitleEnabled(false);
+
+ setupDrawerNavigation(savedInstanceState);
+
+ // set actionbar without home button if called from another app
+ // ActionBarHelper.setBackButton(this);
+
+ // set drop down navigation
+ mNavigationStrings = getResources().getStringArray(R.array.import_action_list);
+ Context context = getSupportActionBar().getThemedContext();
+ ArrayAdapter<CharSequence> list = ArrayAdapter.createFromResource(context,
+ R.array.import_action_list, R.layout.sherlock_spinner_item);
+ list.setDropDownViewResource(R.layout.sherlock_spinner_dropdown_item);
+ getSupportActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
+ getSupportActionBar().setListNavigationCallbacks(list, this);
+
+ handleActions(savedInstanceState, getIntent());
+ }
+
+ protected void handleActions(Bundle savedInstanceState, Intent intent) {
+ String action = intent.getAction();
+ Bundle extras = intent.getExtras();
+
+ if (extras == null) {
+ extras = new Bundle();
+ }
+
+ /**
+ * Android Standard Actions
+ */
+ 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;
+ }
+
+ /**
+ * Keychain's own Actions
+ */
+ if (ACTION_IMPORT_KEY.equals(action)) {
+ if ("file".equals(intent.getScheme()) && intent.getDataString() != null) {
+ String importFilename = intent.getData().getPath();
+
+ // display selected filename
+ getSupportActionBar().setSelectedNavigationItem(1);
+ Bundle args = new Bundle();
+ args.putString(ImportKeysFileFragment.ARG_PATH, importFilename);
+ loadFragment(ImportKeysFileFragment.class, args, mNavigationStrings[1]);
+
+ // directly load data
+ startListFragment(savedInstanceState, null, importFilename);
+ } else if (extras.containsKey(EXTRA_KEY_BYTES)) {
+ byte[] importData = intent.getByteArrayExtra(EXTRA_KEY_BYTES);
+
+ // directly load data
+ startListFragment(savedInstanceState, importData, null);
+ }
+ } else {
+ // Internal actions
+ startListFragment(savedInstanceState, null, null);
+
+ if (ACTION_IMPORT_KEY_FROM_FILE.equals(action)) {
+ getSupportActionBar().setSelectedNavigationItem(1);
+ loadFragment(ImportKeysFileFragment.class, null, mNavigationStrings[1]);
+ } else if (ACTION_IMPORT_KEY_FROM_QR_CODE.equals(action)) {
+ getSupportActionBar().setSelectedNavigationItem(2);
+ loadFragment(ImportKeysQrCodeFragment.class, null, mNavigationStrings[2]);
+ } else if (ACTION_IMPORT_KEY_FROM_NFC.equals(action)) {
+ getSupportActionBar().setSelectedNavigationItem(3);
+ loadFragment(ImportKeysNFCFragment.class, null, mNavigationStrings[3]);
+ }
+ }
+ }
+
+ private void startListFragment(Bundle savedInstanceState, byte[] bytes, String filename) {
+ // Check that the activity is using the layout version with
+ // the fragment_container FrameLayout
+ if (findViewById(R.id.import_keys_list_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
+ mListFragment = ImportKeysListFragment.newInstance(bytes, filename);
+
+ // 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();
+ }
+ }
+
+ @Override
+ public boolean onNavigationItemSelected(int itemPosition, long itemId) {
+ // Create new fragment from our own Fragment class
+ switch (itemPosition) {
+ case 0:
+ loadFragment(ImportKeysServerFragment.class, null, mNavigationStrings[itemPosition]);
+ break;
+ case 1:
+ loadFragment(ImportKeysFileFragment.class, null, mNavigationStrings[itemPosition]);
+ break;
+ case 2:
+ loadFragment(ImportKeysQrCodeFragment.class, null, mNavigationStrings[itemPosition]);
+ break;
+ case 3:
+ loadFragment(ImportKeysClipboardFragment.class, null, mNavigationStrings[itemPosition]);
+ break;
+ case 4:
+ loadFragment(ImportKeysNFCFragment.class, null, mNavigationStrings[itemPosition]);
+ break;
+
+ default:
+ break;
+ }
+ return true;
+ }
+
+ 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 loadCallback(byte[] importData, String importFilename) {
+ mListFragment.loadNew(importData, importFilename);
+ }
+
+ // private void importAndSignOld(final long keyId, final String expectedFingerprint) {
+ // if (expectedFingerprint != null && expectedFingerprint.length() > 0) {
+ //
+ // Thread t = new Thread() {
+ // @Override
+ // public void run() {
+ // try {
+ // // TODO: display some sort of spinner here while the user waits
+ //
+ // // TODO: there should be only 1
+ // HkpKeyServer server = new HkpKeyServer(mPreferences.getKeyServers()[0]);
+ // String encodedKey = server.get(keyId);
+ //
+ // PGPKeyRing keyring = PGPHelper.decodeKeyRing(new ByteArrayInputStream(
+ // encodedKey.getBytes()));
+ // if (keyring != null && keyring instanceof PGPPublicKeyRing) {
+ // PGPPublicKeyRing publicKeyRing = (PGPPublicKeyRing) keyring;
+ //
+ // // make sure the fingerprints match before we cache this thing
+ // String actualFingerprint = PGPHelper.convertFingerprintToHex(publicKeyRing
+ // .getPublicKey().getFingerprint());
+ // if (expectedFingerprint.equals(actualFingerprint)) {
+ // // store the signed key in our local cache
+ // int retval = PGPMain.storeKeyRingInCache(publicKeyRing);
+ // if (retval != Id.return_value.ok
+ // && retval != Id.return_value.updated) {
+ // status.putString(EXTRA_ERROR,
+ // "Failed to store signed key in local cache");
+ // } else {
+ // Intent intent = new Intent(ImportFromQRCodeActivity.this,
+ // SignKeyActivity.class);
+ // intent.putExtra(EXTRA_KEY_ID, keyId);
+ // startActivityForResult(intent, Id.request.sign_key);
+ // }
+ // } else {
+ // status.putString(
+ // EXTRA_ERROR,
+ // "Scanned fingerprint does NOT match the fingerprint of the received key. You shouldnt trust this key.");
+ // }
+ // }
+ // } catch (QueryException e) {
+ // Log.e(TAG, "Failed to query KeyServer", e);
+ // status.putString(EXTRA_ERROR, "Failed to query KeyServer");
+ // status.putInt(Constants.extras.STATUS, Id.message.done);
+ // } catch (IOException e) {
+ // Log.e(TAG, "Failed to query KeyServer", e);
+ // status.putString(EXTRA_ERROR, "Failed to query KeyServer");
+ // status.putInt(Constants.extras.STATUS, Id.message.done);
+ // }
+ // }
+ // };
+ //
+ // t.setName("KeyExchange Download Thread");
+ // t.setDaemon(true);
+ // t.start();
+ // }
+ // }
+
+ /**
+ * Import keys with mImportData
+ */
+ public void importKeys() {
+ if (mListFragment.getKeyBytes() != null || mListFragment.getImportFilename() != 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 ids
+ List<ImportKeysListEntry> listEntries = mListFragment.getData();
+ ArrayList<Long> selectedKeyIds = new ArrayList<Long>();
+ for (ImportKeysListEntry entry : listEntries) {
+ if (entry.isSelected()) {
+ selectedKeyIds.add(entry.keyId);
+ }
+ }
+
+ data.putSerializable(KeychainIntentService.IMPORT_KEY_LIST, selectedKeyIds);
+
+ if (mListFragment.getKeyBytes() != null) {
+ data.putInt(KeychainIntentService.TARGET, KeychainIntentService.TARGET_BYTES);
+ data.putByteArray(KeychainIntentService.IMPORT_BYTES, mListFragment.getKeyBytes());
+ } else {
+ data.putInt(KeychainIntentService.TARGET, KeychainIntentService.TARGET_FILE);
+ data.putString(KeychainIntentService.IMPORT_FILENAME,
+ mListFragment.getImportFilename());
+ }
+
+ intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
+
+ // Message is received after importing is done in ApgService
+ KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this,
+ R.string.progress_importing, ProgressDialog.STYLE_HORIZONTAL) {
+ public void handleMessage(Message message) {
+ // handle messages by standard ApgHandler 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);
+ }
+ Toast.makeText(ImportKeysActivity.this, toastMessage, Toast.LENGTH_SHORT)
+ .show();
+ if (bad > 0) {
+ AlertDialog.Builder alert = new AlertDialog.Builder(
+ ImportKeysActivity.this);
+
+ alert.setIcon(android.R.drawable.ic_dialog_alert);
+ alert.setTitle(R.string.warning);
+
+ alert.setMessage(ImportKeysActivity.this.getResources()
+ .getQuantityString(R.plurals.bad_keys_encountered, bad, bad));
+
+ alert.setPositiveButton(android.R.string.ok,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ dialog.cancel();
+ }
+ });
+ alert.setCancelable(true);
+ alert.create().show();
+ } else if (mDeleteAfterImport) {
+ // everything went well, so now delete, if that was turned on
+ DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment
+ .newInstance(mListFragment.getImportFilename());
+ deleteFileDialog.show(getSupportFragmentManager(), "deleteDialog");
+ }
+ }
+ };
+ };
+
+ // 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 {
+ Toast.makeText(this, R.string.error_nothing_import, Toast.LENGTH_LONG).show();
+ }
+ }
+
+ public void importOnClick() {
+ importKeys();
+ }
+
+ // public void signAndUploadOnClick() {
+ // // first, import!
+ // // importOnClick(view);
+ //
+ // // TODO: implement sign and upload!
+ // Toast.makeText(ImportKeysActivity.this, "Not implemented right now!", Toast.LENGTH_SHORT)
+ // .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/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysClipboardFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysClipboardFragment.java
new file mode 100644
index 000000000..04671587a
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysClipboardFragment.java
@@ -0,0 +1,79 @@
+/*
+ * 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 org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
+
+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;
+
+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();
+ mImportActivity.loadCallback(sendText.getBytes(), null);
+ }
+ });
+
+ return view;
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ mImportActivity = (ImportKeysActivity) getActivity();
+ }
+
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java
new file mode 100644
index 000000000..a02bfd678
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java
@@ -0,0 +1,124 @@
+/*
+ * 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 org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.Id;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.helper.FileHelper;
+import org.sufficientlysecure.keychain.util.Log;
+
+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 android.widget.EditText;
+
+import com.beardedhen.androidbootstrap.BootstrapButton;
+
+public class ImportKeysFileFragment extends Fragment {
+ public static final String ARG_PATH = "path";
+
+ private ImportKeysActivity mImportActivity;
+ private EditText mFilename;
+ private BootstrapButton mBrowse;
+
+ /**
+ * Creates new instance of this fragment
+ */
+ public static ImportKeysFileFragment newInstance(String path) {
+ ImportKeysFileFragment frag = new ImportKeysFileFragment();
+
+ Bundle args = new Bundle();
+ args.putString(ARG_PATH, path);
+
+ 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);
+
+ mFilename = (EditText) view.findViewById(R.id.import_keys_file_input);
+ 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, mFilename.getText().toString(),
+ "*/*", Id.request.filename);
+ }
+ });
+
+ return view;
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ mImportActivity = (ImportKeysActivity) getActivity();
+
+ // set default path
+ String path = Constants.path.APP_DIR + "/";
+ if (getArguments() != null && getArguments().containsKey(ARG_PATH)) {
+ path = getArguments().getString(ARG_PATH);
+ }
+ mFilename.setText(path);
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ switch (requestCode & 0xFFFF) {
+ case Id.request.filename: {
+ if (resultCode == Activity.RESULT_OK && data != null) {
+ String path = null;
+ try {
+ path = data.getData().getPath();
+ Log.d(Constants.TAG, "path=" + path);
+
+ // set filename to edittext
+ mFilename.setText(path);
+ } catch (NullPointerException e) {
+ Log.e(Constants.TAG, "Nullpointer while retrieving path!", e);
+ }
+
+ // load data
+ mImportActivity.loadCallback(null, path);
+ }
+
+ break;
+ }
+
+ default:
+ super.onActivityResult(requestCode, resultCode, data);
+
+ break;
+ }
+ }
+
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java
new file mode 100644
index 000000000..18d9dc2ec
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java
@@ -0,0 +1,177 @@
+/*
+ * 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 java.io.ByteArrayInputStream;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.util.List;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+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.util.InputData;
+import org.sufficientlysecure.keychain.util.Log;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.support.v4.app.LoaderManager;
+import android.support.v4.content.Loader;
+import android.view.View;
+import android.widget.ListView;
+
+import com.actionbarsherlock.app.SherlockListFragment;
+
+public class ImportKeysListFragment extends SherlockListFragment implements
+ LoaderManager.LoaderCallbacks<List<ImportKeysListEntry>> {
+ private static final String ARG_FILENAME = "filename";
+ private static final String ARG_BYTES = "bytes";
+
+ private Activity mActivity;
+ private ImportKeysAdapter mAdapter;
+
+ private byte[] mKeyBytes;
+ private String mImportFilename;
+
+ public byte[] getKeyBytes() {
+ return mKeyBytes;
+ }
+
+ public String getImportFilename() {
+ return mImportFilename;
+ }
+
+ public List<ImportKeysListEntry> getData() {
+ return mAdapter.getData();
+ }
+
+ /**
+ * Creates new instance of this fragment
+ */
+ public static ImportKeysListFragment newInstance(byte[] bytes, String filename) {
+ ImportKeysListFragment frag = new ImportKeysListFragment();
+
+ Bundle args = new Bundle();
+ args.putByteArray(ARG_BYTES, bytes);
+ args.putString(ARG_FILENAME, filename);
+
+ frag.setArguments(args);
+
+ return frag;
+ }
+
+ /**
+ * Define Adapter and Loader on create of Activity
+ */
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ mActivity = getActivity();
+
+ if (getArguments() != null) {
+ mImportFilename = getArguments().getString(ARG_FILENAME);
+ mKeyBytes = getArguments().getByteArray(ARG_BYTES);
+ }
+
+ // 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);
+
+ // 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(0, 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[] importData, String importFilename) {
+ this.mKeyBytes = importData;
+ this.mImportFilename = importFilename;
+
+ getLoaderManager().restartLoader(0, null, this);
+ }
+
+ @Override
+ public Loader<List<ImportKeysListEntry>> onCreateLoader(int id, Bundle args) {
+ InputData inputData = getInputData(mKeyBytes, mImportFilename);
+ return new ImportKeysListLoader(mActivity, inputData);
+ }
+
+ private InputData getInputData(byte[] importBytes, String importFilename) {
+ InputData inputData = null;
+ if (importBytes != null) {
+ inputData = new InputData(new ByteArrayInputStream(importBytes), importBytes.length);
+ } else if (importFilename != null) {
+ try {
+ inputData = new InputData(new FileInputStream(importFilename),
+ importFilename.length());
+ } catch (FileNotFoundException e) {
+ Log.e(Constants.TAG, "Failed to init FileInputStream!", e);
+ }
+ }
+
+ return inputData;
+ }
+
+ @Override
+ public void onLoadFinished(Loader<List<ImportKeysListEntry>> loader,
+ List<ImportKeysListEntry> data) {
+ Log.d(Constants.TAG, "data: " + data);
+
+ // swap in the real data!
+ mAdapter.setData(data);
+ mAdapter.notifyDataSetChanged();
+
+ setListAdapter(mAdapter);
+
+ // The list should now be shown.
+ if (isResumed()) {
+ setListShown(true);
+ } else {
+ setListShownNoAnimation(true);
+ }
+ }
+
+ @Override
+ public void onLoaderReset(Loader<List<ImportKeysListEntry>> loader) {
+ // Clear the data in the adapter.
+ mAdapter.clear();
+ }
+
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysNFCFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysNFCFragment.java
new file mode 100644
index 000000000..83af8cf48
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysNFCFragment.java
@@ -0,0 +1,70 @@
+/*
+ * 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 org.sufficientlysecure.keychain.R;
+
+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;
+
+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, 1);
+ startActivityForResult(intent, 0);
+ }
+ });
+
+ return view;
+ }
+
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysQrCodeFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysQrCodeFragment.java
new file mode 100644
index 000000000..3ede641d3
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysQrCodeFragment.java
@@ -0,0 +1,209 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.Locale;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.util.IntentIntegratorSupportV4;
+import org.sufficientlysecure.keychain.util.Log;
+
+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 android.widget.ProgressBar;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.beardedhen.androidbootstrap.BootstrapButton;
+import com.google.zxing.integration.android.IntentResult;
+
+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) {
+
+ Log.d(Constants.TAG, "scanResult content: " + scanResult.getContents());
+
+ // look if it's fingerprint only
+ if (scanResult.getContents().toLowerCase(Locale.ENGLISH).startsWith("openpgp4fpr")) {
+ importFingerprint(scanResult.getContents().toLowerCase(Locale.ENGLISH));
+ return;
+ }
+
+ // look if it is the whole key
+ String[] parts = scanResult.getContents().split(",");
+ if (parts.length == 3) {
+ importParts(parts);
+ return;
+ }
+
+ // fail...
+ Toast.makeText(getActivity(), R.string.import_qr_code_wrong, Toast.LENGTH_LONG)
+ .show();
+ }
+
+ break;
+ }
+
+ default:
+ super.onActivityResult(requestCode, resultCode, data);
+
+ break;
+ }
+ }
+
+ private void importFingerprint(String uri) {
+ String fingerprint = uri.split(":")[1];
+
+ Log.d(Constants.TAG, "fingerprint: " + fingerprint);
+
+ if (fingerprint.length() < 16) {
+ Toast.makeText(getActivity(), R.string.import_qr_code_too_short_fingerprint,
+ Toast.LENGTH_LONG).show();
+ return;
+ }
+
+ Intent queryIntent = new Intent(getActivity(), KeyServerQueryActivity.class);
+ queryIntent.setAction(KeyServerQueryActivity.ACTION_LOOK_UP_KEY_ID);
+ queryIntent.putExtra(KeyServerQueryActivity.EXTRA_FINGERPRINT, fingerprint);
+ startActivity(queryIntent);
+ }
+
+ 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);
+ }
+ }
+
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysServerFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysServerFragment.java
new file mode 100644
index 000000000..c985f1f60
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysServerFragment.java
@@ -0,0 +1,67 @@
+/*
+ * 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 org.sufficientlysecure.keychain.R;
+
+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;
+
+public class ImportKeysServerFragment extends Fragment {
+ private BootstrapButton mButton;
+
+ /**
+ * Creates new instance of this fragment
+ */
+ public static ImportKeysServerFragment newInstance() {
+ ImportKeysServerFragment frag = new ImportKeysServerFragment();
+
+ 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_keyserver_fragment, container, false);
+
+ mButton = (BootstrapButton) view.findViewById(R.id.import_keyserver_button);
+ mButton.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ // TODO: use fragment instead of activity, handle onresult here!
+ startActivityForResult(new Intent(getActivity(), KeyServerQueryActivity.class), 0);
+ }
+ });
+
+ return view;
+ }
+
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListPublicActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListPublicActivity.java
new file mode 100644
index 000000000..1b8bfe6f3
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListPublicActivity.java
@@ -0,0 +1,72 @@
+/*
+ * 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 org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.Id;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.helper.ExportHelper;
+
+import android.content.Intent;
+import android.os.Bundle;
+
+import com.actionbarsherlock.view.Menu;
+import com.actionbarsherlock.view.MenuItem;
+
+public class KeyListPublicActivity extends DrawerActivity {
+
+ ExportHelper mExportHelper;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mExportHelper = new ExportHelper(this);
+
+ setContentView(R.layout.key_list_public_activity);
+
+ // now setup navigation drawer in DrawerActivity...
+ setupDrawerNavigation(savedInstanceState);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ getSupportMenuInflater().inflate(R.menu.key_list_public, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.menu_key_list_public_import:
+ Intent intentImport = new Intent(this, ImportKeysActivity.class);
+ startActivityForResult(intentImport, 0);
+
+ return true;
+ case R.id.menu_key_list_public_export:
+ mExportHelper.showExportKeysDialog(null, Id.type.public_key, Constants.path.APP_DIR
+ + "/pubexport.asc");
+
+ return true;
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListPublicFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListPublicFragment.java
new file mode 100644
index 000000000..6ae2b9bf9
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListPublicFragment.java
@@ -0,0 +1,299 @@
+/*
+ * 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 java.util.Set;
+
+import org.sufficientlysecure.keychain.Id;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.provider.KeychainContract;
+import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
+import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.ui.adapter.KeyListPublicAdapter;
+import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment;
+
+import se.emilsjolander.stickylistheaders.ApiLevelTooLowException;
+import se.emilsjolander.stickylistheaders.StickyListHeadersListView;
+import android.annotation.SuppressLint;
+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.Fragment;
+import android.support.v4.app.LoaderManager;
+import android.support.v4.content.CursorLoader;
+import android.support.v4.content.Loader;
+import android.view.ActionMode;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.AbsListView.MultiChoiceModeListener;
+import android.widget.AdapterView;
+import android.widget.ListView;
+
+import com.beardedhen.androidbootstrap.BootstrapButton;
+
+/**
+ * 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 KeyListPublicFragment extends Fragment implements AdapterView.OnItemClickListener,
+ LoaderManager.LoaderCallbacks<Cursor> {
+
+ private KeyListPublicAdapter mAdapter;
+ private StickyListHeadersListView mStickyList;
+
+ // empty 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 view = inflater.inflate(R.layout.key_list_public_fragment, container, false);
+
+ mButtonEmptyCreate = (BootstrapButton) view.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) view.findViewById(R.id.key_list_empty_button_import);
+ mButtonEmptyImport.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ Intent intentImportFromFile = new Intent(getActivity(), ImportKeysActivity.class);
+ intentImportFromFile.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_FILE);
+ startActivityForResult(intentImportFromFile, 0);
+ }
+ });
+
+ return view;
+ }
+
+ /**
+ * Define Adapter and Loader on create of Activity
+ */
+ @SuppressLint("NewApi")
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ // mKeyListPublicActivity = (KeyListPublicActivity) getActivity();
+ mStickyList = (StickyListHeadersListView) getActivity().findViewById(R.id.list);
+
+ mStickyList.setOnItemClickListener(this);
+ mStickyList.setAreHeadersSticky(true);
+ mStickyList.setDrawingListUnderStickyHeader(false);
+ mStickyList.setFastScrollEnabled(true);
+ try {
+ mStickyList.setFastScrollAlwaysVisible(true);
+ } catch (ApiLevelTooLowException e) {
+ }
+
+ // this view is made visible if no data is available
+ mStickyList.setEmptyView(getActivity().findViewById(R.id.empty));
+
+ /*
+ * 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() {
+
+ private int count = 0;
+
+ @Override
+ public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+ android.view.MenuInflater inflater = getActivity().getMenuInflater();
+ inflater.inflate(R.menu.key_list_public_multi, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+ return false;
+ }
+
+ @Override
+ public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+ Set<Integer> positions = mAdapter.getCurrentCheckedPosition();
+
+ // get IDs for checked positions as long array
+ long[] ids = new long[positions.size()];
+ int i = 0;
+ for (int pos : positions) {
+ ids[i] = mAdapter.getItemId(pos);
+ i++;
+ }
+
+ switch (item.getItemId()) {
+ case R.id.menu_key_list_public_multi_encrypt: {
+ encrypt(ids);
+
+ break;
+ }
+ case R.id.menu_key_list_public_multi_delete: {
+ showDeleteKeyDialog(ids);
+
+ break;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void onDestroyActionMode(ActionMode mode) {
+ count = 0;
+ mAdapter.clearSelection();
+ }
+
+ @Override
+ public void onItemCheckedStateChanged(ActionMode mode, int position, long id,
+ boolean checked) {
+ if (checked) {
+ count++;
+ mAdapter.setNewSelection(position, checked);
+ } else {
+ count--;
+ mAdapter.removeSelection(position);
+ }
+
+ String keysSelected = getResources().getQuantityString(
+ R.plurals.key_list_selected_keys, count, count);
+ mode.setTitle(keysSelected);
+ }
+
+ });
+ }
+
+ // NOTE: Not supported by StickyListHeader, thus no indicator is shown while loading
+ // Start out with a progress indicator.
+ // setListShown(false);
+
+ // Create an empty adapter we will use to display the loaded data.
+ mAdapter = new KeyListPublicAdapter(getActivity(), null, Id.type.public_key, USER_ID_INDEX);
+ 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,
+ UserIds.USER_ID };
+
+ static final int USER_ID_INDEX = 2;
+
+ static final String SORT_ORDER = UserIds.USER_ID + " ASC";
+
+ @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.buildPublicKeyRingsUri();
+
+ // 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, 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);
+
+ // NOTE: Not supported by StickyListHeader, thus no indicator is shown while loading
+ // 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(KeychainContract.KeyRings.buildPublicKeyRingsUri(Long.toString(id)));
+ startActivity(viewIntent);
+ }
+
+ public void encrypt(long[] keyRingRowIds) {
+ // get master key ids from row ids
+ long[] keyRingIds = new long[keyRingRowIds.length];
+ for (int i = 0; i < keyRingRowIds.length; i++) {
+ keyRingIds[i] = ProviderHelper.getPublicMasterKeyId(getActivity(), keyRingRowIds[i]);
+ }
+
+ Intent intent = new Intent(getActivity(), EncryptActivity.class);
+ intent.setAction(EncryptActivity.ACTION_ENCRYPT);
+ intent.putExtra(EncryptActivity.EXTRA_ENCRYPTION_KEY_IDS, keyRingIds);
+ // used instead of startActivity set actionbar based on callingPackage
+ startActivityForResult(intent, 0);
+ }
+
+ /**
+ * Show dialog to delete key
+ *
+ * @param keyRingRowIds
+ */
+ public void showDeleteKeyDialog(long[] keyRingRowIds) {
+ DeleteKeyDialogFragment deleteKeyDialog = DeleteKeyDialogFragment.newInstance(null,
+ keyRingRowIds, Id.type.public_key);
+
+ deleteKeyDialog.show(getActivity().getSupportFragmentManager(), "deleteKeyDialog");
+ }
+
+} \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListSecretActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListSecretActivity.java
new file mode 100644
index 000000000..1ad9781ec
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListSecretActivity.java
@@ -0,0 +1,95 @@
+/*
+ * 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 org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.Id;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.helper.ExportHelper;
+
+import android.content.Intent;
+import android.os.Bundle;
+
+import com.actionbarsherlock.view.Menu;
+import com.actionbarsherlock.view.MenuItem;
+
+public class KeyListSecretActivity extends DrawerActivity {
+
+ ExportHelper mExportHelper;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mExportHelper = new ExportHelper(this);
+
+ setContentView(R.layout.key_list_secret_activity);
+
+ // now setup navigation drawer in DrawerActivity...
+ setupDrawerNavigation(savedInstanceState);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ getSupportMenuInflater().inflate(R.menu.key_list_secret, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.menu_key_list_secret_create:
+ createKey();
+
+ return true;
+ case R.id.menu_key_list_secret_create_expert:
+ createKeyExpert();
+
+ return true;
+ case R.id.menu_key_list_secret_export:
+ mExportHelper.showExportKeysDialog(null, Id.type.secret_key, Constants.path.APP_DIR
+ + "/secexport.asc");
+
+ return true;
+ case R.id.menu_key_list_secret_import:
+ Intent intentImportFromFile = new Intent(this, ImportKeysActivity.class);
+ intentImportFromFile.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_FILE);
+ startActivityForResult(intentImportFromFile, 0);
+
+ return true;
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ 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/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListSecretFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListSecretFragment.java
new file mode 100644
index 000000000..0e2d22e1e
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListSecretFragment.java
@@ -0,0 +1,215 @@
+/*
+ * 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 java.util.Set;
+
+import org.sufficientlysecure.keychain.Id;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.provider.KeychainContract;
+import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
+import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
+import org.sufficientlysecure.keychain.ui.adapter.KeyListSecretAdapter;
+import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment;
+
+import android.annotation.SuppressLint;
+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.view.ActionMode;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ListView;
+import android.widget.AbsListView.MultiChoiceModeListener;
+import android.widget.AdapterView.OnItemClickListener;
+
+import com.actionbarsherlock.app.SherlockListFragment;
+
+public class KeyListSecretFragment extends SherlockListFragment implements
+ LoaderManager.LoaderCallbacks<Cursor>, OnItemClickListener {
+
+ private KeyListSecretActivity mKeyListSecretActivity;
+ private KeyListSecretAdapter mAdapter;
+
+ /**
+ * Define Adapter and Loader on create of Activity
+ */
+ @SuppressLint("NewApi")
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ mKeyListSecretActivity = (KeyListSecretActivity) getActivity();
+
+ getListView().setOnItemClickListener(this);
+
+ // 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));
+
+ /*
+ * ActionBarSherlock does not support MultiChoiceModeListener. Thus multi-selection is only
+ * available for Android >= 3.0
+ */
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
+ getListView().setMultiChoiceModeListener(new MultiChoiceModeListener() {
+
+ private int count = 0;
+
+ @Override
+ public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+ android.view.MenuInflater inflater = getActivity().getMenuInflater();
+ inflater.inflate(R.menu.key_list_secret_multi, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+ return false;
+ }
+
+ @Override
+ public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+ Set<Integer> positions = mAdapter.getCurrentCheckedPosition();
+
+ // get IDs for checked positions as long array
+ long[] ids = new long[positions.size()];
+ int i = 0;
+ for (int pos : positions) {
+ ids[i] = mAdapter.getItemId(pos);
+ i++;
+ }
+
+ switch (item.getItemId()) {
+ case R.id.menu_key_list_public_multi_delete: {
+ showDeleteKeyDialog(ids);
+
+ break;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void onDestroyActionMode(ActionMode mode) {
+ count = 0;
+ mAdapter.clearSelection();
+ }
+
+ @Override
+ public void onItemCheckedStateChanged(ActionMode mode, int position, long id,
+ boolean checked) {
+ if (checked) {
+ count++;
+ mAdapter.setNewSelection(position, checked);
+ } else {
+ count--;
+ mAdapter.removeSelection(position);
+ }
+
+ 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);
+
+ // Start out with a progress indicator.
+ setListShown(false);
+
+ // Create an empty adapter we will use to display the loaded data.
+ mAdapter = new KeyListSecretAdapter(mKeyListSecretActivity, 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 rows that we will retrieve.
+ static final String[] PROJECTION = new String[] { KeyRings._ID, KeyRings.MASTER_KEY_ID,
+ UserIds.USER_ID };
+ static final String SORT_ORDER = UserIds.USER_ID + " COLLATE LOCALIZED ASC";
+
+ 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 = KeyRings.buildSecretKeyRingsUri();
+
+ // 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, SORT_ORDER);
+ }
+
+ 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);
+ }
+ }
+
+ 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 editIntent = new Intent(mKeyListSecretActivity, EditKeyActivity.class);
+ editIntent.setData(KeychainContract.KeyRings.buildSecretKeyRingsUri(Long.toString(id)));
+ editIntent.setAction(EditKeyActivity.ACTION_EDIT_KEY);
+ startActivityForResult(editIntent, 0);
+ }
+
+ /**
+ * Show dialog to delete key
+ *
+ * @param keyRingRowIds
+ */
+ public void showDeleteKeyDialog(long[] keyRingRowIds) {
+ DeleteKeyDialogFragment deleteKeyDialog = DeleteKeyDialogFragment.newInstance(null,
+ keyRingRowIds, Id.type.secret_key);
+
+ deleteKeyDialog.show(getActivity().getSupportFragmentManager(), "deleteKeyDialog");
+ }
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyServerQueryActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyServerQueryActivity.java
new file mode 100644
index 000000000..85cde1e7e
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyServerQueryActivity.java
@@ -0,0 +1,391 @@
+/*
+ * 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.ProgressDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Message;
+import android.os.Messenger;
+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.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ArrayAdapter;
+import android.widget.BaseAdapter;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.LinearLayout.LayoutParams;
+import android.widget.ListView;
+import android.widget.Spinner;
+import android.widget.TextView;
+import android.widget.TextView.OnEditorActionListener;
+import android.widget.Toast;
+import com.actionbarsherlock.app.SherlockFragmentActivity;
+import com.actionbarsherlock.view.MenuItem;
+import java.util.ArrayList;
+import java.util.List;
+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.pgp.PgpKeyHelper;
+import org.sufficientlysecure.keychain.service.KeychainIntentService;
+import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
+import org.sufficientlysecure.keychain.util.KeyServer.KeyInfo;
+import org.sufficientlysecure.keychain.util.Log;
+
+public class KeyServerQueryActivity extends SherlockFragmentActivity {
+
+ // possible intent actions for this activity
+ public static final String ACTION_LOOK_UP_KEY_ID = Constants.INTENT_PREFIX + "LOOK_UP_KEY_ID";
+
+ public static final String ACTION_LOOK_UP_KEY_ID_AND_RETURN = Constants.INTENT_PREFIX
+ + "LOOK_UP_KEY_ID_AND_RETURN";
+
+ public static final String EXTRA_KEY_ID = "key_id";
+ public static final String EXTRA_FINGERPRINT = "fingerprint";
+
+ public static final String RESULT_EXTRA_TEXT = "text";
+
+ private ListView mList;
+ private EditText mQuery;
+ private Button mSearch;
+ private Spinner mKeyServer;
+
+ private KeyInfoListAdapter mAdapter;
+
+ private int mQueryType;
+ private String mQueryString;
+ private long mQueryId;
+
+ private volatile List<KeyInfo> mSearchResult;
+
+ private volatile String mKeyData;
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+
+ case android.R.id.home:
+ // app icon in Action Bar clicked; go home
+ Intent intent = new Intent(this, KeyListPublicActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ startActivity(intent);
+ return true;
+
+ default:
+ break;
+
+ }
+ return false;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.key_server_query);
+
+ mQuery = (EditText) findViewById(R.id.query);
+ mSearch = (Button) findViewById(R.id.btn_search);
+ mList = (ListView) findViewById(R.id.list);
+ mAdapter = new KeyInfoListAdapter(this);
+ mList.setAdapter(mAdapter);
+
+ mKeyServer = (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);
+ mKeyServer.setAdapter(adapter);
+ if (adapter.getCount() > 0) {
+ mKeyServer.setSelection(0);
+ } else {
+ mSearch.setEnabled(false);
+ }
+
+ mList.setOnItemClickListener(new OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView<?> adapter, View view, int position, long keyId) {
+ get(keyId);
+ }
+ });
+
+ mSearch.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ String query = mQuery.getText().toString();
+ search(query);
+ }
+ });
+ mQuery.setOnEditorActionListener(new OnEditorActionListener() {
+ @Override
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ if (actionId == EditorInfo.IME_ACTION_SEARCH) {
+ String query = mQuery.getText().toString();
+ search(query);
+ return false; // FIXME This is a hack to hide a keyboard
+ // after search http://tinyurl.com/pwdc3q9
+
+ }
+ return false;
+ }
+ });
+
+ Intent intent = getIntent();
+ String action = intent.getAction();
+ if (ACTION_LOOK_UP_KEY_ID.equals(action) || ACTION_LOOK_UP_KEY_ID_AND_RETURN.equals(action)) {
+ long keyId = intent.getLongExtra(EXTRA_KEY_ID, 0);
+ if (keyId != 0) {
+ String query = "0x" + PgpKeyHelper.convertKeyToHex(keyId);
+ mQuery.setText(query);
+ search(query);
+ }
+ String fingerprint = intent.getStringExtra(EXTRA_FINGERPRINT);
+ if (fingerprint != null) {
+ fingerprint = "0x" + fingerprint;
+ mQuery.setText(fingerprint);
+ search(fingerprint);
+ }
+ }
+ }
+
+ private void search(String query) {
+ mQueryType = Id.keyserver.search;
+ mQueryString = query;
+ mAdapter.setKeys(new ArrayList<KeyInfo>());
+
+ start();
+ }
+
+ private void get(long keyId) {
+ mQueryType = Id.keyserver.get;
+ mQueryId = keyId;
+
+ start();
+ }
+
+ private void start() {
+ Log.d(Constants.TAG, "start search with service");
+
+ // Send all information needed to service to query keys in other thread
+ Intent intent = new Intent(this, KeychainIntentService.class);
+
+ intent.setAction(KeychainIntentService.ACTION_QUERY_KEYRING);
+
+ // fill values for this action
+ Bundle data = new Bundle();
+
+ String server = (String) mKeyServer.getSelectedItem();
+ data.putString(KeychainIntentService.QUERY_KEY_SERVER, server);
+
+ data.putInt(KeychainIntentService.QUERY_KEY_TYPE, mQueryType);
+
+ if (mQueryType == Id.keyserver.search) {
+ data.putString(KeychainIntentService.QUERY_KEY_STRING, mQueryString);
+ } else if (mQueryType == Id.keyserver.get) {
+ data.putLong(KeychainIntentService.QUERY_KEY_ID, mQueryId);
+ }
+
+ intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
+
+ // Message is received after querying is done in ApgService
+ KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this,
+ R.string.progress_querying, ProgressDialog.STYLE_SPINNER) {
+ @Override
+ public void handleMessage(Message message) {
+ // handle messages by standard ApgHandler first
+ super.handleMessage(message);
+
+ if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
+ // get returned data bundle
+ Bundle returnData = message.getData();
+
+ if (mQueryType == Id.keyserver.search) {
+ mSearchResult = returnData
+ .getParcelableArrayList(KeychainIntentService.RESULT_QUERY_KEY_SEARCH_RESULT);
+ } else if (mQueryType == Id.keyserver.get) {
+ mKeyData = returnData
+ .getString(KeychainIntentService.RESULT_QUERY_KEY_DATA);
+ }
+
+ // TODO: IMPROVE CODE!!! some global variables can be
+ // avoided!!!
+ if (mQueryType == Id.keyserver.search) {
+ if (mSearchResult != null) {
+ Toast.makeText(
+ KeyServerQueryActivity.this,
+ getResources().getQuantityString(R.plurals.keys_found,
+ mSearchResult.size(), mSearchResult.size()),
+ Toast.LENGTH_SHORT).show();
+ mAdapter.setKeys(mSearchResult);
+ }
+ } else if (mQueryType == Id.keyserver.get) {
+ Intent orgIntent = getIntent();
+ if (ACTION_LOOK_UP_KEY_ID_AND_RETURN.equals(orgIntent.getAction())) {
+ if (mKeyData != null) {
+ Intent intent = new Intent();
+ intent.putExtra(RESULT_EXTRA_TEXT, mKeyData);
+ setResult(RESULT_OK, intent);
+ } else {
+ setResult(RESULT_CANCELED);
+ }
+ finish();
+ } else {
+ if (mKeyData != null) {
+ Intent intent = new Intent(KeyServerQueryActivity.this,
+ ImportKeysActivity.class);
+ intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY);
+ intent.putExtra(ImportKeysActivity.EXTRA_KEY_BYTES,
+ mKeyData.getBytes());
+ startActivity(intent);
+ }
+ }
+ }
+
+ }
+ };
+ };
+
+ // 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);
+ }
+
+ public class KeyInfoListAdapter extends BaseAdapter {
+ protected LayoutInflater mInflater;
+
+ protected Activity mActivity;
+
+ protected List<KeyInfo> mKeys;
+
+ public KeyInfoListAdapter(Activity activity) {
+ mActivity = activity;
+ mInflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ mKeys = new ArrayList<KeyInfo>();
+ }
+
+ public void setKeys(List<KeyInfo> keys) {
+ mKeys = keys;
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return true;
+ }
+
+ @Override
+ public int getCount() {
+ return mKeys.size();
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return mKeys.get(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return mKeys.get(position).keyId;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ KeyInfo keyInfo = mKeys.get(position);
+
+ View view = mInflater.inflate(R.layout.key_server_query_result_item, null);
+
+ TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId);
+ mainUserId.setText(R.string.unknown_user_id);
+ TextView mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest);
+ mainUserIdRest.setText("");
+ TextView keyId = (TextView) view.findViewById(R.id.keyId);
+ keyId.setText(R.string.no_key);
+ TextView algorithm = (TextView) view.findViewById(R.id.algorithm);
+ algorithm.setText("");
+ TextView status = (TextView) view.findViewById(R.id.status);
+ status.setText("");
+
+ String userId = keyInfo.userIds.get(0);
+ if (userId != null) {
+ String chunks[] = userId.split(" <", 2);
+ userId = chunks[0];
+ if (chunks.length > 1) {
+ mainUserIdRest.setText("<" + chunks[1]);
+ }
+ mainUserId.setText(userId);
+ }
+
+ keyId.setText(PgpKeyHelper.convertKeyIdToHex(keyInfo.keyId));
+
+ if (mainUserIdRest.getText().length() == 0) {
+ mainUserIdRest.setVisibility(View.GONE);
+ }
+
+ algorithm.setText("" + keyInfo.size + "/" + keyInfo.algorithm);
+
+ if (keyInfo.revoked != null) {
+ status.setText("revoked");
+ } else {
+ status.setVisibility(View.GONE);
+ }
+
+ LinearLayout ll = (LinearLayout) view.findViewById(R.id.list);
+ if (keyInfo.userIds.size() == 1) {
+ ll.setVisibility(View.GONE);
+ } else {
+ boolean first = true;
+ boolean second = true;
+ for (String uid : keyInfo.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.key_server_query_result_user_id, null);
+ uidView.setText(uid);
+ ll.addView(uidView);
+ second = false;
+ }
+ }
+
+ return view;
+ }
+ }
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyServerUploadActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyServerUploadActivity.java
new file mode 100644
index 000000000..9dca1094f
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyServerUploadActivity.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 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 android.app.ProgressDialog;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Message;
+import android.os.Messenger;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.ArrayAdapter;
+import android.widget.Spinner;
+import android.widget.Toast;
+
+import com.actionbarsherlock.app.SherlockFragmentActivity;
+import com.beardedhen.androidbootstrap.BootstrapButton;
+
+/**
+ * gpg --send-key activity
+ *
+ * Sends the selected public key to a key server
+ */
+public class KeyServerUploadActivity extends SherlockFragmentActivity {
+
+ // Not used in sourcode, but listed in AndroidManifest!
+ public static final String ACTION_EXPORT_KEY_TO_SERVER = Constants.INTENT_PREFIX
+ + "EXPORT_KEY_TO_SERVER";
+
+ public static final String EXTRA_KEYRING_ROW_ID = "key_row_id";
+
+ private BootstrapButton mUploadButton;
+ private Spinner mKeyServerSpinner;
+
+ @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();
+ }
+ });
+ }
+
+ 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);
+
+ // fill values for this action
+ Bundle data = new Bundle();
+
+ int keyRingId = getIntent().getIntExtra(EXTRA_KEYRING_ROW_ID, -1);
+ data.putInt(KeychainIntentService.UPLOAD_KEY_KEYRING_ROW_ID, keyRingId);
+
+ 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 ApgService
+ KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this,
+ R.string.progress_exporting, ProgressDialog.STYLE_HORIZONTAL) {
+ public void handleMessage(Message message) {
+ // handle messages by standard ApgHandler first
+ super.handleMessage(message);
+
+ if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
+
+ Toast.makeText(KeyServerUploadActivity.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/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesActivity.java
new file mode 100644
index 000000000..f73681f54
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesActivity.java
@@ -0,0 +1,221 @@
+/*
+ * 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 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 android.content.Intent;
+import android.os.Bundle;
+import android.preference.CheckBoxPreference;
+import android.preference.Preference;
+import android.preference.PreferenceScreen;
+
+import com.actionbarsherlock.app.ActionBar;
+import com.actionbarsherlock.app.SherlockPreferenceActivity;
+
+public class PreferencesActivity extends SherlockPreferenceActivity {
+ private IntegerListPreference mPassPhraseCacheTtl = null;
+ private IntegerListPreference mEncryptionAlgorithm = null;
+ private IntegerListPreference mHashAlgorithm = null;
+ private IntegerListPreference mMessageCompression = null;
+ private IntegerListPreference mFileCompression = null;
+ private CheckBoxPreference mAsciiArmour = null;
+ private CheckBoxPreference mForceV3Signatures = null;
+ private PreferenceScreen mKeyServerPreference = null;
+ private Preferences mPreferences;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ mPreferences = Preferences.getPreferences(this);
+ super.onCreate(savedInstanceState);
+
+ final ActionBar actionBar = getSupportActionBar();
+ actionBar.setDisplayShowTitleEnabled(true);
+ actionBar.setDisplayHomeAsUpEnabled(false);
+ actionBar.setHomeButtonEnabled(false);
+
+ addPreferencesFromResource(R.xml.preferences);
+
+ mPassPhraseCacheTtl = (IntegerListPreference) findPreference(Constants.pref.PASS_PHRASE_CACHE_TTL);
+ mPassPhraseCacheTtl.setValue("" + mPreferences.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());
+ mPreferences.setPassPhraseCacheTtl(Integer.parseInt(newValue.toString()));
+ return false;
+ }
+ });
+
+ mEncryptionAlgorithm = (IntegerListPreference) findPreference(Constants.pref.DEFAULT_ENCRYPTION_ALGORITHM);
+ 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("" + mPreferences.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());
+ mPreferences.setDefaultEncryptionAlgorithm(Integer.parseInt(newValue
+ .toString()));
+ return false;
+ }
+ });
+
+ mHashAlgorithm = (IntegerListPreference) findPreference(Constants.pref.DEFAULT_HASH_ALGORITHM);
+ 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("" + mPreferences.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());
+ mPreferences.setDefaultHashAlgorithm(Integer.parseInt(newValue.toString()));
+ return false;
+ }
+ });
+
+ mMessageCompression = (IntegerListPreference) findPreference(Constants.pref.DEFAULT_MESSAGE_COMPRESSION);
+ valueIds = new int[] { Id.choice.compression.none, Id.choice.compression.zip,
+ Id.choice.compression.zlib, Id.choice.compression.bzip2, };
+ 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) + ")", };
+ values = new String[valueIds.length];
+ for (int i = 0; i < values.length; ++i) {
+ values[i] = "" + valueIds[i];
+ }
+ mMessageCompression.setEntries(entries);
+ mMessageCompression.setEntryValues(values);
+ mMessageCompression.setValue("" + mPreferences.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());
+ mPreferences.setDefaultMessageCompression(Integer.parseInt(newValue
+ .toString()));
+ return false;
+ }
+ });
+
+ mFileCompression = (IntegerListPreference) findPreference(Constants.pref.DEFAULT_FILE_COMPRESSION);
+ mFileCompression.setEntries(entries);
+ mFileCompression.setEntryValues(values);
+ mFileCompression.setValue("" + mPreferences.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());
+ mPreferences.setDefaultFileCompression(Integer.parseInt(newValue.toString()));
+ return false;
+ }
+ });
+
+ mAsciiArmour = (CheckBoxPreference) findPreference(Constants.pref.DEFAULT_ASCII_ARMOUR);
+ mAsciiArmour.setChecked(mPreferences.getDefaultAsciiArmour());
+ mAsciiArmour.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ mAsciiArmour.setChecked((Boolean) newValue);
+ mPreferences.setDefaultAsciiArmour((Boolean) newValue);
+ return false;
+ }
+ });
+
+ mForceV3Signatures = (CheckBoxPreference) findPreference(Constants.pref.FORCE_V3_SIGNATURES);
+ mForceV3Signatures.setChecked(mPreferences.getForceV3Signatures());
+ mForceV3Signatures
+ .setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ mForceV3Signatures.setChecked((Boolean) newValue);
+ mPreferences.setForceV3Signatures((Boolean) newValue);
+ return false;
+ }
+ });
+
+ mKeyServerPreference = (PreferenceScreen) findPreference(Constants.pref.KEY_SERVERS);
+ String servers[] = mPreferences.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,
+ mPreferences.getKeyServers());
+ startActivityForResult(intent, Id.request.key_server_preference);
+ return false;
+ }
+ });
+ }
+
+ @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);
+ mPreferences.setKeyServers(servers);
+ mKeyServerPreference.setSummary(getResources().getQuantityString(
+ R.plurals.n_key_servers, servers.length, servers.length));
+ break;
+ }
+
+ default: {
+ super.onActivityResult(requestCode, resultCode, data);
+ break;
+ }
+ }
+ }
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesKeyServerActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesKeyServerActivity.java
new file mode 100644
index 000000000..674510550
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesKeyServerActivity.java
@@ -0,0 +1,126 @@
+/*
+ * 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 java.util.Vector;
+
+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 android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.actionbarsherlock.app.SherlockActivity;
+
+public class PreferencesKeyServerActivity extends SherlockActivity 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.setDoneCancelView(getSupportActionBar(), R.string.btn_okay,
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // ok
+ okClicked();
+ }
+ }, R.string.btn_do_not_save, 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 (int i = 0; i < servers.length; ++i) {
+ KeyServerEditor view = (KeyServerEditor) mInflater.inflate(
+ R.layout.key_server_editor, mEditors, false);
+ view.setEditorListener(this);
+ view.setValue(servers[i]);
+ mEditors.addView(view);
+ }
+ }
+ }
+
+ public void onDeleted(Editor editor) {
+ // nothing to do
+ }
+
+ 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/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyActivity.java
new file mode 100644
index 000000000..e1dfed12a
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyActivity.java
@@ -0,0 +1,144 @@
+/*
+ * 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 org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.helper.ActionBarHelper;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+
+import com.actionbarsherlock.app.SherlockFragmentActivity;
+
+public class SelectPublicKeyActivity extends SherlockFragmentActivity {
+
+ // 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 selectedMasterKeyIds[];
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Inflate a "Done"/"Cancel" custom action bar view
+ ActionBarHelper.setDoneCancelView(getSupportActionBar(), R.string.btn_okay,
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // ok
+ okClicked();
+ }
+ }, R.string.btn_do_not_save, 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(selectedMasterKeyIds);
+
+ // 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
+ selectedMasterKeyIds = 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/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyFragment.java
new file mode 100644
index 000000000..6bbab09b2
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyFragment.java
@@ -0,0 +1,253 @@
+/*
+ * 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 java.util.Date;
+import java.util.Vector;
+
+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 android.app.Activity;
+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.widget.ListView;
+
+public class SelectPublicKeyFragment extends ListFragmentWorkaround implements
+ LoaderManager.LoaderCallbacks<Cursor> {
+ public static final String ARG_PRESELECTED_KEY_IDS = "preselected_key_ids";
+
+ private Activity mActivity;
+ private SelectKeyCursorAdapter mAdapter;
+ private ListView mListView;
+
+ private long mSelectedMasterKeyIds[];
+
+ /**
+ * 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);
+ }
+
+ /**
+ * Define Adapter and Loader on create of Activity
+ */
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ mActivity = getSherlockActivity();
+ mListView = getListView();
+
+ mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
+
+ // Give some text to display if there is no data. In a real
+ // application this would come from a resource.
+ setEmptyText(getString(R.string.list_empty));
+
+ mAdapter = new SelectKeyCursorAdapter(mActivity, null, 0, mListView, 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 < mListView.getCount(); ++i) {
+ long keyId = mAdapter.getMasterKeyId(i);
+ for (int j = 0; j < masterKeyIds.length; ++j) {
+ if (keyId == masterKeyIds[j]) {
+ mListView.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 < mListView.getCount(); ++i) {
+ if (mListView.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 < mListView.getCount(); ++i) {
+ if (mListView.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) {
+ // 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.buildPublicKeyRingsUri();
+
+ // 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(available_keys." + Keys._ID + ") FROM " + Tables.KEYS
+ + " AS available_keys WHERE available_keys." + Keys.KEY_RING_ROW_ID + " = "
+ + KeychainDatabase.Tables.KEY_RINGS + "." + KeyRings._ID
+ + " AND available_keys." + Keys.IS_REVOKED + " = '0' AND available_keys."
+ + Keys.CAN_ENCRYPT + " = '1') AS "
+ + SelectKeyCursorAdapter.PROJECTION_ROW_AVAILABLE,
+ "(SELECT COUNT(valid_keys." + Keys._ID + ") FROM " + Tables.KEYS
+ + " AS valid_keys WHERE valid_keys." + Keys.KEY_RING_ROW_ID + " = "
+ + KeychainDatabase.Tables.KEY_RINGS + "." + KeyRings._ID
+ + " AND valid_keys." + Keys.IS_REVOKED + " = '0' AND valid_keys."
+ + Keys.CAN_ENCRYPT + " = '1' AND valid_keys." + Keys.CREATION + " <= '"
+ + now + "' AND " + "(valid_keys." + Keys.EXPIRY + " IS NULL OR valid_keys."
+ + Keys.EXPIRY + " >= '" + now + "')) AS "
+ + SelectKeyCursorAdapter.PROJECTION_ROW_VALID, };
+
+ String inMasterKeyList = null;
+ if (mSelectedMasterKeyIds != null && mSelectedMasterKeyIds.length > 0) {
+ inMasterKeyList = KeyRings.MASTER_KEY_ID + " IN (";
+ for (int i = 0; i < mSelectedMasterKeyIds.length; ++i) {
+ if (i != 0) {
+ inMasterKeyList += ", ";
+ }
+ inMasterKeyList += DatabaseUtils.sqlEscapeString("" + mSelectedMasterKeyIds[i]);
+ }
+ inMasterKeyList += ")";
+ }
+
+ // if (searchString != null && searchString.trim().length() > 0) {
+ // String[] chunks = searchString.trim().split(" +");
+ // qb.appendWhere("(EXISTS (SELECT tmp." + UserIds._ID + " FROM " + UserIds.TABLE_NAME
+ // + " AS tmp WHERE " + "tmp." + UserIds.KEY_ID + " = " + Keys.TABLE_NAME + "."
+ // + Keys._ID);
+ // for (int i = 0; i < chunks.length; ++i) {
+ // qb.appendWhere(" AND tmp." + UserIds.USER_ID + " LIKE ");
+ // qb.appendWhereEscapeString("%" + chunks[i] + "%");
+ // }
+ // qb.appendWhere("))");
+ //
+ // if (inIdList != null) {
+ // qb.appendWhere(" OR (" + inIdList + ")");
+ // }
+ // }
+
+ String orderBy = UserIds.USER_ID + " ASC";
+ if (inMasterKeyList != null) {
+ // sort by selected master keys
+ orderBy = inMasterKeyList + " DESC, " + orderBy;
+ }
+
+ // Now create and return a CursorLoader that will take care of
+ // creating a Cursor for the data being displayed.
+ return new CursorLoader(getActivity(), baseUri, projection, null, 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);
+ }
+
+ // 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);
+ }
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectSecretKeyActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectSecretKeyActivity.java
new file mode 100644
index 000000000..57587cc75
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectSecretKeyActivity.java
@@ -0,0 +1,139 @@
+/*
+ * 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 org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+
+import android.content.Intent;
+import android.os.Bundle;
+
+import com.actionbarsherlock.app.ActionBar;
+import com.actionbarsherlock.app.SherlockFragmentActivity;
+import com.actionbarsherlock.view.Menu;
+
+public class SelectSecretKeyActivity extends SherlockFragmentActivity {
+
+ // Actions for internal use only:
+ public static final String ACTION_SELECT_SECRET_KEY = Constants.INTENT_PREFIX
+ + "SELECT_SECRET_KEYRING";
+
+ public static final String EXTRA_FILTER_CERTIFY = "filter_certify";
+
+ public static final String RESULT_EXTRA_MASTER_KEY_ID = "master_key_id";
+ public static final String RESULT_EXTRA_USER_ID = "user_id";
+
+ private boolean mFilterCertify = false;
+ 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);
+
+ setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
+
+ // 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());
+ // }
+ // });
+
+ mFilterCertify = getIntent().getBooleanExtra(EXTRA_FILTER_CERTIFY, false);
+
+ handleIntent(getIntent());
+
+ // 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 masterKeyId
+ * @param userId
+ */
+ public void afterListSelection(long masterKeyId, String userId) {
+ Intent data = new Intent();
+ data.putExtra(RESULT_EXTRA_MASTER_KEY_ID, masterKeyId);
+ data.putExtra(RESULT_EXTRA_USER_ID, (String) userId);
+ setResult(RESULT_OK, data);
+ finish();
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ handleIntent(intent);
+ }
+
+ private void handleIntent(Intent intent) {
+ // TODO: reimplement!
+
+ // 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));
+ // }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ // TODO: reimplement!
+ // menu.add(0, Id.menu.option.search, 0, R.string.menu_search).setIcon(
+ // android.R.drawable.ic_menu_search);
+ return true;
+ }
+
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectSecretKeyFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectSecretKeyFragment.java
new file mode 100644
index 000000000..6083ba00d
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectSecretKeyFragment.java
@@ -0,0 +1,188 @@
+/*
+ * 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 java.util.Date;
+
+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 android.database.Cursor;
+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.view.View;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ListView;
+
+import com.actionbarsherlock.app.SherlockListFragment;
+
+public class SelectSecretKeyFragment extends SherlockListFragment 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) getSherlockActivity();
+ mListView = getListView();
+
+ mListView.setOnItemClickListener(new OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
+ long masterKeyId = mAdapter.getMasterKeyId(position);
+ String userId = mAdapter.getUserId(position);
+
+ // return data to activity, which results in finishing it
+ mActivity.afterListSelection(masterKeyId, userId);
+ }
+ });
+
+ // 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.buildSecretKeyRingsUri();
+
+ String CapFilter = null;
+ if (mFilterCertify) {
+ CapFilter = "(cert > 0)";
+ }
+
+ // 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(cert_keys." + Keys._ID + ") FROM " + Tables.KEYS
+ + " AS cert_keys WHERE cert_keys." + Keys.KEY_RING_ROW_ID + " = "
+ + KeychainDatabase.Tables.KEY_RINGS + "." + KeyRings._ID + " AND cert_keys."
+ + Keys.CAN_CERTIFY + " = '1') AS cert",
+ "(SELECT COUNT(available_keys." + Keys._ID + ") FROM " + Tables.KEYS
+ + " AS available_keys WHERE available_keys." + Keys.KEY_RING_ROW_ID + " = "
+ + KeychainDatabase.Tables.KEY_RINGS + "." + KeyRings._ID
+ + " AND available_keys." + Keys.IS_REVOKED + " = '0' AND available_keys."
+ + Keys.CAN_SIGN + " = '1') AS "
+ + SelectKeyCursorAdapter.PROJECTION_ROW_AVAILABLE,
+ "(SELECT COUNT(valid_keys." + Keys._ID + ") FROM " + Tables.KEYS
+ + " AS valid_keys WHERE valid_keys." + Keys.KEY_RING_ROW_ID + " = "
+ + KeychainDatabase.Tables.KEY_RINGS + "." + KeyRings._ID + " AND valid_keys."
+ + Keys.IS_REVOKED + " = '0' AND valid_keys." + Keys.CAN_SIGN
+ + " = '1' AND valid_keys." + Keys.CREATION + " <= '" + now + "' AND "
+ + "(valid_keys." + Keys.EXPIRY + " IS NULL OR valid_keys." + Keys.EXPIRY
+ + " >= '" + now + "')) AS " + SelectKeyCursorAdapter.PROJECTION_ROW_VALID, };
+
+ // if (searchString != null && searchString.trim().length() > 0) {
+ // String[] chunks = searchString.trim().split(" +");
+ // qb.appendWhere("EXISTS (SELECT tmp." + UserIds._ID + " FROM " + UserIds.TABLE_NAME
+ // + " AS tmp WHERE " + "tmp." + UserIds.KEY_ID + " = " + Keys.TABLE_NAME + "."
+ // + Keys._ID);
+ // for (int i = 0; i < chunks.length; ++i) {
+ // qb.appendWhere(" AND tmp." + UserIds.USER_ID + " LIKE ");
+ // qb.appendWhereEscapeString("%" + chunks[i] + "%");
+ // }
+ // qb.appendWhere(")");
+ // }
+
+ String orderBy = UserIds.USER_ID + " ASC";
+
+ // 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, CapFilter, 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/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectSecretKeyLayoutFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectSecretKeyLayoutFragment.java
new file mode 100644
index 000000000..221325613
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectSecretKeyLayoutFragment.java
@@ -0,0 +1,139 @@
+/*
+ * 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 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.ProviderHelper;
+
+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.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.beardedhen.androidbootstrap.BootstrapButton;
+
+public class SelectSecretKeyLayoutFragment extends Fragment {
+
+ private TextView mKeyUserId;
+ private TextView mKeyUserIdRest;
+ private BootstrapButton mSelectKeyButton;
+
+ private SelectSecretKeyCallback mCallback;
+
+ private static final int REQUEST_CODE_SELECT_KEY = 8882;
+
+ public interface SelectSecretKeyCallback {
+ void onKeySelected(long secretKeyId);
+ }
+
+ public void setCallback(SelectSecretKeyCallback callback) {
+ mCallback = callback;
+ }
+
+ public void selectKey(long secretKeyId) {
+ if (secretKeyId == Id.key.none) {
+ mKeyUserId.setText(R.string.api_settings_no_key);
+ mKeyUserIdRest.setText("");
+ } else {
+ String uid = getResources().getString(R.string.unknown_user_id);
+ String uidExtra = "";
+ PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRingByMasterKeyId(
+ getActivity(), secretKeyId);
+ if (keyRing != null) {
+ PGPSecretKey key = PgpKeyHelper.getMasterKey(keyRing);
+ if (key != null) {
+ String userId = PgpKeyHelper.getMainUserIdSafe(getActivity(), key);
+ String chunks[] = userId.split(" <", 2);
+ uid = chunks[0];
+ if (chunks.length > 1) {
+ uidExtra = "<" + chunks[1];
+ }
+ }
+ }
+ mKeyUserId.setText(uid);
+ mKeyUserIdRest.setText(uidExtra);
+ }
+ }
+
+ public void setError(String error) {
+ mKeyUserId.requestFocus();
+ mKeyUserId.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);
+
+ mKeyUserId = (TextView) view.findViewById(R.id.select_secret_key_user_id);
+ mKeyUserIdRest = (TextView) view.findViewById(R.id.select_secret_key_user_id_rest);
+ mSelectKeyButton = (BootstrapButton) view
+ .findViewById(R.id.select_secret_key_select_key_button);
+ mSelectKeyButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ startSelectKeyActivity();
+ }
+ });
+
+ return view;
+ }
+
+ private void startSelectKeyActivity() {
+ Intent intent = new Intent(getActivity(), SelectSecretKeyActivity.class);
+ startActivityForResult(intent, REQUEST_CODE_SELECT_KEY);
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ switch (requestCode & 0xFFFF) {
+ case REQUEST_CODE_SELECT_KEY: {
+ long secretKeyId;
+ if (resultCode == Activity.RESULT_OK) {
+ Bundle bundle = data.getExtras();
+ secretKeyId = bundle.getLong(SelectSecretKeyActivity.RESULT_EXTRA_MASTER_KEY_ID);
+
+ selectKey(secretKeyId);
+
+ // remove displayed errors
+ mKeyUserId.setError(null);
+
+ // give value back to callback
+ mCallback.onKeySelected(secretKeyId);
+ }
+ break;
+ }
+
+ default:
+ super.onActivityResult(requestCode, resultCode, data);
+
+ break;
+ }
+ }
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SignKeyActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SignKeyActivity.java
new file mode 100644
index 000000000..3370a0564
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SignKeyActivity.java
@@ -0,0 +1,308 @@
+/*
+ * 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 java.util.Iterator;
+
+import org.spongycastle.openpgp.PGPPublicKeyRing;
+import org.spongycastle.openpgp.PGPSignature;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.helper.Preferences;
+import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
+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.dialog.PassphraseDialogFragment;
+import org.sufficientlysecure.keychain.util.Log;
+
+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.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.Spinner;
+import android.widget.Toast;
+
+import com.actionbarsherlock.app.ActionBar;
+import com.actionbarsherlock.app.SherlockFragmentActivity;
+import com.beardedhen.androidbootstrap.BootstrapButton;
+
+/**
+ * gpg --sign-key
+ *
+ * signs the specified public key with the specified secret master key
+ */
+public class SignKeyActivity extends SherlockFragmentActivity implements
+ SelectSecretKeyLayoutFragment.SelectSecretKeyCallback {
+
+ public static final String EXTRA_KEY_ID = "key_id";
+
+ private long mPubKeyId = 0;
+ private long mMasterKeyId = 0;
+
+ private BootstrapButton mSignButton;
+ private CheckBox mUploadKeyCheckbox;
+ private Spinner mSelectKeyserverSpinner;
+
+ private SelectSecretKeyLayoutFragment mSelectKeyFragment;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.sign_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);
+
+ 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();
+ }
+ }
+ }
+ });
+
+ mPubKeyId = getIntent().getLongExtra(EXTRA_KEY_ID, 0);
+ if (mPubKeyId == 0) {
+ Log.e(Constants.TAG, "No pub key id given!");
+ finish();
+ }
+ }
+
+ private void showPassphraseDialog(final long secretKeyId) {
+ // Message is received after passphrase is cached
+ Handler returnHandler = new Handler() {
+ @Override
+ public void handleMessage(Message message) {
+ if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) {
+ startSigning();
+ }
+ }
+ };
+
+ // Create a new Messenger for the communication back
+ Messenger messenger = new Messenger(returnHandler);
+
+ try {
+ PassphraseDialogFragment passphraseDialog = PassphraseDialogFragment.newInstance(this,
+ messenger, secretKeyId);
+
+ passphraseDialog.show(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);
+ }
+ }
+
+ /**
+ * handles the UI bits of the signing process on the UI thread
+ */
+ private void initiateSigning() {
+ PGPPublicKeyRing pubring = ProviderHelper.getPGPPublicKeyRingByMasterKeyId(this, mPubKeyId);
+ if (pubring != null) {
+ // if we have already signed this key, dont bother doing it again
+ boolean alreadySigned = false;
+
+ @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) {
+ showPassphraseDialog(mMasterKeyId);
+ return; // bail out; need to wait until the user has entered the passphrase
+ // before trying again
+ } 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() {
+ // Send all information needed to service to sign key in other thread
+ Intent intent = new Intent(this, KeychainIntentService.class);
+
+ intent.setAction(KeychainIntentService.ACTION_SIGN_KEYRING);
+
+ // fill values for this action
+ Bundle data = new Bundle();
+
+ data.putLong(KeychainIntentService.SIGN_KEY_MASTER_KEY_ID, mMasterKeyId);
+ data.putLong(KeychainIntentService.SIGN_KEY_PUB_KEY_ID, mPubKeyId);
+
+ intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
+
+ // Message is received after signing is done in ApgService
+ KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this,
+ R.string.progress_signing, ProgressDialog.STYLE_SPINNER) {
+ public void handleMessage(Message message) {
+ // handle messages by standard ApgHandler first
+ super.handleMessage(message);
+
+ if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
+
+ Toast.makeText(SignKeyActivity.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 key server
+ */
+ uploadKey();
+ } else {
+ 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);
+
+ // fill values for this action
+ Bundle data = new Bundle();
+
+ data.putLong(KeychainIntentService.UPLOAD_KEY_KEYRING_ROW_ID, mPubKeyId);
+
+ 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 ApgService
+ KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this,
+ R.string.progress_exporting, ProgressDialog.STYLE_HORIZONTAL) {
+ public void handleMessage(Message message) {
+ // handle messages by standard ApgHandler first
+ super.handleMessage(message);
+
+ if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
+
+ Toast.makeText(SignKeyActivity.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);
+ }
+
+ /**
+ * callback from select key fragment
+ */
+ @Override
+ public void onKeySelected(long secretKeyId) {
+ mMasterKeyId = secretKeyId;
+ }
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java
new file mode 100644
index 000000000..ea86bbfca
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java
@@ -0,0 +1,484 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.Date;
+
+import org.spongycastle.openpgp.PGPPublicKey;
+import org.spongycastle.openpgp.PGPPublicKeyRing;
+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.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.ui.dialog.DeleteKeyDialogFragment;
+import org.sufficientlysecure.keychain.ui.dialog.ShareNfcDialogFragment;
+import org.sufficientlysecure.keychain.ui.dialog.ShareQrCodeDialogFragment;
+import org.sufficientlysecure.keychain.util.Log;
+
+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.support.v4.app.LoaderManager;
+import android.support.v4.content.CursorLoader;
+import android.support.v4.content.Loader;
+import android.text.format.DateFormat;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.actionbarsherlock.app.SherlockFragmentActivity;
+import com.actionbarsherlock.view.Menu;
+import com.actionbarsherlock.view.MenuItem;
+import com.beardedhen.androidbootstrap.BootstrapButton;
+
+public class ViewKeyActivity extends SherlockFragmentActivity implements
+ LoaderManager.LoaderCallbacks<Cursor> {
+
+ ExportHelper mExportHelper;
+
+ protected Uri mDataUri;
+
+ 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 BootstrapButton mActionEncrypt;
+
+ private ListView mUserIds;
+ private ListView mKeys;
+
+ private static final int LOADER_ID_KEYRING = 0;
+ private static final int LOADER_ID_USER_IDS = 1;
+ private static final int LOADER_ID_KEYS = 2;
+ private ViewKeyUserIdsAdapter mUserIdsAdapter;
+ private ViewKeyKeysAdapter mKeysAdapter;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mExportHelper = new ExportHelper(this);
+
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ getSupportActionBar().setIcon(android.R.color.transparent);
+ getSupportActionBar().setHomeButtonEnabled(true);
+
+ setContentView(R.layout.view_key_activity);
+
+ mName = (TextView) findViewById(R.id.name);
+ mEmail = (TextView) findViewById(R.id.email);
+ mComment = (TextView) findViewById(R.id.comment);
+ mKeyId = (TextView) findViewById(R.id.key_id);
+ mAlgorithm = (TextView) findViewById(R.id.algorithm);
+ mCreation = (TextView) findViewById(R.id.creation);
+ mExpiry = (TextView) findViewById(R.id.expiry);
+ mFingerprint = (TextView) findViewById(R.id.fingerprint);
+ mActionEncrypt = (BootstrapButton) findViewById(R.id.action_encrypt);
+ mUserIds = (ListView) findViewById(R.id.user_ids);
+ mKeys = (ListView) findViewById(R.id.keys);
+
+ Intent intent = getIntent();
+ mDataUri = intent.getData();
+ if (mDataUri == null) {
+ Log.e(Constants.TAG, "Intent data missing. Should be Uri of key!");
+ finish();
+ return;
+ } else {
+ Log.d(Constants.TAG, "uri: " + mDataUri);
+ loadData(mDataUri);
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ getSupportMenuInflater().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, KeyListPublicActivity.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_sign:
+ signKey(mDataUri);
+ return true;
+ case R.id.menu_key_view_export_keyserver:
+ uploadToKeyserver(mDataUri);
+ return true;
+ case R.id.menu_key_view_export_file:
+ mExportHelper.showExportKeysDialog(mDataUri, Id.type.public_key, Constants.path.APP_DIR
+ + "/pubexport.asc");
+ 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: {
+ // Message is received after key is deleted
+ Handler returnHandler = new Handler() {
+ @Override
+ public void handleMessage(Message message) {
+ if (message.what == DeleteKeyDialogFragment.MESSAGE_OKAY) {
+ setResult(RESULT_CANCELED);
+ finish();
+ }
+ }
+ };
+
+ mExportHelper.deleteKey(mDataUri, Id.type.public_key, returnHandler);
+ return true;
+ }
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ private void loadData(Uri dataUri) {
+ mActionEncrypt.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ // TODO: don't get object here!!! solve this differently!
+ PGPPublicKeyRing ring = (PGPPublicKeyRing) ProviderHelper.getPGPKeyRing(
+ ViewKeyActivity.this, mDataUri);
+ PGPPublicKey publicKey = ring.getPublicKey();
+
+ long[] encryptionKeyIds = new long[] { publicKey.getKeyID() };
+ Intent intent = new Intent(ViewKeyActivity.this, 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);
+ }
+ });
+
+ mUserIdsAdapter = new ViewKeyUserIdsAdapter(this, null, 0);
+
+ mUserIds.setAdapter(mUserIdsAdapter);
+ // mUserIds.setEmptyView(findViewById(android.R.id.empty));
+ // mUserIds.setClickable(true);
+ // mUserIds.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ // @Override
+ // public void onItemClick(AdapterView<?> arg0, View arg1, int position, long id) {
+ // }
+ // });
+
+ mKeysAdapter = new ViewKeyKeysAdapter(this, null, 0);
+
+ mKeys.setAdapter(mKeysAdapter);
+
+ // Prepare the loader. Either re-connect with an existing one,
+ // or start a new one.
+ getSupportLoaderManager().initLoader(LOADER_ID_KEYRING, null, this);
+ getSupportLoaderManager().initLoader(LOADER_ID_USER_IDS, null, this);
+ getSupportLoaderManager().initLoader(LOADER_ID_KEYS, null, this);
+ }
+
+ static final String[] KEYRING_PROJECTION = new String[] { KeyRings._ID, KeyRings.MASTER_KEY_ID,
+ UserIds.USER_ID };
+ static final int KEYRING_INDEX_ID = 0;
+ static final int KEYRING_INDEX_MASTER_KEY_ID = 1;
+ static final int KEYRING_INDEX_USER_ID = 2;
+
+ static final String[] USER_IDS_PROJECTION = new String[] { UserIds._ID, UserIds.USER_ID,
+ UserIds.RANK, };
+ // not the main user id
+ static final String USER_IDS_SELECTION = UserIds.RANK + " > 0 ";
+ static final String USER_IDS_SORT_ORDER = UserIds.USER_ID + " COLLATE LOCALIZED ASC";
+
+ static final String[] KEYS_PROJECTION = new String[] { Keys._ID, Keys.KEY_ID,
+ Keys.IS_MASTER_KEY, Keys.ALGORITHM, Keys.KEY_SIZE, Keys.CAN_CERTIFY, Keys.CAN_SIGN,
+ Keys.CAN_ENCRYPT, Keys.CREATION, Keys.EXPIRY };
+ static final String KEYS_SORT_ORDER = Keys.RANK + " ASC";
+ static final int KEYS_INDEX_ID = 0;
+ static final int KEYS_INDEX_KEY_ID = 1;
+ static final int KEYS_INDEX_IS_MASTER_KEY = 2;
+ static final int KEYS_INDEX_ALGORITHM = 3;
+ static final int KEYS_INDEX_KEY_SIZE = 4;
+ static final int KEYS_INDEX_CAN_CERTIFY = 5;
+ static final int KEYS_INDEX_CAN_SIGN = 6;
+ static final int KEYS_INDEX_CAN_ENCRYPT = 7;
+ static final int KEYS_INDEX_CREATION = 8;
+ static final int KEYS_INDEX_EXPIRY = 9;
+
+ public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+ switch (id) {
+ case LOADER_ID_KEYRING: {
+ Uri baseUri = mDataUri;
+
+ // Now create and return a CursorLoader that will take care of
+ // creating a Cursor for the data being displayed.
+ return new CursorLoader(this, baseUri, KEYRING_PROJECTION, null, null, null);
+ }
+ case LOADER_ID_USER_IDS: {
+ Uri baseUri = UserIds.buildUserIdsUri(mDataUri);
+
+ // Now create and return a CursorLoader that will take care of
+ // creating a Cursor for the data being displayed.
+ return new CursorLoader(this, baseUri, USER_IDS_PROJECTION, USER_IDS_SELECTION, null,
+ USER_IDS_SORT_ORDER);
+ }
+ case LOADER_ID_KEYS: {
+ Uri baseUri = Keys.buildKeysUri(mDataUri);
+
+ // Now create and return a CursorLoader that will take care of
+ // creating a Cursor for the data being displayed.
+ return new CursorLoader(this, baseUri, KEYS_PROJECTION, null, null, KEYS_SORT_ORDER);
+ }
+
+ default:
+ return null;
+ }
+ }
+
+ 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.)
+ switch (loader.getId()) {
+ case LOADER_ID_KEYRING:
+ if (data.moveToFirst()) {
+ // get name, email, and comment from USER_ID
+ String[] mainUserId = PgpKeyHelper.splitUserId(data
+ .getString(KEYRING_INDEX_USER_ID));
+ setTitle(mainUserId[0]);
+ mName.setText(mainUserId[0]);
+ mEmail.setText(mainUserId[1]);
+ mComment.setText(mainUserId[2]);
+ }
+
+ break;
+ case LOADER_ID_USER_IDS:
+ mUserIdsAdapter.swapCursor(data);
+ break;
+ case LOADER_ID_KEYS:
+ // the first key here is our master key
+ if (data.moveToFirst()) {
+ // get key id from MASTER_KEY_ID
+ long keyId = data.getLong(KEYS_INDEX_KEY_ID);
+
+ String keyIdStr = "0x" + PgpKeyHelper.convertKeyIdToHex(keyId);
+ mKeyId.setText(keyIdStr);
+
+ // get creation date from CREATION
+ if (data.isNull(KEYS_INDEX_CREATION)) {
+ mCreation.setText(R.string.none);
+ } else {
+ Date creationDate = new Date(data.getLong(KEYS_INDEX_CREATION) * 1000);
+
+ mCreation.setText(DateFormat.getDateFormat(getApplicationContext()).format(
+ creationDate));
+ }
+
+ // get creation date from EXPIRY
+ if (data.isNull(KEYS_INDEX_EXPIRY)) {
+ mExpiry.setText(R.string.none);
+ } else {
+ Date expiryDate = new Date(data.getLong(KEYS_INDEX_EXPIRY) * 1000);
+
+ mExpiry.setText(DateFormat.getDateFormat(getApplicationContext()).format(
+ expiryDate));
+ }
+
+ String algorithmStr = PgpKeyHelper.getAlgorithmInfo(
+ data.getInt(KEYS_INDEX_ALGORITHM), data.getInt(KEYS_INDEX_KEY_SIZE));
+ mAlgorithm.setText(algorithmStr);
+
+ // TODO: Can this be done better? fingerprint in db?
+ String fingerprint = PgpKeyHelper.getFingerPrint(this, keyId);
+
+ fingerprint = fingerprint.replace(" ", "\n");
+ mFingerprint.setText(fingerprint);
+
+ // TODO: get image with getUserAttributes() on key and then
+ // PGPUserAttributeSubpacketVector
+ }
+
+ mKeysAdapter.swapCursor(data);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ /**
+ * 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_KEYRING:
+ // TODO?
+ break;
+ case LOADER_ID_USER_IDS:
+ mUserIdsAdapter.swapCursor(null);
+ break;
+ case LOADER_ID_KEYS:
+ mKeysAdapter.swapCursor(null);
+ break;
+ default:
+ break;
+ }
+ }
+
+ private void uploadToKeyserver(Uri dataUri) {
+ long keyRingRowId = Long.valueOf(dataUri.getLastPathSegment());
+
+ Intent uploadIntent = new Intent(this, KeyServerUploadActivity.class);
+ uploadIntent.setAction(KeyServerUploadActivity.ACTION_EXPORT_KEY_TO_SERVER);
+ uploadIntent.putExtra(KeyServerUploadActivity.EXTRA_KEYRING_ROW_ID, (int) keyRingRowId);
+ startActivityForResult(uploadIntent, Id.request.export_to_server);
+ }
+
+ private void updateFromKeyserver(Uri dataUri) {
+ long updateKeyId = 0;
+ PGPPublicKeyRing updateRing = (PGPPublicKeyRing) ProviderHelper
+ .getPGPKeyRing(this, dataUri);
+
+ if (updateRing != null) {
+ updateKeyId = PgpKeyHelper.getMasterKey(updateRing).getKeyID();
+ }
+ if (updateKeyId == 0) {
+ Log.e(Constants.TAG, "this shouldn't happen. KeyId == 0!");
+ return;
+ }
+
+ Intent queryIntent = new Intent(this, KeyServerQueryActivity.class);
+ queryIntent.setAction(KeyServerQueryActivity.ACTION_LOOK_UP_KEY_ID_AND_RETURN);
+ queryIntent.putExtra(KeyServerQueryActivity.EXTRA_KEY_ID, updateKeyId);
+
+ // TODO: lookup??
+ startActivityForResult(queryIntent, Id.request.look_up_key_id);
+ }
+
+ private void signKey(Uri dataUri) {
+ long keyId = 0;
+ PGPPublicKeyRing signKey = (PGPPublicKeyRing) ProviderHelper.getPGPKeyRing(this, dataUri);
+
+ if (signKey != null) {
+ keyId = PgpKeyHelper.getMasterKey(signKey).getKeyID();
+ }
+ if (keyId == 0) {
+ Log.e(Constants.TAG, "this shouldn't happen. KeyId == 0!");
+ return;
+ }
+
+ Intent signIntent = new Intent(this, SignKeyActivity.class);
+ signIntent.putExtra(SignKeyActivity.EXTRA_KEY_ID, keyId);
+ startActivity(signIntent);
+ }
+
+ private void shareKey(Uri dataUri, boolean fingerprintOnly) {
+ String content = null;
+ if (fingerprintOnly) {
+ long masterKeyId = ProviderHelper.getMasterKeyId(this, dataUri);
+
+ // TODO: dublicated in ShareQrCodeDialog
+ content = "openpgp4fpr:";
+
+ String fingerprint = PgpKeyHelper.convertKeyToHex(masterKeyId);
+
+ content = content + fingerprint;
+ } else {
+ // get public keyring as ascii armored string
+ long masterKeyId = ProviderHelper.getMasterKeyId(this, dataUri);
+ ArrayList<String> keyringArmored = ProviderHelper.getKeyRingsAsArmoredString(this,
+ dataUri, 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, dataUri,
+ 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");
+ }
+
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivityJB.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivityJB.java
new file mode 100644
index 000000000..a2e3e4339
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivityJB.java
@@ -0,0 +1,122 @@
+/*
+ * 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 org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+
+import android.annotation.TargetApi;
+import android.database.Cursor;
+import android.net.Uri;
+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.support.v4.app.LoaderManager;
+import android.widget.Toast;
+
+@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+public class ViewKeyActivityJB extends ViewKeyActivity implements CreateNdefMessageCallback,
+ OnNdefPushCompleteCallback, LoaderManager.LoaderCallbacks<Cursor> {
+
+ // NFC
+ private NfcAdapter mNfcAdapter;
+ private byte[] mSharedKeyringBytes;
+ private static final int NFC_SENT = 1;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ initNfc(mDataUri);
+ }
+
+ /**
+ * NFC: Initialize NFC sharing if OS and device supports it
+ */
+ private void initNfc(Uri dataUri) {
+ // 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
+
+ // get public keyring as byte array
+ long masterKeyId = ProviderHelper.getMasterKeyId(this, dataUri);
+ mSharedKeyringBytes = ProviderHelper.getKeyRingsAsByteArray(this, dataUri,
+ new long[] { masterKeyId });
+
+ // 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.
+ */
+ NdefMessage msg = new NdefMessage(NdefRecord.createMime(Constants.NFC_MIME,
+ mSharedKeyringBytes), NdefRecord.createApplicationRecord(Constants.PACKAGE_NAME));
+ return msg;
+ }
+
+ /**
+ * 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:
+ Toast.makeText(getApplicationContext(), R.string.nfc_successfull, Toast.LENGTH_LONG)
+ .show();
+ break;
+ }
+ }
+ };
+
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java
new file mode 100644
index 000000000..41539d0f8
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java
@@ -0,0 +1,159 @@
+/*
+ * 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 java.util.List;
+
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
+
+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;
+
+public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> {
+ protected LayoutInflater mInflater;
+ protected Activity mActivity;
+
+ protected List<ImportKeysListEntry> data;
+
+ 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.data = 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 data;
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return true;
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ ImportKeysListEntry entry = data.get(position);
+
+ View view = mInflater.inflate(R.layout.import_keys_list_entry, null);
+
+ TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId);
+ mainUserId.setText(R.string.unknown_user_id);
+ TextView mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest);
+ mainUserIdRest.setText("");
+ TextView keyId = (TextView) view.findViewById(R.id.keyId);
+ keyId.setText(R.string.no_key);
+ TextView fingerprint = (TextView) view.findViewById(R.id.fingerprint);
+ TextView algorithm = (TextView) view.findViewById(R.id.algorithm);
+ algorithm.setText("");
+ TextView status = (TextView) view.findViewById(R.id.status);
+ status.setText("");
+
+ String userId = entry.userIds.get(0);
+ if (userId != null) {
+ String[] userIdSplit = PgpKeyHelper.splitUserId(userId);
+
+ if (userIdSplit[0] != null && userIdSplit[0].length() > 0) {
+ // show red user id if it is a secret key
+ if (entry.secretKey) {
+ userId = mActivity.getString(R.string.secret_key) + " " + userId;
+ mainUserId.setTextColor(Color.RED);
+ } else {
+ mainUserId.setText(userIdSplit[0]);
+ }
+ }
+
+ if (userIdSplit[1] != null && userIdSplit[1].length() > 0) {
+ mainUserIdRest.setText(userIdSplit[1]);
+ mainUserIdRest.setVisibility(View.VISIBLE);
+ } else {
+ mainUserIdRest.setVisibility(View.GONE);
+ }
+
+ }
+
+ keyId.setText(entry.hexKeyId);
+ fingerprint.setText(mActivity.getString(R.string.fingerprint) + " " + entry.fingerPrint);
+
+ algorithm.setText("" + entry.bitStrength + "/" + entry.algorithm);
+
+ if (entry.revoked) {
+ status.setText("revoked");
+ } else {
+ status.setVisibility(View.GONE);
+ }
+
+ LinearLayout ll = (LinearLayout) view.findViewById(R.id.list);
+ 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) view.findViewById(R.id.selected);
+ cBox.setChecked(entry.isSelected());
+
+ return view;
+ }
+
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListEntry.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListEntry.java
new file mode 100644
index 000000000..1a100a585
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListEntry.java
@@ -0,0 +1,105 @@
+/*
+ * 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 java.io.Serializable;
+import java.util.ArrayList;
+
+import org.spongycastle.openpgp.PGPKeyRing;
+import org.spongycastle.openpgp.PGPPublicKey;
+import org.spongycastle.openpgp.PGPSecretKeyRing;
+import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
+import org.sufficientlysecure.keychain.util.AlgorithmNames;
+import org.sufficientlysecure.keychain.util.IterableIterator;
+
+public class ImportKeysListEntry implements Serializable {
+ private static final long serialVersionUID = -7797972103284992662L;
+ public ArrayList<String> userIds;
+ public long keyId;
+
+ public boolean revoked;
+ // public Date date;
+ public String fingerPrint;
+ public String hexKeyId;
+ public int bitStrength;
+ public String algorithm;
+ public boolean secretKey;
+ AlgorithmNames algorithmNames;
+
+ private boolean selected;
+
+ /**
+ * Constructor for later querying from keyserver
+ */
+ public ImportKeysListEntry() {
+ secretKey = false;
+ userIds = new ArrayList<String>();
+ }
+
+ public boolean isSelected() {
+ return selected;
+ }
+
+ public void setSelected(boolean selected) {
+ this.selected = selected;
+ }
+
+ /**
+ * Constructor based on key object, used for import from NFC, QR Codes, files
+ *
+ * @param pgpKey
+ */
+ @SuppressWarnings("unchecked")
+ public ImportKeysListEntry(PGPKeyRing pgpKeyRing) {
+ // selected is default
+ this.selected = 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.revoked = pgpKeyRing.getPublicKey().isRevoked();
+ this.fingerPrint = PgpKeyHelper.convertFingerprintToHex(pgpKeyRing.getPublicKey()
+ .getFingerprint());
+ this.hexKeyId = PgpKeyHelper.convertKeyIdToHex(keyId);
+ this.bitStrength = pgpKeyRing.getPublicKey().getBitStrength();
+ int algorithm = pgpKeyRing.getPublicKey().getAlgorithm();
+ if (algorithm == PGPPublicKey.RSA_ENCRYPT || algorithm == PGPPublicKey.RSA_GENERAL
+ || algorithm == PGPPublicKey.RSA_SIGN) {
+ this.algorithm = "RSA";
+ } else if (algorithm == PGPPublicKey.DSA) {
+ this.algorithm = "DSA";
+ } else if (algorithm == PGPPublicKey.ELGAMAL_ENCRYPT
+ || algorithm == PGPPublicKey.ELGAMAL_GENERAL) {
+ this.algorithm = "ElGamal";
+ } else if (algorithm == PGPPublicKey.EC || algorithm == PGPPublicKey.ECDSA) {
+ this.algorithm = "ECC";
+ } else {
+ // TODO: with resources
+ this.algorithm = "unknown";
+ }
+ }
+} \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java
new file mode 100644
index 000000000..00ad8c957
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java
@@ -0,0 +1,128 @@
+/*
+ * 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 java.io.BufferedInputStream;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+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 android.content.Context;
+import android.support.v4.content.AsyncTaskLoader;
+
+public class ImportKeysListLoader extends AsyncTaskLoader<List<ImportKeysListEntry>> {
+ Context mContext;
+
+ InputData mInputData;
+
+ ArrayList<ImportKeysListEntry> data = new ArrayList<ImportKeysListEntry>();
+
+ public ImportKeysListLoader(Context context, InputData inputData) {
+ super(context);
+ this.mContext = context;
+ this.mInputData = inputData;
+ }
+
+ @Override
+ public List<ImportKeysListEntry> loadInBackground() {
+ if (mInputData == null) {
+ Log.e(Constants.TAG, "Input data is null!");
+ return data;
+ }
+
+ generateListOfKeyrings(mInputData);
+
+ return data;
+ }
+
+ @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(List<ImportKeysListEntry> data) {
+ super.deliverResult(data);
+ }
+
+ /**
+ * Reads all PGPKeyRing objects from input
+ *
+ * @param keyringBytes
+ * @return
+ */
+ private void generateListOfKeyrings(InputData inputData) {
+ 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
+ // armour blocks
+ BufferedInputStream bufferedInput = new BufferedInputStream(progressIn);
+ try {
+
+ // read all available blocks... (asc files can contain many blocks with BEGIN END)
+ while (bufferedInput.available() > 0) {
+ 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!");
+ }
+ }
+ }
+ } catch (Exception e) {
+ Log.e(Constants.TAG, "Exception on parsing key file!", e);
+ }
+ }
+
+ private void addToData(PGPKeyRing keyring) {
+ ImportKeysListEntry item = new ImportKeysListEntry(keyring);
+ data.add(item);
+ }
+
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyListPublicAdapter.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyListPublicAdapter.java
new file mode 100644
index 000000000..42afa2e95
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyListPublicAdapter.java
@@ -0,0 +1,221 @@
+/*
+ * 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 java.util.HashMap;
+import java.util.Set;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
+import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
+import org.sufficientlysecure.keychain.util.Log;
+
+import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.Color;
+import android.support.v4.widget.CursorAdapter;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+/**
+ * Implements StickyListHeadersAdapter from library
+ */
+public class KeyListPublicAdapter extends CursorAdapter implements StickyListHeadersAdapter {
+ private LayoutInflater mInflater;
+ private int mSectionColumnIndex;
+ private int mIndexUserId;
+
+ @SuppressLint("UseSparseArrays")
+ private HashMap<Integer, Boolean> mSelection = new HashMap<Integer, Boolean>();
+
+ public KeyListPublicAdapter(Context context, Cursor c, int flags, int sectionColumnIndex) {
+ super(context, c, flags);
+
+ mInflater = LayoutInflater.from(context);
+ mSectionColumnIndex = sectionColumnIndex;
+ 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);
+ }
+ }
+
+ /**
+ * Bind cursor data to the item list view
+ *
+ * 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) {
+ TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId);
+ mainUserId.setText(R.string.unknown_user_id);
+ TextView mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest);
+ mainUserIdRest.setText("");
+
+ String userId = cursor.getString(mIndexUserId);
+ if (userId != null) {
+ String[] userIdSplit = PgpKeyHelper.splitUserId(userId);
+
+ if (userIdSplit[0] != null && userIdSplit[0].length() > 0) {
+ mainUserId.setText(userIdSplit[0]);
+ }
+
+ if (userIdSplit[1] != null && userIdSplit[1].length() > 0) {
+ mainUserIdRest.setText(userIdSplit[1]);
+ }
+ }
+ }
+
+ @Override
+ public View newView(Context context, Cursor cursor, ViewGroup parent) {
+ return mInflater.inflate(R.layout.key_list_item, null);
+ }
+
+ /**
+ * 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.
+ *
+ * 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_public_header, parent, false);
+ holder.text = (TextView) convertView.findViewById(R.id.stickylist_header_text);
+ 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(mSectionColumnIndex);
+ String headerText = convertView.getResources().getString(R.string.unknown_user_id);
+ if (userId != null && userId.length() > 0) {
+ headerText = "" + mCursor.getString(mSectionColumnIndex).subSequence(0, 1).charAt(0);
+ }
+ holder.text.setText(headerText);
+ 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);
+ }
+
+ // return the first character of the name as ID because this is what
+ // headers private HashMap<Integer, Boolean> mSelection = new HashMap<Integer,
+ // Boolean>();are based upon
+ String userId = mCursor.getString(mSectionColumnIndex);
+ if (userId != null && userId.length() > 0) {
+ return userId.subSequence(0, 1).charAt(0);
+ } else {
+ return Long.MAX_VALUE;
+ }
+ }
+
+ class HeaderViewHolder {
+ TextView text;
+ }
+
+ /** -------------------------- MULTI-SELECTION METHODS -------------- */
+ public void setNewSelection(int position, boolean value) {
+ mSelection.put(position, value);
+ notifyDataSetChanged();
+ }
+
+ public boolean isPositionChecked(int position) {
+ Boolean result = mSelection.get(position);
+ return result == null ? false : result;
+ }
+
+ public Set<Integer> getCurrentCheckedPosition() {
+ return mSelection.keySet();
+ }
+
+ 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
+ */
+ // default color
+ v.setBackgroundColor(Color.TRANSPARENT);
+ if (mSelection.get(position) != null) {
+ // this is a selected position, change color!
+ v.setBackgroundColor(parent.getResources().getColor(R.color.emphasis));
+ }
+ return v;
+ }
+
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyListSecretAdapter.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyListSecretAdapter.java
new file mode 100644
index 000000000..0043203bf
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyListSecretAdapter.java
@@ -0,0 +1,139 @@
+/*
+ * 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 java.util.HashMap;
+import java.util.Set;
+
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
+import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.Color;
+import android.support.v4.widget.CursorAdapter;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+public class KeyListSecretAdapter extends CursorAdapter {
+ private LayoutInflater mInflater;
+
+ private int mIndexUserId;
+
+ @SuppressLint("UseSparseArrays")
+ private HashMap<Integer, Boolean> mSelection = new HashMap<Integer, Boolean>();
+
+ public KeyListSecretAdapter(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) {
+ mIndexUserId = cursor.getColumnIndexOrThrow(UserIds.USER_ID);
+ }
+ }
+
+ @Override
+ public void bindView(View view, Context context, Cursor cursor) {
+ TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId);
+ mainUserId.setText(R.string.unknown_user_id);
+ TextView mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest);
+ mainUserIdRest.setText("");
+
+ String userId = cursor.getString(mIndexUserId);
+ if (userId != null) {
+ String[] userIdSplit = PgpKeyHelper.splitUserId(userId);
+
+ if (userIdSplit[0] != null && userIdSplit[0].length() > 0) {
+ mainUserId.setText(userIdSplit[0]);
+ }
+
+ if (userIdSplit[1] != null && userIdSplit[1].length() > 0) {
+ mainUserIdRest.setText(userIdSplit[1]);
+ }
+ }
+ }
+
+ @Override
+ public View newView(Context context, Cursor cursor, ViewGroup parent) {
+ return mInflater.inflate(R.layout.key_list_item, null);
+ }
+
+ /** -------------------------- MULTI-SELECTION METHODS -------------- */
+ public void setNewSelection(int position, boolean value) {
+ mSelection.put(position, value);
+ notifyDataSetChanged();
+ }
+
+ public boolean isPositionChecked(int position) {
+ Boolean result = mSelection.get(position);
+ return result == null ? false : result;
+ }
+
+ public Set<Integer> getCurrentCheckedPosition() {
+ return mSelection.keySet();
+ }
+
+ 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
+ */
+ // default color
+ v.setBackgroundColor(Color.TRANSPARENT);
+ if (mSelection.get(position) != null) {
+ // this is a selected position, change color!
+ v.setBackgroundColor(parent.getResources().getColor(R.color.emphasis));
+ }
+ return v;
+ }
+
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyValueSpinnerAdapter.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyValueSpinnerAdapter.java
new file mode 100644
index 000000000..78f7b1f7e
--- /dev/null
+++ b/OpenPGP-Keychain/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 java.util.Comparator;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import android.content.Context;
+import android.widget.ArrayAdapter;
+
+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;
+ }
+} \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java
new file mode 100644
index 000000000..f4a5e1a5d
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java
@@ -0,0 +1,167 @@
+/*
+ * 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 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;
+
+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.CheckBox;
+import android.widget.ListView;
+import android.widget.TextView;
+
+public class SelectKeyCursorAdapter extends CursorAdapter {
+
+ protected int mKeyType;
+
+ private LayoutInflater mInflater;
+ private ListView mListView;
+
+ private int mIndexUserId;
+ private int mIndexMasterKeyId;
+ private int mIndexProjectionValid;
+ private int mIndexProjectionAvailable;
+
+ public final static String PROJECTION_ROW_AVAILABLE = "available";
+ public final static 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);
+ mainUserId.setText(R.string.unknown_user_id);
+ TextView mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest);
+ mainUserIdRest.setText("");
+ TextView keyId = (TextView) view.findViewById(R.id.keyId);
+ keyId.setText(R.string.no_key);
+ TextView status = (TextView) view.findViewById(R.id.status);
+ status.setText(R.string.unknown_status);
+
+ String userId = cursor.getString(mIndexUserId);
+ if (userId != null) {
+ String[] userIdSplit = PgpKeyHelper.splitUserId(userId);
+
+ if (userIdSplit[0] != null && userIdSplit[0].length() > 0) {
+ mainUserId.setText(userIdSplit[0]);
+ }
+
+ if (userIdSplit[1] != null && userIdSplit[1].length() > 0) {
+ mainUserIdRest.setText(userIdSplit[1]);
+ }
+ }
+
+ long masterKeyId = cursor.getLong(mIndexMasterKeyId);
+ keyId.setText(PgpKeyHelper.convertKeyIdToHex(masterKeyId));
+
+ 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/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyKeysAdapter.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyKeysAdapter.java
new file mode 100644
index 000000000..d5162c403
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyKeysAdapter.java
@@ -0,0 +1,123 @@
+/*
+ * 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 org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
+import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
+
+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.ImageView;
+import android.widget.TextView;
+
+public class ViewKeyKeysAdapter extends CursorAdapter {
+ private LayoutInflater mInflater;
+
+ private int mIndexKeyId;
+ private int mIndexAlgorithm;
+ private int mIndexKeySize;
+ private int mIndexIsMasterKey;
+ private int mIndexCanCertify;
+ private int mIndexCanEncrypt;
+ private int mIndexCanSign;
+
+ 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);
+ mIndexIsMasterKey = cursor.getColumnIndexOrThrow(Keys.IS_MASTER_KEY);
+ mIndexCanCertify = cursor.getColumnIndexOrThrow(Keys.CAN_CERTIFY);
+ mIndexCanEncrypt = cursor.getColumnIndexOrThrow(Keys.CAN_ENCRYPT);
+ mIndexCanSign = cursor.getColumnIndexOrThrow(Keys.CAN_SIGN);
+ }
+ }
+
+ @Override
+ public void bindView(View view, Context context, Cursor cursor) {
+ String keyIdStr = "0x" + PgpKeyHelper.convertKeyIdToHex(cursor.getLong(mIndexKeyId));
+ String algorithmStr = PgpKeyHelper.getAlgorithmInfo(cursor.getInt(mIndexAlgorithm),
+ cursor.getInt(mIndexKeySize));
+
+ TextView keyId = (TextView) view.findViewById(R.id.keyId);
+ keyId.setText(keyIdStr);
+
+ TextView keyDetails = (TextView) view.findViewById(R.id.keyDetails);
+ keyDetails.setText("(" + algorithmStr + ")");
+
+ ImageView masterKeyIcon = (ImageView) view.findViewById(R.id.ic_masterKey);
+ if (cursor.getInt(mIndexIsMasterKey) != 1) {
+ masterKeyIcon.setVisibility(View.INVISIBLE);
+ } else {
+ masterKeyIcon.setVisibility(View.VISIBLE);
+ }
+
+ ImageView certifyIcon = (ImageView) view.findViewById(R.id.ic_certifyKey);
+ if (cursor.getInt(mIndexCanCertify) != 1) {
+ certifyIcon.setVisibility(View.GONE);
+ } else {
+ certifyIcon.setVisibility(View.VISIBLE);
+ }
+
+ ImageView encryptIcon = (ImageView) view.findViewById(R.id.ic_encryptKey);
+ if (cursor.getInt(mIndexCanEncrypt) != 1) {
+ encryptIcon.setVisibility(View.GONE);
+ } else {
+ encryptIcon.setVisibility(View.VISIBLE);
+ }
+
+ ImageView signIcon = (ImageView) view.findViewById(R.id.ic_signKey);
+ if (cursor.getInt(mIndexCanSign) != 1) {
+ signIcon.setVisibility(View.GONE);
+ } else {
+ signIcon.setVisibility(View.VISIBLE);
+ }
+ }
+
+ @Override
+ public View newView(Context context, Cursor cursor, ViewGroup parent) {
+ return mInflater.inflate(R.layout.view_key_keys_item, null);
+ }
+
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyUserIdsAdapter.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyUserIdsAdapter.java
new file mode 100644
index 000000000..cf8699417
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyUserIdsAdapter.java
@@ -0,0 +1,76 @@
+/*
+ * 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 org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
+
+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.TextView;
+
+public class ViewKeyUserIdsAdapter extends CursorAdapter {
+ private LayoutInflater mInflater;
+
+ private int mIndexUserId;
+
+ public ViewKeyUserIdsAdapter(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) {
+ mIndexUserId = cursor.getColumnIndexOrThrow(UserIds.USER_ID);
+ }
+ }
+
+ @Override
+ public void bindView(View view, Context context, Cursor cursor) {
+ String userIdStr = cursor.getString(mIndexUserId);
+
+ TextView userId = (TextView) view.findViewById(R.id.userId);
+ userId.setText(userIdStr);
+ }
+
+ @Override
+ public View newView(Context context, Cursor cursor, ViewGroup parent) {
+ return mInflater.inflate(R.layout.view_key_userids_item, null);
+ }
+
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteFileDialogFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteFileDialogFragment.java
new file mode 100644
index 000000000..89b9acf61
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteFileDialogFragment.java
@@ -0,0 +1,121 @@
+/*
+ * 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 org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.service.KeychainIntentService;
+import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
+
+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.FragmentActivity;
+import android.widget.Toast;
+
+import com.actionbarsherlock.app.SherlockDialogFragment;
+
+public class DeleteFileDialogFragment extends SherlockDialogFragment {
+ 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(android.R.drawable.ic_dialog_alert);
+ 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(
+ R.string.progress_deleting_securely, ProgressDialog.STYLE_HORIZONTAL);
+
+ // Message is received after deleting is done in ApgService
+ KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(activity, deletingDialog) {
+ public void handleMessage(Message message) {
+ // handle messages by standard ApgHandler 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();
+ }
+} \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java
new file mode 100644
index 000000000..20c3ec3f3
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java
@@ -0,0 +1,151 @@
+/*
+ * 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 org.spongycastle.openpgp.PGPPublicKeyRing;
+import org.spongycastle.openpgp.PGPSecretKeyRing;
+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.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.util.Log;
+
+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.FragmentActivity;
+
+import com.actionbarsherlock.app.SherlockDialogFragment;
+
+public class DeleteKeyDialogFragment extends SherlockDialogFragment {
+ private static final String ARG_MESSENGER = "messenger";
+ private static final String ARG_DELETE_KEY_RING_ROW_IDS = "delete_file";
+ private static final String ARG_KEY_TYPE = "key_type";
+
+ public static final int MESSAGE_OKAY = 1;
+
+ private Messenger mMessenger;
+
+ /**
+ * Creates new instance of this delete file dialog fragment
+ */
+ public static DeleteKeyDialogFragment newInstance(Messenger messenger, long[] keyRingRowIds,
+ int keyType) {
+ DeleteKeyDialogFragment frag = new DeleteKeyDialogFragment();
+ Bundle args = new Bundle();
+
+ args.putParcelable(ARG_MESSENGER, messenger);
+ args.putLongArray(ARG_DELETE_KEY_RING_ROW_IDS, keyRingRowIds);
+ args.putInt(ARG_KEY_TYPE, keyType);
+
+ frag.setArguments(args);
+
+ return frag;
+ }
+
+ /**
+ * Creates dialog
+ */
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final FragmentActivity activity = getActivity();
+ mMessenger = getArguments().getParcelable(ARG_MESSENGER);
+
+ final long[] keyRingRowIds = getArguments().getLongArray(ARG_DELETE_KEY_RING_ROW_IDS);
+ final int keyType = getArguments().getInt(ARG_KEY_TYPE);
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(activity);
+ builder.setTitle(R.string.warning);
+
+ if (keyRingRowIds.length == 1) {
+ // TODO: better way to do this?
+ String userId = activity.getString(R.string.unknown_user_id);
+
+ if (keyType == Id.type.public_key) {
+ PGPPublicKeyRing keyRing = ProviderHelper.getPGPPublicKeyRingByRowId(activity,
+ keyRingRowIds[0]);
+ userId = PgpKeyHelper.getMainUserIdSafe(activity,
+ PgpKeyHelper.getMasterKey(keyRing));
+ } else {
+ PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRingByRowId(activity,
+ keyRingRowIds[0]);
+ userId = PgpKeyHelper.getMainUserIdSafe(activity,
+ PgpKeyHelper.getMasterKey(keyRing));
+ }
+
+ builder.setMessage(getString(
+ keyType == Id.type.public_key ? R.string.key_deletion_confirmation
+ : R.string.secret_key_deletion_confirmation, userId));
+ } else {
+ builder.setMessage(R.string.key_deletion_confirmation_multi);
+ }
+
+ builder.setIcon(android.R.drawable.ic_dialog_alert);
+ builder.setPositiveButton(R.string.btn_delete, new DialogInterface.OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog, int id) {
+ if (keyType == Id.type.public_key) {
+ for (long keyRowId : keyRingRowIds) {
+ ProviderHelper.deletePublicKeyRing(activity, keyRowId);
+ }
+ } else {
+ for (long keyRowId : keyRingRowIds) {
+ ProviderHelper.deleteSecretKeyRing(activity, keyRowId);
+ }
+ }
+
+ dismiss();
+
+ sendMessageToHandler(MESSAGE_OKAY);
+ }
+ });
+ 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) {
+ 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);
+ }
+ }
+} \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java
new file mode 100644
index 000000000..a68f9bdbe
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java
@@ -0,0 +1,224 @@
+/*
+ * 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 org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.helper.FileHelper;
+import org.sufficientlysecure.keychain.util.Log;
+
+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.view.LayoutInflater;
+import android.view.View;
+import android.widget.CheckBox;
+import android.widget.EditText;
+
+import com.actionbarsherlock.app.SherlockDialogFragment;
+import com.beardedhen.androidbootstrap.BootstrapButton;
+
+public class FileDialogFragment extends SherlockDialogFragment {
+ 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 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);
+ alert.setMessage(message);
+
+ View view = inflater.inflate(R.layout.file_dialog, null);
+
+ 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
+ *
+ * @param messageId
+ * @param progress
+ * @param max
+ */
+ 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/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/LookupUnknownKeyDialogFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/LookupUnknownKeyDialogFragment.java
new file mode 100644
index 000000000..d018f4b8e
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/LookupUnknownKeyDialogFragment.java
@@ -0,0 +1,138 @@
+/*
+ * 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 org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.Id;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
+import org.sufficientlysecure.keychain.ui.KeyServerQueryActivity;
+import org.sufficientlysecure.keychain.util.Log;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnCancelListener;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+
+import com.actionbarsherlock.app.SherlockDialogFragment;
+
+
+public class LookupUnknownKeyDialogFragment extends SherlockDialogFragment {
+ private static final String ARG_MESSENGER = "messenger";
+ private static final String ARG_UNKNOWN_KEY_ID = "unknown_key_id";
+
+ public static final int MESSAGE_OKAY = 1;
+ public static final int MESSAGE_CANCEL = 2;
+
+ private Messenger mMessenger;
+
+ /**
+ * Creates new instance of this dialog fragment
+ *
+ * @param messenger
+ * @param unknownKeyId
+ * @return
+ */
+ public static LookupUnknownKeyDialogFragment newInstance(Messenger messenger, long unknownKeyId) {
+ LookupUnknownKeyDialogFragment frag = new LookupUnknownKeyDialogFragment();
+ Bundle args = new Bundle();
+ args.putLong(ARG_UNKNOWN_KEY_ID, unknownKeyId);
+ args.putParcelable(ARG_MESSENGER, messenger);
+
+ frag.setArguments(args);
+
+ return frag;
+ }
+
+ /**
+ * Creates dialog
+ */
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final Activity activity = getActivity();
+
+ final long unknownKeyId = getArguments().getLong(ARG_UNKNOWN_KEY_ID);
+ mMessenger = getArguments().getParcelable(ARG_MESSENGER);
+
+ AlertDialog.Builder alert = new AlertDialog.Builder(activity);
+
+ alert.setIcon(android.R.drawable.ic_dialog_alert);
+ alert.setTitle(R.string.title_unknown_signature_key);
+ alert.setMessage(getString(R.string.lookup_unknown_key,
+ PgpKeyHelper.convertKeyIdToHex(unknownKeyId)));
+
+ alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog, int id) {
+ dismiss();
+
+ sendMessageToHandler(MESSAGE_OKAY);
+
+ Intent intent = new Intent(activity, KeyServerQueryActivity.class);
+ intent.setAction(KeyServerQueryActivity.ACTION_LOOK_UP_KEY_ID);
+ intent.putExtra(KeyServerQueryActivity.EXTRA_KEY_ID, unknownKeyId);
+ startActivityForResult(intent, Id.request.look_up_key_id);
+ }
+ });
+ alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog, int id) {
+ dismiss();
+
+ sendMessageToHandler(MESSAGE_CANCEL);
+ }
+ });
+ alert.setCancelable(true);
+ alert.setOnCancelListener(new OnCancelListener() {
+
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ sendMessageToHandler(MESSAGE_CANCEL);
+ }
+ });
+
+ return alert.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) {
+ 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);
+ }
+ }
+} \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/PassphraseDialogFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/PassphraseDialogFragment.java
new file mode 100644
index 000000000..898457324
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/PassphraseDialogFragment.java
@@ -0,0 +1,286 @@
+/*
+ * 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 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;
+
+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.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+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 com.actionbarsherlock.app.SherlockDialogFragment;
+
+public class PassphraseDialogFragment extends SherlockDialogFragment 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;
+
+ private Messenger mMessenger;
+ private EditText mPassphraseEditText;
+ private boolean canKB;
+
+ /**
+ * 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 {
+ // TODO: by master key id???
+ secretKey = PgpKeyHelper.getMasterKey(ProviderHelper.getPGPSecretKeyRingByKeyId(activity,
+ secretKeyId));
+ // secretKey = PGPHelper.getMasterKey(PGPMain.getSecretKeyRing(secretKeyId));
+
+ 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);
+ canKB = 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 == true) {
+ 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
+ .getPGPSecretKeyRingByKeyId(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 == false && clickSecretKey.getKeyID() != keyId) {
+ PassphraseCacheService.addCachedPassphrase(activity, clickSecretKey.getKeyID(),
+ passPhrase);
+ }
+
+ sendMessageToHandler(MESSAGE_OKAY);
+ }
+ });
+
+ alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog, int id) {
+ dialog.cancel();
+ }
+ });
+
+ canKB = true;
+ return alert.create();
+ }
+
+ @Override
+ public void onActivityCreated(Bundle arg0) {
+ super.onActivityCreated(arg0);
+ if (canKB) {
+ // 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);
+ }
+ }
+
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ProgressDialogFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ProgressDialogFragment.java
new file mode 100644
index 000000000..aa6cdfd68
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ProgressDialogFragment.java
@@ -0,0 +1,125 @@
+/*
+ * 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.OnKeyListener;
+import android.os.Bundle;
+import android.view.KeyEvent;
+
+import com.actionbarsherlock.app.SherlockDialogFragment;
+
+public class ProgressDialogFragment extends SherlockDialogFragment {
+ private static final String ARG_MESSAGE_ID = "message_id";
+ private static final String ARG_STYLE = "style";
+
+ /**
+ * Creates new instance of this fragment
+ *
+ * @param id
+ * @return
+ */
+ public static ProgressDialogFragment newInstance(int messageId, int style) {
+ ProgressDialogFragment frag = new ProgressDialogFragment();
+ Bundle args = new Bundle();
+ args.putInt(ARG_MESSAGE_ID, messageId);
+ args.putInt(ARG_STYLE, style);
+
+ frag.setArguments(args);
+ 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 messageId
+ * @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 messageId
+ * @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);
+ }
+
+ /**
+ * Creates dialog
+ */
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ Activity activity = getActivity();
+
+ ProgressDialog dialog = new ProgressDialog(activity);
+ dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
+ dialog.setCancelable(false);
+ dialog.setCanceledOnTouchOutside(false);
+
+ int messageId = getArguments().getInt(ARG_MESSAGE_ID);
+ int style = getArguments().getInt(ARG_STYLE);
+
+ dialog.setMessage(getString(messageId));
+ dialog.setProgressStyle(style);
+
+ // 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;
+ }
+} \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SetPassphraseDialogFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SetPassphraseDialogFragment.java
new file mode 100644
index 000000000..aac066d61
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SetPassphraseDialogFragment.java
@@ -0,0 +1,191 @@
+/*
+ * 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 org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.util.Log;
+
+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.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 com.actionbarsherlock.app.SherlockDialogFragment;
+
+public class SetPassphraseDialogFragment extends SherlockDialogFragment 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);
+ }
+ }
+} \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ShareNfcDialogFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ShareNfcDialogFragment.java
new file mode 100644
index 000000000..bae398751
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ShareNfcDialogFragment.java
@@ -0,0 +1,102 @@
+/*
+ * 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 org.sufficientlysecure.htmltextview.HtmlTextView;
+import org.sufficientlysecure.keychain.R;
+
+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.FragmentActivity;
+
+import com.actionbarsherlock.app.SherlockDialogFragment;
+
+@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+public class ShareNfcDialogFragment extends SherlockDialogFragment {
+
+ /**
+ * 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.setIcon(android.R.drawable.ic_dialog_info);
+ 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();
+ }
+} \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ShareQrCodeDialogFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ShareQrCodeDialogFragment.java
new file mode 100644
index 000000000..771816bfc
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ShareQrCodeDialogFragment.java
@@ -0,0 +1,209 @@
+/*
+ * 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 java.util.ArrayList;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.util.Log;
+import org.sufficientlysecure.keychain.util.QrCodeUtils;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.actionbarsherlock.app.SherlockDialogFragment;
+
+public class ShareQrCodeDialogFragment extends SherlockDialogFragment {
+ private static final String ARG_URI = "uri";
+ private static final String ARG_FINGERPRINT_ONLY = "fingerprint_only";
+
+ private ImageView mImage;
+ private TextView mText;
+
+ private ArrayList<String> mContentList;
+ private int mCounter;
+
+ private static final int QR_CODE_SIZE = 1000;
+
+ /**
+ * Creates new instance of this dialog fragment
+ *
+ * @param content
+ * Content to be shared via QR Codes
+ * @return
+ */
+ public static ShareQrCodeDialogFragment newInstance(Uri dataUri, boolean fingerprintOnly) {
+ ShareQrCodeDialogFragment frag = new ShareQrCodeDialogFragment();
+ Bundle args = new Bundle();
+ args.putParcelable(ARG_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_URI);
+ boolean fingerprintOnly = getArguments().getBoolean(ARG_FINGERPRINT_ONLY);
+
+ AlertDialog.Builder alert = new AlertDialog.Builder(activity);
+
+ 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);
+
+ // TODO
+ long masterKeyId = ProviderHelper.getMasterKeyId(getActivity(), dataUri);
+
+ String content = null;
+ if (fingerprintOnly) {
+ content = "openpgp4fpr:";
+
+ String fingerprint = PgpKeyHelper.convertKeyToHex(masterKeyId);
+
+ mText.setText(getString(R.string.share_qr_code_dialog_fingerprint_text) + " "
+ + fingerprint);
+
+ content = content + fingerprint;
+
+ Log.d(Constants.TAG, "content: " + content);
+
+ alert.setPositiveButton(R.string.btn_okay, null);
+ } else {
+ mText.setText(R.string.share_qr_code_dialog_start);
+
+ // get public keyring as ascii armored string
+ ArrayList<String> keyringArmored = ProviderHelper.getKeyRingsAsArmoredString(
+ getActivity(), dataUri, new long[] { masterKeyId });
+
+ // TODO: binary?
+
+ content = keyringArmored.get(0);
+
+ // OnClickListener are set in onResume to prevent automatic dismissing of Dialogs
+ // http://stackoverflow.com/questions/2620444/how-to-prevent-a-dialog-from-closing-when-a-button-is-clicked
+ alert.setPositiveButton(R.string.btn_next, null);
+ alert.setNegativeButton(android.R.string.cancel, null);
+ }
+
+ mContentList = splitString(content, 1000);
+
+ // start with first
+ mCounter = 0;
+ updateQrCode();
+
+ return alert.create();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ 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--;
+ updateQrCode();
+ updateDialog(backButton, nextButton);
+ } else {
+ dismiss();
+ }
+ }
+ });
+ nextButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+
+ if (mCounter < mContentList.size() - 1) {
+ mCounter++;
+ updateQrCode();
+ updateDialog(backButton, nextButton);
+ } else {
+ dismiss();
+ }
+ }
+ });
+ }
+
+ private void updateQrCode() {
+ // Content: <counter>,<size>,<content>
+ mImage.setImageBitmap(QrCodeUtils.getQRCodeBitmap(mCounter + "," + mContentList.size()
+ + "," + mContentList.get(mCounter), 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/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/Editor.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/Editor.java
new file mode 100644
index 000000000..1cf510d3a
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/Editor.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.ui.widget;
+
+public interface Editor {
+ public interface EditorListener {
+ public void onDeleted(Editor editor);
+ }
+
+ public void setEditorListener(EditorListener listener);
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/FixedListView.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/FixedListView.java
new file mode 100644
index 000000000..b9dcd0d25
--- /dev/null
+++ b/OpenPGP-Keychain/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.
+ *
+ * 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);
+ }
+
+} \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/IntegerListPreference.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/IntegerListPreference.java
new file mode 100644
index 000000000..bc60b2adf
--- /dev/null
+++ b/OpenPGP-Keychain/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/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyEditor.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyEditor.java
new file mode 100644
index 000000000..0f5d26644
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyEditor.java
@@ -0,0 +1,256 @@
+/*
+ * 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 java.text.DateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Vector;
+
+import org.spongycastle.openpgp.PGPPublicKey;
+import org.spongycastle.openpgp.PGPSecretKey;
+import org.sufficientlysecure.keychain.Id;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
+import org.sufficientlysecure.keychain.util.Choice;
+
+import android.app.DatePickerDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.DatePicker;
+import android.widget.LinearLayout;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+import com.beardedhen.androidbootstrap.BootstrapButton;
+
+public class KeyEditor extends LinearLayout implements Editor, OnClickListener {
+ private PGPSecretKey mKey;
+
+ private EditorListener mEditorListener = null;
+
+ private boolean mIsMasterKey;
+ BootstrapButton mDeleteButton;
+ TextView mAlgorithm;
+ TextView mKeyId;
+ Spinner mUsage;
+ TextView mCreationDate;
+ BootstrapButton mExpiryDateButton;
+ GregorianCalendar mExpiryDate;
+
+ private int mDatePickerResultCount = 0;
+ private DatePickerDialog.OnDateSetListener mExpiryDateSetListener = new DatePickerDialog.OnDateSetListener() {
+ public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
+ if (mDatePickerResultCount++ == 0) // Note: Ignore results after the first one - android
+ // sends multiples.
+ {
+ GregorianCalendar date = new GregorianCalendar(year, monthOfYear, dayOfMonth);
+ setExpiryDate(date);
+ }
+ }
+ };
+
+ 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);
+ mUsage = (Spinner) findViewById(R.id.usage);
+ Choice choices[] = {
+ new Choice(Id.choice.usage.sign_only, getResources().getString(
+ R.string.choice_sign_only)),
+ new Choice(Id.choice.usage.encrypt_only, getResources().getString(
+ R.string.choice_encrypt_only)),
+ new Choice(Id.choice.usage.sign_and_encrypt, getResources().getString(
+ R.string.choice_sign_and_encrypt)), };
+ ArrayAdapter<Choice> adapter = new ArrayAdapter<Choice>(getContext(),
+ android.R.layout.simple_spinner_item, choices);
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ mUsage.setAdapter(adapter);
+
+ mDeleteButton = (BootstrapButton) findViewById(R.id.delete);
+ mDeleteButton.setOnClickListener(this);
+
+ setExpiryDate(null);
+
+ mExpiryDateButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ GregorianCalendar date = mExpiryDate;
+ if (date == null) {
+ date = new GregorianCalendar();
+ }
+
+ DatePickerDialog dialog = new DatePickerDialog(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) {
+ if (mDatePickerResultCount++ == 0) // Note: Ignore results after the
+ // first
+ // one - android sends multiples.
+ {
+ setExpiryDate(null);
+ }
+ }
+ });
+ dialog.show();
+ }
+ });
+
+ super.onFinishInflate();
+ }
+
+ public void setCanEdit(boolean bCanEdit) {
+ if (!bCanEdit) {
+ mDeleteButton.setVisibility(View.INVISIBLE);
+ mUsage.setEnabled(false);
+ mExpiryDateButton.setEnabled(false);
+ }
+ }
+
+ public void setValue(PGPSecretKey key, boolean isMasterKey, int usage) {
+ mKey = key;
+
+ mIsMasterKey = isMasterKey;
+ if (mIsMasterKey) {
+ mDeleteButton.setVisibility(View.INVISIBLE);
+ }
+
+ mAlgorithm.setText(PgpKeyHelper.getAlgorithmInfo(key));
+ String keyId1Str = PgpKeyHelper.convertKeyIdToHex(key.getKeyID());
+ String keyId2Str = PgpKeyHelper.convertKeyIdToHex(key.getKeyID() >> 32);
+ mKeyId.setText(keyId1Str + " " + keyId2Str);
+
+ Vector<Choice> choices = new Vector<Choice>();
+ boolean isElGamalKey = (key.getPublicKey().getAlgorithm() == PGPPublicKey.ELGAMAL_ENCRYPT);
+ boolean isDSAKey = (key.getPublicKey().getAlgorithm() == PGPPublicKey.DSA);
+ if (!isElGamalKey) {
+ choices.add(new Choice(Id.choice.usage.sign_only, getResources().getString(
+ R.string.choice_sign_only)));
+ }
+ if (!mIsMasterKey && !isDSAKey) {
+ choices.add(new Choice(Id.choice.usage.encrypt_only, getResources().getString(
+ R.string.choice_encrypt_only)));
+ }
+ if (!isElGamalKey && !isDSAKey) {
+ choices.add(new Choice(Id.choice.usage.sign_and_encrypt, getResources().getString(
+ R.string.choice_sign_and_encrypt)));
+ }
+
+ ArrayAdapter<Choice> adapter = new ArrayAdapter<Choice>(getContext(),
+ android.R.layout.simple_spinner_item, choices);
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ mUsage.setAdapter(adapter);
+
+ // Set value in choice dropdown to key
+ int selectId = 0;
+ if (PgpKeyHelper.isEncryptionKey(key)) {
+ if (PgpKeyHelper.isSigningKey(key)) {
+ selectId = Id.choice.usage.sign_and_encrypt;
+ } else {
+ selectId = Id.choice.usage.encrypt_only;
+ }
+ } else {
+ // set usage if it is predefined
+ if (usage != -1) {
+ selectId = usage;
+ } else {
+ selectId = Id.choice.usage.sign_only;
+ }
+
+ }
+
+ for (int i = 0; i < choices.size(); ++i) {
+ if (choices.get(i).getId() == selectId) {
+ mUsage.setSelection(i);
+ break;
+ }
+ }
+
+ GregorianCalendar cal = new GregorianCalendar();
+ cal.setTime(PgpKeyHelper.getCreationDate(key));
+ mCreationDate.setText(DateFormat.getDateInstance().format(cal.getTime()));
+ cal = new GregorianCalendar();
+ Date date = PgpKeyHelper.getExpiryDate(key);
+ if (date == null) {
+ setExpiryDate(null);
+ } else {
+ cal.setTime(PgpKeyHelper.getExpiryDate(key));
+ setExpiryDate(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);
+ }
+ }
+ }
+
+ public void setEditorListener(EditorListener listener) {
+ mEditorListener = listener;
+ }
+
+ 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() {
+ return ((Choice) mUsage.getSelectedItem()).getId();
+ }
+
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyServerEditor.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyServerEditor.java
new file mode 100644
index 000000000..01259ccd1
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyServerEditor.java
@@ -0,0 +1,79 @@
+/*
+ * 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 org.sufficientlysecure.keychain.R;
+
+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;
+
+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);
+ }
+ }
+ }
+
+ public void setEditorListener(EditorListener listener) {
+ mEditorListener = listener;
+ }
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SectionView.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SectionView.java
new file mode 100644
index 000000000..1f9605fb1
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SectionView.java
@@ -0,0 +1,364 @@
+/*
+ * 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 java.util.Iterator;
+import java.util.Vector;
+
+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.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.ProgressDialogFragment;
+import org.sufficientlysecure.keychain.ui.widget.Editor.EditorListener;
+import org.sufficientlysecure.keychain.util.Choice;
+
+import android.app.AlertDialog;
+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.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.LinearLayout;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+import com.actionbarsherlock.app.SherlockFragmentActivity;
+import com.beardedhen.androidbootstrap.BootstrapButton;
+
+public class SectionView extends LinearLayout implements OnClickListener, EditorListener {
+ private LayoutInflater mInflater;
+ private BootstrapButton mPlusButton;
+ private ViewGroup mEditors;
+ private TextView mTitle;
+ private int mType = 0;
+
+ private Choice mNewKeyAlgorithmChoice;
+ private int mNewKeySize;
+ private boolean canEdit = true;
+
+ private SherlockFragmentActivity mActivity;
+
+ private ProgressDialogFragment mGeneratingDialog;
+
+ public SectionView(Context context) {
+ super(context);
+ mActivity = (SherlockFragmentActivity) context;
+ }
+
+ public SectionView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mActivity = (SherlockFragmentActivity) 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 setCanEdit(boolean bCanEdit) {
+ canEdit = bCanEdit;
+ if (!canEdit) {
+ 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) {
+ this.updateEditorsVisible();
+ }
+
+ protected void updateEditorsVisible() {
+ final boolean hasChildren = mEditors.getChildCount() > 0;
+ mEditors.setVisibility(hasChildren ? View.VISIBLE : View.GONE);
+ }
+
+ /** {@inheritDoc} */
+ public void onClick(View v) {
+ if (canEdit) {
+ switch (mType) {
+ case Id.type.user_id: {
+ UserIdEditor view = (UserIdEditor) mInflater.inflate(
+ R.layout.edit_key_user_id_item, mEditors, false);
+ view.setEditorListener(this);
+ if (mEditors.getChildCount() == 0) {
+ view.setIsMainUserId(true);
+ }
+ mEditors.addView(view);
+ break;
+ }
+
+ case Id.type.key: {
+ AlertDialog.Builder dialog = new AlertDialog.Builder(getContext());
+
+ View view = mInflater.inflate(R.layout.create_key_dialog, null);
+ dialog.setView(view);
+ dialog.setTitle(R.string.title_create_key);
+
+ boolean wouldBeMasterKey = (mEditors.getChildCount() == 0);
+
+ final Spinner algorithm = (Spinner) view.findViewById(R.id.create_key_algorithm);
+ Vector<Choice> choices = new Vector<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>(getContext(),
+ 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(
+ getContext(), 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 {
+ int nKeyIndex = keySize.getSelectedItemPosition();
+ switch (nKeyIndex) {
+ case 0:
+ mNewKeySize = 512;
+ break;
+ case 1:
+ mNewKeySize = 1024;
+ break;
+ case 2:
+ mNewKeySize = 2048;
+ break;
+ case 3:
+ mNewKeySize = 4096;
+ break;
+ }
+ } catch (NumberFormatException e) {
+ mNewKeySize = 0;
+ }
+
+ mNewKeyAlgorithmChoice = (Choice) algorithm.getSelectedItem();
+ createKey();
+ }
+ });
+
+ dialog.setCancelable(true);
+ dialog.setNegativeButton(android.R.string.cancel,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface di, int id) {
+ di.dismiss();
+ }
+ });
+
+ dialog.create().show();
+ 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);
+ if (mEditors.getChildCount() == 0) {
+ view.setIsMainUserId(true);
+ }
+ view.setCanEdit(canEdit);
+ mEditors.addView(view);
+ }
+
+ this.updateEditorsVisible();
+ }
+
+ public void setKeys(Vector<PGPSecretKey> list, Vector<Integer> usages) {
+ 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));
+ view.setCanEdit(canEdit);
+ mEditors.addView(view);
+ }
+
+ this.updateEditorsVisible();
+ }
+
+ private void createKey() {
+ // Send all information needed to service to edit key in other thread
+ Intent intent = new Intent(mActivity, KeychainIntentService.class);
+
+ intent.setAction(KeychainIntentService.ACTION_GENERATE_KEY);
+
+ // fill values for this action
+ Bundle data = new Bundle();
+
+ String passPhrase;
+ if (mEditors.getChildCount() > 0) {
+ PGPSecretKey masterKey = ((KeyEditor) mEditors.getChildAt(0)).getValue();
+ passPhrase = PassphraseCacheService
+ .getCachedPassphrase(mActivity, masterKey.getKeyID());
+
+ data.putByteArray(KeychainIntentService.GENERATE_KEY_MASTER_KEY,
+ PgpConversionHelper.PGPSecretKeyToBytes(masterKey));
+ } else {
+ passPhrase = "";
+ }
+ 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(R.string.progress_generating,
+ ProgressDialog.STYLE_SPINNER);
+
+ // Message is received after generating is done in ApgService
+ KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(mActivity,
+ mGeneratingDialog) {
+ public void handleMessage(Message message) {
+ // handle messages by standard ApgHandler first
+ super.handleMessage(message);
+
+ if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
+ // get new key from data bundle returned from service
+ Bundle data = message.getData();
+ PGPSecretKeyRing newKeyRing = (PGPSecretKeyRing) PgpConversionHelper
+ .BytesToPGPKeyRing(data
+ .getByteArray(KeychainIntentService.RESULT_NEW_KEY));
+
+ boolean isMasterKey = (mEditors.getChildCount() == 0);
+
+ // take only the key from this ring
+ PGPSecretKey newKey = null;
+ @SuppressWarnings("unchecked")
+ Iterator<PGPSecretKey> it = newKeyRing.getSecretKeys();
+
+ if (isMasterKey) {
+ newKey = it.next();
+ } else {
+ // first one is the master key
+ it.next();
+ newKey = it.next();
+ }
+
+ // add view with new key
+ KeyEditor view = (KeyEditor) mInflater.inflate(R.layout.edit_key_key_item,
+ mEditors, false);
+ view.setEditorListener(SectionView.this);
+ view.setValue(newKey, isMasterKey, -1);
+ mEditors.addView(view);
+ SectionView.this.updateEditorsVisible();
+ }
+ };
+ };
+
+ // 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);
+ }
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/UnderlineTextView.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/UnderlineTextView.java
new file mode 100644
index 000000000..752d44f89
--- /dev/null
+++ b/OpenPGP-Keychain/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/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/UserIdEditor.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/UserIdEditor.java
new file mode 100644
index 000000000..5428b626e
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/UserIdEditor.java
@@ -0,0 +1,207 @@
+/*
+ * 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 java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.sufficientlysecure.keychain.R;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.RadioButton;
+
+import com.beardedhen.androidbootstrap.BootstrapButton;
+
+public class UserIdEditor extends LinearLayout implements Editor, OnClickListener {
+ private EditorListener mEditorListener = null;
+
+ private BootstrapButton mDeleteButton;
+ private RadioButton mIsMainUserId;
+ private EditText mName;
+ private EditText mEmail;
+ private EditText mComment;
+
+ // see http://www.regular-expressions.info/email.html
+ // RFC 2822 if we omit the syntax using double quotes and square brackets
+ // android.util.Patterns.EMAIL_ADDRESS is only available as of Android 2.2+
+ private static final Pattern EMAIL_PATTERN = Pattern
+ .compile(
+ "[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?",
+ Pattern.CASE_INSENSITIVE);
+
+ public static class NoNameException extends Exception {
+ static final long serialVersionUID = 0xf812773343L;
+
+ public NoNameException(String message) {
+ super(message);
+ }
+ }
+
+ public void setCanEdit(boolean bCanEdit) {
+ if (!bCanEdit) {
+ mDeleteButton.setVisibility(View.INVISIBLE);
+ mName.setEnabled(false);
+ mIsMainUserId.setEnabled(false);
+ mEmail.setEnabled(false);
+ mComment.setEnabled(false);
+ }
+ }
+
+ public static class NoEmailException extends Exception {
+ static final long serialVersionUID = 0xf812773344L;
+
+ public NoEmailException(String message) {
+ super(message);
+ }
+ }
+
+ 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);
+ }
+
+ @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);
+ mEmail = (EditText) findViewById(R.id.email);
+ mComment = (EditText) findViewById(R.id.comment);
+
+ super.onFinishInflate();
+ }
+
+ public void setValue(String userId) {
+ mName.setText("");
+ mComment.setText("");
+ mEmail.setText("");
+
+ Pattern withComment = Pattern.compile("^(.*) [(](.*)[)] <(.*)>$");
+ Matcher matcher = withComment.matcher(userId);
+ if (matcher.matches()) {
+ mName.setText(matcher.group(1));
+ mComment.setText(matcher.group(2));
+ mEmail.setText(matcher.group(3));
+ return;
+ }
+
+ Pattern withoutComment = Pattern.compile("^(.*) <(.*)>$");
+ matcher = withoutComment.matcher(userId);
+ if (matcher.matches()) {
+ mName.setText(matcher.group(1));
+ mEmail.setText(matcher.group(2));
+ return;
+ }
+ }
+
+ public String getValue() throws NoNameException, NoEmailException, InvalidEmailException {
+ String name = ("" + mName.getText()).trim();
+ String email = ("" + mEmail.getText()).trim();
+ String comment = ("" + mComment.getText()).trim();
+
+ if (email.length() > 0) {
+ Matcher emailMatcher = EMAIL_PATTERN.matcher(email);
+ if (!emailMatcher.matches()) {
+ throw new InvalidEmailException(getContext().getString(R.string.error_invalid_email,
+ email));
+ }
+ }
+
+ String userId = name;
+ if (comment.length() > 0) {
+ userId += " (" + comment + ")";
+ }
+ if (email.length() > 0) {
+ userId += " <" + email + ">";
+ }
+
+ if (userId.equals("")) {
+ // ok, empty one...
+ return userId;
+ }
+
+ // otherwise make sure that name and email exist
+ if (name.equals("")) {
+ throw new NoNameException("need a name");
+ }
+
+ if (email.equals("")) {
+ throw new NoEmailException("need an email");
+ }
+
+ 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);
+ }
+ 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);
+ }
+ }
+ }
+ }
+
+ public void setIsMainUserId(boolean value) {
+ mIsMainUserId.setChecked(value);
+ }
+
+ public boolean isMainUserId() {
+ return mIsMainUserId.isChecked();
+ }
+
+ public void setEditorListener(EditorListener listener) {
+ mEditorListener = listener;
+ }
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/AlgorithmNames.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/AlgorithmNames.java
new file mode 100644
index 000000000..d3c37edc6
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/AlgorithmNames.java
@@ -0,0 +1,92 @@
+/*
+ * 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.HashMap;
+import org.spongycastle.bcpg.HashAlgorithmTags;
+import org.spongycastle.openpgp.PGPEncryptedData;
+import org.sufficientlysecure.keychain.Id;
+import org.sufficientlysecure.keychain.R;
+import android.annotation.SuppressLint;
+import android.app.Activity;
+
+@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/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/Choice.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/Choice.java
new file mode 100644
index 000000000..9e0042c00
--- /dev/null
+++ b/OpenPGP-Keychain/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/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/HkpKeyServer.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/HkpKeyServer.java
new file mode 100644
index 000000000..8b5e94858
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/HkpKeyServer.java
@@ -0,0 +1,257 @@
+/*
+ * 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 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.URLEncoder;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.GregorianCalendar;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+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.pgp.PgpHelper;
+import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
+
+import android.text.Html;
+
+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 = 11371;
+
+ // example:
+ // pub 2048R/<a href="/pks/lookup?op=get&search=0x887DF4BE9F5C9090">9F5C9090</a> 2009-08-17 <a
+ // href="/pks/lookup?op=vindex&search=0x887DF4BE9F5C9090">Jörg Runge
+ // &lt;joerg@joergrunge.de&gt;</a>
+ public static Pattern PUB_KEY_LINE = Pattern
+ .compile(
+ "pub +([0-9]+)([a-z]+)/.*?0x([0-9a-z]+).*? +([0-9-]+) +(.+)[\n\r]+((?: +.+[\n\r]+)*)",
+ Pattern.CASE_INSENSITIVE);
+ public static Pattern USER_ID_LINE = Pattern.compile("^ +(.+)$", Pattern.MULTILINE
+ | Pattern.CASE_INSENSITIVE);
+
+ public HkpKeyServer(String host) {
+ mHost = host;
+ }
+
+ public HkpKeyServer(String host, short port) {
+ mHost = host;
+ mPort = port;
+ }
+
+ static private 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;
+ 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<KeyInfo> search(String query) throws QueryException, TooManyResponses,
+ InsufficientQuery {
+ ArrayList<KeyInfo> results = new ArrayList<KeyInfo>();
+
+ 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&search=" + encodedQuery;
+
+ String data = null;
+ try {
+ data = query(request);
+ } catch (HttpError e) {
+ if (e.getCode() == 404) {
+ return results;
+ } else {
+ if (e.getData().toLowerCase().contains("no keys found")) {
+ return results;
+ } else if (e.getData().toLowerCase().contains("too many")) {
+ throw new TooManyResponses();
+ } else if (e.getData().toLowerCase().contains("insufficient")) {
+ throw new InsufficientQuery();
+ }
+ }
+ throw new QueryException("querying server(s) for '" + mHost + "' failed");
+ }
+
+ Matcher matcher = PUB_KEY_LINE.matcher(data);
+ while (matcher.find()) {
+ KeyInfo info = new KeyInfo();
+ info.size = Integer.parseInt(matcher.group(1));
+ info.algorithm = matcher.group(2);
+ info.keyId = PgpKeyHelper.convertHexToKeyId(matcher.group(3));
+ info.fingerPrint = PgpKeyHelper.convertKeyIdToHex(info.keyId);
+ String chunks[] = matcher.group(4).split("-");
+ info.date = new GregorianCalendar(Integer.parseInt(chunks[0]),
+ Integer.parseInt(chunks[1]), Integer.parseInt(chunks[2])).getTime();
+ info.userIds = new ArrayList<String>();
+ if (matcher.group(5).startsWith("*** KEY")) {
+ info.revoked = matcher.group(5);
+ } else {
+ String tmp = matcher.group(5).replaceAll("<.*?>", "");
+ tmp = Html.fromHtml(tmp).toString();
+ info.userIds.add(tmp);
+ }
+ if (matcher.group(6).length() > 0) {
+ Matcher matcher2 = USER_ID_LINE.matcher(matcher.group(6));
+ while (matcher2.find()) {
+ String tmp = matcher2.group(1).replaceAll("<.*?>", "");
+ tmp = Html.fromHtml(tmp).toString();
+ info.userIds.add(tmp);
+ }
+ }
+ results.add(info);
+ }
+
+ return results;
+ }
+
+ @Override
+ public String get(long keyId) throws QueryException {
+ HttpClient client = new DefaultHttpClient();
+ try {
+ HttpGet get = new HttpGet("http://" + mHost + ":" + mPort
+ + "/pks/lookup?op=get&search=0x" + PgpKeyHelper.convertKeyToHex(keyId));
+
+ 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 armoredText) throws AddKeyException {
+ HttpClient client = new DefaultHttpClient();
+ try {
+ HttpPost post = new HttpPost("http://" + mHost + ":" + mPort + "/pks/add");
+
+ List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(2);
+ nameValuePairs.add(new BasicNameValuePair("keytext", armoredText));
+ 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/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/InputData.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/InputData.java
new file mode 100644
index 000000000..092c14e00
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/InputData.java
@@ -0,0 +1,39 @@
+/*
+ * 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/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/IntentIntegratorSupportV4.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/IntentIntegratorSupportV4.java
new file mode 100644
index 000000000..a43c03e3e
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/IntentIntegratorSupportV4.java
@@ -0,0 +1,47 @@
+/*
+ * 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 android.content.Intent;
+import android.support.v4.app.Fragment;
+
+import com.google.zxing.integration.android.IntentIntegrator;
+
+/**
+ * IntentIntegrator for the V4 Android compatibility package.
+ *
+ * @author Lachezar Dobrev
+ */
+public final class IntentIntegratorSupportV4 extends IntentIntegrator {
+
+ private final Fragment fragment;
+
+ /**
+ * @param fragment
+ * Fragment to handle activity response.
+ */
+ public IntentIntegratorSupportV4(Fragment fragment) {
+ super(fragment.getActivity());
+ this.fragment = fragment;
+ }
+
+ @Override
+ protected void startActivityForResult(Intent intent, int code) {
+ fragment.startActivityForResult(intent, code);
+ }
+
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/IterableIterator.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/IterableIterator.java
new file mode 100644
index 000000000..caaa07524
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/IterableIterator.java
@@ -0,0 +1,31 @@
+/*
+ * 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.Iterator;
+
+public class IterableIterator<T> implements Iterable<T> {
+ private Iterator<T> mIter;
+
+ public IterableIterator(Iterator<T> iter) {
+ mIter = iter;
+ }
+
+ public Iterator<T> iterator() {
+ return mIter;
+ }
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/KeyServer.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/KeyServer.java
new file mode 100644
index 000000000..cdc188887
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/KeyServer.java
@@ -0,0 +1,97 @@
+/*
+ * 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 java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public abstract class KeyServer {
+ static public class QueryException extends Exception {
+ private static final long serialVersionUID = 2703768928624654512L;
+
+ public QueryException(String message) {
+ super(message);
+ }
+ }
+
+ static public class TooManyResponses extends Exception {
+ private static final long serialVersionUID = 2703768928624654513L;
+ }
+
+ static public class InsufficientQuery extends Exception {
+ private static final long serialVersionUID = 2703768928624654514L;
+ }
+
+ static public class AddKeyException extends Exception {
+ private static final long serialVersionUID = -507574859137295530L;
+ }
+
+ static public class KeyInfo implements Serializable, Parcelable {
+ private static final long serialVersionUID = -7797972113284992662L;
+ public ArrayList<String> userIds;
+ public String revoked;
+ public Date date;
+ public String fingerPrint;
+ public long keyId;
+ public int size;
+ public String algorithm;
+
+ public KeyInfo() {
+ userIds = new ArrayList<String>();
+ }
+
+ public KeyInfo(Parcel in) {
+ this();
+
+ in.readStringList(this.userIds);
+ this.revoked = in.readString();
+ this.date = (Date) in.readSerializable();
+ this.fingerPrint = in.readString();
+ this.keyId = in.readLong();
+ this.size = in.readInt();
+ this.algorithm = in.readString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeStringList(userIds);
+ dest.writeString(revoked);
+ dest.writeSerializable(date);
+ dest.writeString(fingerPrint);
+ dest.writeLong(keyId);
+ dest.writeInt(size);
+ dest.writeString(algorithm);
+ }
+ }
+
+ abstract List<KeyInfo> search(String query) throws QueryException, TooManyResponses,
+ InsufficientQuery;
+
+ abstract String get(long keyId) throws QueryException;
+
+ abstract void add(String armouredText) throws AddKeyException;
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/Log.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/Log.java
new file mode 100644
index 000000000..bcf275c32
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/Log.java
@@ -0,0 +1,84 @@
+/*
+ * 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/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/PRNGFixes.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/PRNGFixes.java
new file mode 100644
index 000000000..530a81044
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/PRNGFixes.java
@@ -0,0 +1,361 @@
+/*
+ * 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.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.security.NoSuchAlgorithmException;
+import java.security.Provider;
+import java.security.SecureRandom;
+import java.security.SecureRandomSpi;
+import java.security.Security;
+
+/**
+ * Fixes for the output of the default PRNG having low entropy.
+ *
+ * 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}.
+ *
+ * copied from http://android-developers.blogspot.de/2013/08/some-securerandom-thoughts.html
+ *
+ *
+ * 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)"
+ *
+ *
+ * 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
+ *
+ * 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.
+ *
+ *
+ * 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");
+ }
+ }
+} \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/PausableThreadPoolExecutor.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/PausableThreadPoolExecutor.java
new file mode 100644
index 000000000..aa21581c6
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/PausableThreadPoolExecutor.java
@@ -0,0 +1,90 @@
+/*
+ * 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 isPaused;
+ private ReentrantLock pauseLock = new ReentrantLock();
+ private Condition unpaused = pauseLock.newCondition();
+
+ protected void beforeExecute(Thread t, Runnable r) {
+ super.beforeExecute(t, r);
+ pauseLock.lock();
+ try {
+ while (isPaused)
+ unpaused.await();
+ } catch (InterruptedException ie) {
+ t.interrupt();
+ } finally {
+ pauseLock.unlock();
+ }
+ }
+
+ public void pause() {
+ pauseLock.lock();
+ try {
+ isPaused = true;
+ } finally {
+ pauseLock.unlock();
+ }
+ }
+
+ public void resume() {
+ pauseLock.lock();
+ try {
+ isPaused = false;
+ unpaused.signalAll();
+ } finally {
+ pauseLock.unlock();
+ }
+ }
+} \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/PositionAwareInputStream.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/PositionAwareInputStream.java
new file mode 100644
index 000000000..a783d7820
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/PositionAwareInputStream.java
@@ -0,0 +1,81 @@
+/*
+ * 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/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/Primes.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/Primes.java
new file mode 100644
index 000000000..f503227a3
--- /dev/null
+++ b/OpenPGP-Keychain/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/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/ProgressDialogUpdater.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/ProgressDialogUpdater.java
new file mode 100644
index 000000000..26c05ad0a
--- /dev/null
+++ b/OpenPGP-Keychain/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/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/QrCodeUtils.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/QrCodeUtils.java
new file mode 100644
index 000000000..9e8118e7a
--- /dev/null
+++ b/OpenPGP-Keychain/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 java.util.Hashtable;
+
+import org.sufficientlysecure.keychain.Constants;
+
+import android.graphics.Bitmap;
+import android.graphics.Color;
+
+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;
+
+public class QrCodeUtils {
+ public final static 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/OpenPGP-Keychain/src/main/res/anim/push_left_in.xml b/OpenPGP-Keychain/src/main/res/anim/push_left_in.xml
new file mode 100644
index 000000000..45fb4875a
--- /dev/null
+++ b/OpenPGP-Keychain/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/OpenPGP-Keychain/src/main/res/anim/push_left_out.xml b/OpenPGP-Keychain/src/main/res/anim/push_left_out.xml
new file mode 100644
index 000000000..845679f16
--- /dev/null
+++ b/OpenPGP-Keychain/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/OpenPGP-Keychain/src/main/res/anim/push_right_in.xml b/OpenPGP-Keychain/src/main/res/anim/push_right_in.xml
new file mode 100644
index 000000000..09a244406
--- /dev/null
+++ b/OpenPGP-Keychain/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/OpenPGP-Keychain/src/main/res/anim/push_right_out.xml b/OpenPGP-Keychain/src/main/res/anim/push_right_out.xml
new file mode 100644
index 000000000..e8893a69a
--- /dev/null
+++ b/OpenPGP-Keychain/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/OpenPGP-Keychain/src/main/res/drawable-hdpi/certify_small.png b/OpenPGP-Keychain/src/main/res/drawable-hdpi/certify_small.png
new file mode 100644
index 000000000..9e54464ed
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-hdpi/certify_small.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-hdpi/drawer_shadow.9.png b/OpenPGP-Keychain/src/main/res/drawable-hdpi/drawer_shadow.9.png
new file mode 100644
index 000000000..236bff558
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-hdpi/drawer_shadow.9.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-hdpi/encrypted_small.png b/OpenPGP-Keychain/src/main/res/drawable-hdpi/encrypted_small.png
new file mode 100644
index 000000000..3ff8e9b97
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-hdpi/encrypted_small.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-hdpi/ic_action_add_person.png b/OpenPGP-Keychain/src/main/res/drawable-hdpi/ic_action_add_person.png
new file mode 100644
index 000000000..5ebac9706
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-hdpi/ic_action_add_person.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-hdpi/ic_action_cancel.png b/OpenPGP-Keychain/src/main/res/drawable-hdpi/ic_action_cancel.png
new file mode 100644
index 000000000..cde36e1fa
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-hdpi/ic_action_cancel.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-hdpi/ic_action_discard.png b/OpenPGP-Keychain/src/main/res/drawable-hdpi/ic_action_discard.png
new file mode 100644
index 000000000..9c717dd32
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-hdpi/ic_action_discard.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-hdpi/ic_action_done.png b/OpenPGP-Keychain/src/main/res/drawable-hdpi/ic_action_done.png
new file mode 100644
index 000000000..58bf97217
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-hdpi/ic_action_done.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-hdpi/ic_action_import_export.png b/OpenPGP-Keychain/src/main/res/drawable-hdpi/ic_action_import_export.png
new file mode 100644
index 000000000..742ba271c
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-hdpi/ic_action_import_export.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-hdpi/ic_action_share.png b/OpenPGP-Keychain/src/main/res/drawable-hdpi/ic_action_share.png
new file mode 100644
index 000000000..8a6cbfea2
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-hdpi/ic_action_share.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-hdpi/ic_drawer.png b/OpenPGP-Keychain/src/main/res/drawable-hdpi/ic_drawer.png
new file mode 100644
index 000000000..c59f601ca
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-hdpi/ic_drawer.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-hdpi/ic_menu_search.png b/OpenPGP-Keychain/src/main/res/drawable-hdpi/ic_menu_search.png
new file mode 100644
index 000000000..1cb61faf4
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-hdpi/ic_menu_search.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-hdpi/ic_menu_search_list.png b/OpenPGP-Keychain/src/main/res/drawable-hdpi/ic_menu_search_list.png
new file mode 100644
index 000000000..efee6dfd2
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-hdpi/ic_menu_search_list.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-hdpi/ic_next.png b/OpenPGP-Keychain/src/main/res/drawable-hdpi/ic_next.png
new file mode 100644
index 000000000..d71058055
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-hdpi/ic_next.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-hdpi/ic_previous.png b/OpenPGP-Keychain/src/main/res/drawable-hdpi/ic_previous.png
new file mode 100644
index 000000000..d610e4667
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-hdpi/ic_previous.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-hdpi/icon.png b/OpenPGP-Keychain/src/main/res/drawable-hdpi/icon.png
new file mode 100644
index 000000000..571634090
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-hdpi/icon.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-hdpi/key_small.png b/OpenPGP-Keychain/src/main/res/drawable-hdpi/key_small.png
new file mode 100644
index 000000000..6966048a1
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-hdpi/key_small.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-hdpi/overlay_error.png b/OpenPGP-Keychain/src/main/res/drawable-hdpi/overlay_error.png
new file mode 100644
index 000000000..e6d7e60ba
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-hdpi/overlay_error.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-hdpi/overlay_ok.png b/OpenPGP-Keychain/src/main/res/drawable-hdpi/overlay_ok.png
new file mode 100644
index 000000000..0672f869d
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-hdpi/overlay_ok.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-hdpi/signed_large.png b/OpenPGP-Keychain/src/main/res/drawable-hdpi/signed_large.png
new file mode 100644
index 000000000..c209f4167
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-hdpi/signed_large.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-hdpi/signed_small.png b/OpenPGP-Keychain/src/main/res/drawable-hdpi/signed_small.png
new file mode 100644
index 000000000..54c4906e8
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-hdpi/signed_small.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-ldpi/encrypted_small.png b/OpenPGP-Keychain/src/main/res/drawable-ldpi/encrypted_small.png
new file mode 100644
index 000000000..5e7294a4b
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-ldpi/encrypted_small.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-ldpi/ic_next.png b/OpenPGP-Keychain/src/main/res/drawable-ldpi/ic_next.png
new file mode 100644
index 000000000..474ed8faa
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-ldpi/ic_next.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-ldpi/ic_previous.png b/OpenPGP-Keychain/src/main/res/drawable-ldpi/ic_previous.png
new file mode 100644
index 000000000..6fd885e6b
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-ldpi/ic_previous.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-ldpi/icon.png b/OpenPGP-Keychain/src/main/res/drawable-ldpi/icon.png
new file mode 100644
index 000000000..63bdba209
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-ldpi/icon.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-ldpi/key_small.png b/OpenPGP-Keychain/src/main/res/drawable-ldpi/key_small.png
new file mode 100644
index 000000000..073b95029
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-ldpi/key_small.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-ldpi/overlay_error.png b/OpenPGP-Keychain/src/main/res/drawable-ldpi/overlay_error.png
new file mode 100644
index 000000000..e5a88e18f
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-ldpi/overlay_error.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-ldpi/overlay_ok.png b/OpenPGP-Keychain/src/main/res/drawable-ldpi/overlay_ok.png
new file mode 100644
index 000000000..63374d47f
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-ldpi/overlay_ok.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-ldpi/signed_large.png b/OpenPGP-Keychain/src/main/res/drawable-ldpi/signed_large.png
new file mode 100644
index 000000000..d2917644c
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-ldpi/signed_large.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-ldpi/signed_small.png b/OpenPGP-Keychain/src/main/res/drawable-ldpi/signed_small.png
new file mode 100644
index 000000000..19d45f8da
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-ldpi/signed_small.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-mdpi/certify_small.png b/OpenPGP-Keychain/src/main/res/drawable-mdpi/certify_small.png
new file mode 100644
index 000000000..575b2d866
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-mdpi/certify_small.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-mdpi/drawer_shadow.9.png b/OpenPGP-Keychain/src/main/res/drawable-mdpi/drawer_shadow.9.png
new file mode 100644
index 000000000..ffe3a28d7
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-mdpi/drawer_shadow.9.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-mdpi/encrypted_small.png b/OpenPGP-Keychain/src/main/res/drawable-mdpi/encrypted_small.png
new file mode 100644
index 000000000..bcd8cfc8e
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-mdpi/encrypted_small.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-mdpi/ic_action_add_person.png b/OpenPGP-Keychain/src/main/res/drawable-mdpi/ic_action_add_person.png
new file mode 100644
index 000000000..c43cf6553
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-mdpi/ic_action_add_person.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-mdpi/ic_action_cancel.png b/OpenPGP-Keychain/src/main/res/drawable-mdpi/ic_action_cancel.png
new file mode 100644
index 000000000..9f4c3d6a2
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-mdpi/ic_action_cancel.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-mdpi/ic_action_discard.png b/OpenPGP-Keychain/src/main/res/drawable-mdpi/ic_action_discard.png
new file mode 100644
index 000000000..9dfb7cc2c
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-mdpi/ic_action_discard.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-mdpi/ic_action_done.png b/OpenPGP-Keychain/src/main/res/drawable-mdpi/ic_action_done.png
new file mode 100644
index 000000000..cf5fab3ad
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-mdpi/ic_action_done.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-mdpi/ic_action_import_export.png b/OpenPGP-Keychain/src/main/res/drawable-mdpi/ic_action_import_export.png
new file mode 100644
index 000000000..1d6522beb
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-mdpi/ic_action_import_export.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-mdpi/ic_action_share.png b/OpenPGP-Keychain/src/main/res/drawable-mdpi/ic_action_share.png
new file mode 100644
index 000000000..bff81179a
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-mdpi/ic_action_share.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-mdpi/ic_drawer.png b/OpenPGP-Keychain/src/main/res/drawable-mdpi/ic_drawer.png
new file mode 100644
index 000000000..1ed2c56ee
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-mdpi/ic_drawer.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-mdpi/ic_menu_search.png b/OpenPGP-Keychain/src/main/res/drawable-mdpi/ic_menu_search.png
new file mode 100644
index 000000000..2369d03f3
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-mdpi/ic_menu_search.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-mdpi/ic_menu_search_list.png b/OpenPGP-Keychain/src/main/res/drawable-mdpi/ic_menu_search_list.png
new file mode 100644
index 000000000..9033f1ec2
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-mdpi/ic_menu_search_list.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-mdpi/ic_next.png b/OpenPGP-Keychain/src/main/res/drawable-mdpi/ic_next.png
new file mode 100644
index 000000000..8271c1380
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-mdpi/ic_next.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-mdpi/ic_previous.png b/OpenPGP-Keychain/src/main/res/drawable-mdpi/ic_previous.png
new file mode 100644
index 000000000..ef90db972
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-mdpi/ic_previous.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-mdpi/icon.png b/OpenPGP-Keychain/src/main/res/drawable-mdpi/icon.png
new file mode 100644
index 000000000..bab8c56bb
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-mdpi/icon.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-mdpi/key_small.png b/OpenPGP-Keychain/src/main/res/drawable-mdpi/key_small.png
new file mode 100644
index 000000000..c806b6041
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-mdpi/key_small.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-mdpi/overlay_error.png b/OpenPGP-Keychain/src/main/res/drawable-mdpi/overlay_error.png
new file mode 100644
index 000000000..5fe017433
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-mdpi/overlay_error.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-mdpi/overlay_ok.png b/OpenPGP-Keychain/src/main/res/drawable-mdpi/overlay_ok.png
new file mode 100644
index 000000000..b4f332260
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-mdpi/overlay_ok.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-mdpi/signed_large.png b/OpenPGP-Keychain/src/main/res/drawable-mdpi/signed_large.png
new file mode 100644
index 000000000..ab9495e7b
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-mdpi/signed_large.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-mdpi/signed_small.png b/OpenPGP-Keychain/src/main/res/drawable-mdpi/signed_small.png
new file mode 100644
index 000000000..4202c3f97
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-mdpi/signed_small.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-xhdpi/drawer_shadow.9.png b/OpenPGP-Keychain/src/main/res/drawable-xhdpi/drawer_shadow.9.png
new file mode 100644
index 000000000..fabe9d965
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-xhdpi/drawer_shadow.9.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-xhdpi/ic_action_add_person.png b/OpenPGP-Keychain/src/main/res/drawable-xhdpi/ic_action_add_person.png
new file mode 100644
index 000000000..91434a47b
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-xhdpi/ic_action_add_person.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-xhdpi/ic_action_cancel.png b/OpenPGP-Keychain/src/main/res/drawable-xhdpi/ic_action_cancel.png
new file mode 100644
index 000000000..ca7d159fd
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-xhdpi/ic_action_cancel.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-xhdpi/ic_action_discard.png b/OpenPGP-Keychain/src/main/res/drawable-xhdpi/ic_action_discard.png
new file mode 100644
index 000000000..db69d6c25
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-xhdpi/ic_action_discard.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-xhdpi/ic_action_done.png b/OpenPGP-Keychain/src/main/res/drawable-xhdpi/ic_action_done.png
new file mode 100644
index 000000000..b8915716e
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-xhdpi/ic_action_done.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-xhdpi/ic_action_import_export.png b/OpenPGP-Keychain/src/main/res/drawable-xhdpi/ic_action_import_export.png
new file mode 100644
index 000000000..5e48a9c6b
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-xhdpi/ic_action_import_export.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-xhdpi/ic_action_share.png b/OpenPGP-Keychain/src/main/res/drawable-xhdpi/ic_action_share.png
new file mode 100644
index 000000000..2f6dc413b
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-xhdpi/ic_action_share.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-xhdpi/ic_drawer.png b/OpenPGP-Keychain/src/main/res/drawable-xhdpi/ic_drawer.png
new file mode 100644
index 000000000..a5fa74def
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-xhdpi/ic_drawer.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-xhdpi/ic_menu_search.png b/OpenPGP-Keychain/src/main/res/drawable-xhdpi/ic_menu_search.png
new file mode 100644
index 000000000..578cb24eb
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-xhdpi/ic_menu_search.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-xhdpi/ic_menu_search_list.png b/OpenPGP-Keychain/src/main/res/drawable-xhdpi/ic_menu_search_list.png
new file mode 100644
index 000000000..de20fa0e7
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-xhdpi/ic_menu_search_list.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-xhdpi/icon.png b/OpenPGP-Keychain/src/main/res/drawable-xhdpi/icon.png
new file mode 100644
index 000000000..79b8e27c6
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-xhdpi/icon.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-xxhdpi/drawer_shadow.9.png b/OpenPGP-Keychain/src/main/res/drawable-xxhdpi/drawer_shadow.9.png
new file mode 100644
index 000000000..b91e9d7f2
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-xxhdpi/drawer_shadow.9.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-xxhdpi/ic_action_add_person.png b/OpenPGP-Keychain/src/main/res/drawable-xxhdpi/ic_action_add_person.png
new file mode 100644
index 000000000..f18aa6144
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-xxhdpi/ic_action_add_person.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-xxhdpi/ic_action_discard.png b/OpenPGP-Keychain/src/main/res/drawable-xxhdpi/ic_action_discard.png
new file mode 100644
index 000000000..b522daffe
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-xxhdpi/ic_action_discard.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-xxhdpi/ic_action_import_export.png b/OpenPGP-Keychain/src/main/res/drawable-xxhdpi/ic_action_import_export.png
new file mode 100644
index 000000000..f054a68e6
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-xxhdpi/ic_action_import_export.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-xxhdpi/ic_action_share.png b/OpenPGP-Keychain/src/main/res/drawable-xxhdpi/ic_action_share.png
new file mode 100644
index 000000000..3e441000f
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-xxhdpi/ic_action_share.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-xxhdpi/ic_drawer.png b/OpenPGP-Keychain/src/main/res/drawable-xxhdpi/ic_drawer.png
new file mode 100644
index 000000000..9c4685d6e
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-xxhdpi/ic_drawer.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-xxhdpi/icon.png b/OpenPGP-Keychain/src/main/res/drawable-xxhdpi/icon.png
new file mode 100644
index 000000000..ac8190c93
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-xxhdpi/icon.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable-xxxhdpi/icon.png b/OpenPGP-Keychain/src/main/res/drawable-xxxhdpi/icon.png
new file mode 100644
index 000000000..cdc0fc9f0
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable-xxxhdpi/icon.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable/certify_small.png b/OpenPGP-Keychain/src/main/res/drawable/certify_small.png
new file mode 100644
index 000000000..575b2d866
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable/certify_small.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable/encrypted_small.png b/OpenPGP-Keychain/src/main/res/drawable/encrypted_small.png
new file mode 100644
index 000000000..7f4ab803f
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable/encrypted_small.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable/ic_next.png b/OpenPGP-Keychain/src/main/res/drawable/ic_next.png
new file mode 100644
index 000000000..8271c1380
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable/ic_next.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable/ic_previous.png b/OpenPGP-Keychain/src/main/res/drawable/ic_previous.png
new file mode 100644
index 000000000..ef90db972
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable/ic_previous.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable/key_small.png b/OpenPGP-Keychain/src/main/res/drawable/key_small.png
new file mode 100644
index 000000000..121803508
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable/key_small.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable/overlay_error.png b/OpenPGP-Keychain/src/main/res/drawable/overlay_error.png
new file mode 100644
index 000000000..2372de59e
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable/overlay_error.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable/overlay_ok.png b/OpenPGP-Keychain/src/main/res/drawable/overlay_ok.png
new file mode 100644
index 000000000..2f0005898
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable/overlay_ok.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable/section_header.xml b/OpenPGP-Keychain/src/main/res/drawable/section_header.xml
new file mode 100644
index 000000000..a4468484e
--- /dev/null
+++ b/OpenPGP-Keychain/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/OpenPGP-Keychain/src/main/res/drawable/signed_large.png b/OpenPGP-Keychain/src/main/res/drawable/signed_large.png
new file mode 100644
index 000000000..92e64dc51
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable/signed_large.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/drawable/signed_small.png b/OpenPGP-Keychain/src/main/res/drawable/signed_small.png
new file mode 100644
index 000000000..590220281
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/drawable/signed_small.png
Binary files differ
diff --git a/OpenPGP-Keychain/src/main/res/layout/actionbar_custom_view_done.xml b/OpenPGP-Keychain/src/main/res/layout/actionbar_custom_view_done.xml
new file mode 100644
index 000000000..bcbb5f5c1
--- /dev/null
+++ b/OpenPGP-Keychain/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:divider="@drawable/abs__list_divider_holo_light"
+ android:dividerPadding="12dp"
+ android:orientation="horizontal"
+ android:showDividers="end" >
+
+ <include layout="@layout/actionbar_include_done_button" />
+
+</LinearLayout> \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/res/layout/actionbar_custom_view_done_cancel.xml b/OpenPGP-Keychain/src/main/res/layout/actionbar_custom_view_done_cancel.xml
new file mode 100644
index 000000000..8c8428e75
--- /dev/null
+++ b/OpenPGP-Keychain/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:divider="@drawable/abs__list_divider_holo_light"
+ android:dividerPadding="12dp"
+ 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/OpenPGP-Keychain/src/main/res/layout/actionbar_include_cancel_button.xml b/OpenPGP-Keychain/src/main/res/layout/actionbar_include_cancel_button.xml
new file mode 100644
index 000000000..0f0521d3c
--- /dev/null
+++ b/OpenPGP-Keychain/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.Sherlock.ActionButton"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1" >
+
+ <TextView
+ android:id="@+id/actionbar_cancel_text"
+ style="@style/Widget.Sherlock.ActionBar.TabText"
+ 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"
+ android:text="Cancel (set in-code!)" />
+
+</FrameLayout> \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/res/layout/actionbar_include_done_button.xml b/OpenPGP-Keychain/src/main/res/layout/actionbar_include_done_button.xml
new file mode 100644
index 000000000..54e5933e3
--- /dev/null
+++ b/OpenPGP-Keychain/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.Sherlock.ActionButton"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1" >
+
+ <TextView
+ android:id="@+id/actionbar_done_text"
+ style="@style/Widget.Sherlock.ActionBar.TabText"
+ 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"
+ android:text="Done (set in-code!)" />
+
+</FrameLayout> \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/res/layout/api_app_error_message.xml b/OpenPGP-Keychain/src/main/res/layout/api_app_error_message.xml
new file mode 100644
index 000000000..5927dbf43
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/layout/api_app_error_message.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ 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/OpenPGP-Keychain/src/main/res/layout/api_app_register_activity.xml b/OpenPGP-Keychain/src/main/res/layout/api_app_register_activity.xml
new file mode 100644
index 000000000..2cbc81cf7
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/layout/api_app_register_activity.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:layout_marginLeft="16dp"
+ android:layout_marginRight="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.service.remote.AppSettingsFragment"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ tools:layout="@layout/api_app_settings_fragment" />
+
+</LinearLayout> \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/res/layout/api_app_select_pub_keys_activity.xml b/OpenPGP-Keychain/src/main/res/layout/api_app_select_pub_keys_activity.xml
new file mode 100644
index 000000000..877b4e74e
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/layout/api_app_select_pub_keys_activity.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ 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/OpenPGP-Keychain/src/main/res/layout/api_app_settings_activity.xml b/OpenPGP-Keychain/src/main/res/layout/api_app_settings_activity.xml
new file mode 100644
index 000000000..3dc8d5e84
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/layout/api_app_settings_activity.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout 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"
+ android:layout_marginLeft="16dp"
+ android:layout_marginRight="16dp"
+ android:orientation="vertical" >
+
+ <fragment
+ android:id="@+id/api_app_settings_fragment"
+ android:name="org.sufficientlysecure.keychain.service.remote.AppSettingsFragment"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ tools:layout="@layout/api_app_settings_fragment" />
+
+</LinearLayout> \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/res/layout/api_app_settings_fragment.xml b/OpenPGP-Keychain/src/main/res/layout/api_app_settings_fragment.xml
new file mode 100644
index 000000000..0695e5922
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/layout/api_app_settings_fragment.xml
@@ -0,0 +1,129 @@
+<?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="match_parent"
+ android:layout_height="wrap_content" >
+
+ <LinearLayout
+ 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>
+
+ <fragment
+ android:id="@+id/api_app_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_app_settings_advanced_button"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_marginBottom="4dp"
+ android:layout_marginTop="4dp"
+ android:text="@string/api_settings_show_advanced"
+ bootstrapbutton:bb_icon_left="fa-caret-up"
+ bootstrapbutton:bb_size="default"
+ bootstrapbutton:bb_type="default" />
+
+ <LinearLayout
+ android:id="@+id/api_app_settings_advanced"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:visibility="gone" >
+
+ <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_app_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_app_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_app_settings_compression"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
+ <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" />
+ </LinearLayout>
+ </LinearLayout>
+
+</ScrollView> \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/res/layout/api_apps_adapter_list_item.xml b/OpenPGP-Keychain/src/main/res/layout/api_apps_adapter_list_item.xml
new file mode 100644
index 000000000..cb20a20af
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/layout/api_apps_adapter_list_item.xml
@@ -0,0 +1,28 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="?android:attr/listPreferredItemHeight"
+ android:gravity="left"
+ android:orientation="horizontal" >
+
+ <ImageView
+ android:id="@+id/api_apps_adapter_item_icon"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_alignParentBottom="true"
+ android:layout_alignParentTop="true"
+ android:layout_marginLeft="8dp"
+ android:layout_marginRight="8dp"
+ 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_centerVertical="true"
+ android:layout_toRightOf="@+id/api_apps_adapter_item_icon"
+ android:gravity="center_vertical"
+ android:orientation="vertical"
+ android:text="Set in-code!"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+</RelativeLayout> \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/res/layout/api_apps_list_activity.xml b/OpenPGP-Keychain/src/main/res/layout/api_apps_list_activity.xml
new file mode 100644
index 000000000..71fbcfb12
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/layout/api_apps_list_activity.xml
@@ -0,0 +1,20 @@
+<?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" >
+
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" >
+
+ <fragment
+ android:id="@+id/crypto_consumers_list_fragment"
+ android:name="org.sufficientlysecure.keychain.service.remote.RegisteredAppsListFragment"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+ </FrameLayout>
+
+ <include layout="@layout/drawer_list" />
+
+</android.support.v4.widget.DrawerLayout> \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/res/layout/create_key_dialog.xml b/OpenPGP-Keychain/src/main/res/layout/create_key_dialog.xml
new file mode 100644
index 000000000..a2e908433
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/layout/create_key_dialog.xml
@@ -0,0 +1,54 @@
+<?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" />
+
+ <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/OpenPGP-Keychain/src/main/res/layout/decrypt_activity.xml b/OpenPGP-Keychain/src/main/res/layout/decrypt_activity.xml
new file mode 100644
index 000000000..9d9e1a1e8
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/layout/decrypt_activity.xml
@@ -0,0 +1,177 @@
+<?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" >
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:fillViewport="true"
+ android:orientation="vertical" >
+
+ <ScrollView
+ 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:orientation="vertical"
+ android:paddingLeft="10dp"
+ android:paddingRight="10dp" >
+
+ <LinearLayout
+ 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" >
+
+ <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>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingLeft="5dip" >
+
+ <TextView
+ android:id="@+id/mainUserId"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="left"
+ android:text="Main User Id"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+ <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" />
+ </LinearLayout>
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+
+ <ImageView
+ android:id="@+id/sourcePrevious"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/ic_previous" />
+
+ <TextView
+ android:id="@+id/sourceLabel"
+ style="@style/SectionHeader"
+ android:layout_width="0dip"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:gravity="center_horizontal|center_vertical"
+ android:text="@string/label_message"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+ <ImageView
+ android:id="@+id/sourceNext"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/ic_next" />
+ </LinearLayout>
+
+ <ViewFlipper
+ android:id="@+id/source"
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1" >
+
+ <LinearLayout
+ android:id="@+id/sourceMessage"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:padding="4dp" >
+
+ <EditText
+ android:id="@+id/message"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="top"
+ android:inputType="text|textCapSentences|textMultiLine|textLongMessage"
+ android:scrollHorizontally="true" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/sourceFile"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:padding="4dp" >
+
+ <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:inputType="textNoSuggestions" />
+
+ <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>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+
+ <CheckBox
+ android:id="@+id/deleteAfterDecryption"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:text="@string/label_delete_after_decryption" />
+ </LinearLayout>
+ </LinearLayout>
+ </ViewFlipper>
+ </LinearLayout>
+ </ScrollView>
+ </LinearLayout>
+
+ <include layout="@layout/drawer_list" />
+
+</android.support.v4.widget.DrawerLayout> \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/res/layout/drawer_list.xml b/OpenPGP-Keychain/src/main/res/layout/drawer_list.xml
new file mode 100644
index 000000000..18210afc5
--- /dev/null
+++ b/OpenPGP-Keychain/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="240dp"
+ android:layout_height="match_parent"
+ android:layout_gravity="start"
+ android:background="#fff"
+ android:choiceMode="singleChoice"
+ android:divider="@color/bg_gray"
+ android:dividerHeight="1dp" />
diff --git a/OpenPGP-Keychain/src/main/res/layout/drawer_list_item.xml b/OpenPGP-Keychain/src/main/res/layout/drawer_list_item.xml
new file mode 100644
index 000000000..14760e79d
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/layout/drawer_list_item.xml
@@ -0,0 +1,28 @@
+<LinearLayout 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"
+ android:orientation="horizontal" >
+
+ <com.beardedhen.androidbootstrap.FontAwesomeText
+ android:id="@+id/drawer_item_icon"
+ android:layout_width="32dp"
+ android:layout_height="32dp"
+ android:layout_margin="10dp"
+ android:gravity="center_vertical"
+ android:textSize="24sp"
+ fontawesometext:fa_icon="fa-github" />
+
+ <TextView
+ android:id="@+id/drawer_item_text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_vertical"
+ android:paddingBottom="16dp"
+ android:paddingLeft="4dp"
+ android:paddingRight="16dp"
+ android:paddingTop="16dp"
+ android:textAppearance="@android:style/TextAppearance.Medium"
+ android:textColor="#111" />
+
+</LinearLayout>
diff --git a/OpenPGP-Keychain/src/main/res/layout/edit_key_activity.xml b/OpenPGP-Keychain/src/main/res/layout/edit_key_activity.xml
new file mode 100644
index 000000000..351aec512
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/layout/edit_key_activity.xml
@@ -0,0 +1,62 @@
+<?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.
+-->
+
+<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_pass_phrase"
+ 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/OpenPGP-Keychain/src/main/res/layout/edit_key_key_item.xml b/OpenPGP-Keychain/src/main/res/layout/edit_key_key_item.xml
new file mode 100644
index 000000000..4bf4aa38a
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/layout/edit_key_key_item.xml
@@ -0,0 +1,125 @@
+<?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: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="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>
+
+ <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" />
+
+ <Spinner
+ android:id="@+id/usage"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+ </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> \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/res/layout/edit_key_section.xml b/OpenPGP-Keychain/src/main/res/layout/edit_key_section.xml
new file mode 100644
index 000000000..9f10ff8c1
--- /dev/null
+++ b/OpenPGP-Keychain/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/OpenPGP-Keychain/src/main/res/layout/edit_key_user_id_item.xml b/OpenPGP-Keychain/src/main/res/layout/edit_key_user_id_item.xml
new file mode 100644
index 000000000..663949d8e
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/layout/edit_key_user_id_item.xml
@@ -0,0 +1,98 @@
+<?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" />
+
+ <EditText
+ 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" />
+ </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/OpenPGP-Keychain/src/main/res/layout/encrypt_activity.xml b/OpenPGP-Keychain/src/main/res/layout/encrypt_activity.xml
new file mode 100644
index 000000000..eea484df2
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/layout/encrypt_activity.xml
@@ -0,0 +1,307 @@
+<?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" >
+
+ <ScrollView
+ 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:orientation="vertical"
+ android:paddingLeft="10dp"
+ android:paddingRight="10dp" >
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:padding="4dp" >
+
+ <ImageView
+ android:id="@+id/modePrevious"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/ic_previous" />
+
+ <TextView
+ android:id="@+id/modeLabel"
+ style="@style/SectionHeader"
+ android:layout_width="0dip"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:gravity="center_horizontal|center_vertical"
+ android:text="@string/label_asymmetric" />
+
+ <ImageView
+ android:id="@+id/modeNext"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/ic_next" />
+ </LinearLayout>
+
+ <ViewFlipper
+ android:id="@+id/mode"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" >
+
+ <LinearLayout
+ android:id="@+id/modeAsymmetric"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:padding="4dp" >
+
+ <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" >
+
+ <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="Sign User Id"
+ 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="Sign email"
+ android:textAppearance="?android:attr/textAppearanceSmall" />
+ </LinearLayout>
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:paddingBottom="3dip" >
+
+ <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/btn_select_encrypt_keys"
+ bootstrapbutton:bb_icon_left="fa-user"
+ bootstrapbutton:bb_size="default"
+ bootstrapbutton:bb_type="default" />
+ </LinearLayout>
+ </LinearLayout>
+
+ <TableLayout
+ android:id="@+id/modeSymmetric"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="4dp"
+ android:stretchColumns="1" >
+
+ <TableRow>
+
+ <TextView
+ android:id="@+id/label_passPhrase"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:paddingRight="10dip"
+ 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:layout_gravity="center_vertical"
+ android:paddingRight="10dip"
+ 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>
+ </ViewFlipper>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:padding="4dp" >
+
+ <ImageView
+ android:id="@+id/sourcePrevious"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/ic_previous" />
+
+ <TextView
+ android:id="@+id/sourceLabel"
+ style="@style/SectionHeader"
+ android:layout_width="0dip"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:gravity="center_horizontal|center_vertical"
+ android:text="@string/label_message"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+ <ImageView
+ android:id="@+id/sourceNext"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/ic_next" />
+ </LinearLayout>
+
+ <ViewFlipper
+ android:id="@+id/source"
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1" >
+
+ <LinearLayout
+ android:id="@+id/sourceMessage"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:padding="4dp" >
+
+ <EditText
+ android:id="@+id/message"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="top"
+ android:inputType="text|textCapSentences|textMultiLine|textLongMessage" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/sourceFile"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:padding="4dp" >
+
+ <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:inputType="textNoSuggestions" />
+
+ <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>
+
+ <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/asciiArmour"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:text="@string/label_ascii_armor" />
+ </LinearLayout>
+ </LinearLayout>
+ </ViewFlipper>
+ </LinearLayout>
+ </ScrollView>
+
+ <include layout="@layout/drawer_list" />
+
+</android.support.v4.widget.DrawerLayout> \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/res/layout/file_dialog.xml b/OpenPGP-Keychain/src/main/res/layout/file_dialog.xml
new file mode 100644
index 000000000..87816067f
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/layout/file_dialog.xml
@@ -0,0 +1,61 @@
+<?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"
+ 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" >
+
+ <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="2"
+ android:maxLines="6"
+ 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/OpenPGP-Keychain/src/main/res/layout/help_about_fragment.xml b/OpenPGP-Keychain/src/main/res/layout/help_about_fragment.xml
new file mode 100644
index 000000000..71788e720
--- /dev/null
+++ b/OpenPGP-Keychain/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/OpenPGP-Keychain/src/main/res/layout/help_activity.xml b/OpenPGP-Keychain/src/main/res/layout/help_activity.xml
new file mode 100644
index 000000000..58e4919dc
--- /dev/null
+++ b/OpenPGP-Keychain/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/OpenPGP-Keychain/src/main/res/layout/import_keys_activity.xml b/OpenPGP-Keychain/src/main/res/layout/import_keys_activity.xml
new file mode 100644
index 000000000..d7794ace3
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/layout/import_keys_activity.xml
@@ -0,0 +1,63 @@
+<?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" >
+
+ <RelativeLayout
+ 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="10dp"
+ android:paddingRight="10dp" >
+
+ <com.beardedhen.androidbootstrap.BootstrapButton
+ android:id="@+id/import_import"
+ android:layout_width="match_parent"
+ android:layout_height="60dp"
+ android:padding="4dp"
+ android:text="@string/import_import"
+ bootstrapbutton:bb_icon_left="fa-download"
+ bootstrapbutton:bb_type="info" />
+
+ <!-- <com.beardedhen.androidbootstrap.BootstrapButton -->
+ <!-- android:id="@+id/import_sign_and_upload" -->
+ <!-- android:layout_width="match_parent" -->
+ <!-- android:layout_height="60dp" -->
+ <!-- android:padding="4dp" -->
+ <!-- android:text="@string/import_sign_and_upload" -->
+ <!-- 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>
+
+ <include layout="@layout/drawer_list" />
+
+</android.support.v4.widget.DrawerLayout> \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/res/layout/import_keys_clipboard_fragment.xml b/OpenPGP-Keychain/src/main/res/layout/import_keys_clipboard_fragment.xml
new file mode 100644
index 000000000..abde4e972
--- /dev/null
+++ b/OpenPGP-Keychain/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="60dp"
+ 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/OpenPGP-Keychain/src/main/res/layout/import_keys_file_fragment.xml b/OpenPGP-Keychain/src/main/res/layout/import_keys_file_fragment.xml
new file mode 100644
index 000000000..126bcd8a6
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/layout/import_keys_file_fragment.xml
@@ -0,0 +1,32 @@
+<?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="horizontal" >
+
+ <EditText
+ android:id="@+id/import_keys_file_input"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:gravity="top|left"
+ android:inputType="textMultiLine|textUri"
+ android:lines="2"
+ android:maxLines="6"
+ android:minLines="2"
+ android:scrollbars="vertical" />
+
+ <com.beardedhen.androidbootstrap.BootstrapButton
+ android:id="@+id/import_keys_file_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> \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/res/layout/import_keys_keyserver_fragment.xml b/OpenPGP-Keychain/src/main/res/layout/import_keys_keyserver_fragment.xml
new file mode 100644
index 000000000..74a2d7853
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/layout/import_keys_keyserver_fragment.xml
@@ -0,0 +1,43 @@
+<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:orientation="vertical" >
+
+ <com.beardedhen.androidbootstrap.BootstrapButton
+ android:id="@+id/import_keyserver_button"
+ android:layout_width="match_parent"
+ android:layout_height="60dp"
+ android:layout_margin="10dp"
+ android:text="@string/menu_key_server"
+ bootstrapbutton:bb_size="default"
+ bootstrapbutton:bb_type="default" />
+
+ <!-- <Spinner -->
+ <!-- android:id="@+id/import_keys_server_key_server" -->
+ <!-- android:layout_width="fill_parent" -->
+ <!-- android:layout_height="wrap_content" /> -->
+
+
+ <!-- <LinearLayout -->
+ <!-- android:layout_width="fill_parent" -->
+ <!-- android:layout_height="wrap_content" -->
+ <!-- android:orientation="horizontal" > -->
+
+
+ <!-- <EditText -->
+ <!-- android:id="@+id/import_keys_server_query" -->
+ <!-- android:layout_width="0dip" -->
+ <!-- android:layout_height="wrap_content" -->
+ <!-- android:layout_weight="1" -->
+ <!-- android:inputType="textPersonName|textEmailAddress" /> -->
+
+
+ <!-- <Button -->
+ <!-- android:id="@+id/import_keys_server_search" -->
+ <!-- android:layout_width="wrap_content" -->
+ <!-- android:layout_height="wrap_content" -->
+ <!-- android:text="@string/btn_search" /> -->
+ <!-- </LinearLayout> -->
+
+</LinearLayout> \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/res/layout/import_keys_list_entry.xml b/OpenPGP-Keychain/src/main/res/layout/import_keys_list_entry.xml
new file mode 100644
index 000000000..37d1c5702
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/layout/import_keys_list_entry.xml
@@ -0,0 +1,108 @@
+<?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="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingLeft="3dip"
+ android:paddingRight="?android:attr/scrollbarSize"
+ android:singleLine="true" >
+
+ <LinearLayout
+ android:layout_width="fill_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="Main User ID"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+ <TextView
+ android:id="@+id/fingerprint"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="fingerprint"
+ android:textAppearance="?android:attr/textAppearanceSmall" />
+
+ <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="fill_parent"
+ android:text="BBBBBBBB"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:typeface="monospace" />
+
+ <TextView
+ android:id="@+id/algorithm"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ 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="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="36dip"
+ android:orientation="vertical" >
+ </LinearLayout>
+
+</LinearLayout> \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/res/layout/import_keys_list_entry_user_id.xml b/OpenPGP-Keychain/src/main/res/layout/import_keys_list_entry_user_id.xml
new file mode 100644
index 000000000..9d3a4a1ab
--- /dev/null
+++ b/OpenPGP-Keychain/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/OpenPGP-Keychain/src/main/res/layout/import_keys_nfc_fragment.xml b/OpenPGP-Keychain/src/main/res/layout/import_keys_nfc_fragment.xml
new file mode 100644
index 000000000..203cc6ad6
--- /dev/null
+++ b/OpenPGP-Keychain/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:layout_margin="10dp"
+ android:orientation="horizontal" >
+
+ <com.beardedhen.androidbootstrap.BootstrapButton
+ android:id="@+id/import_nfc_button"
+ android:layout_width="wrap_content"
+ android:layout_height="60dp"
+ 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/OpenPGP-Keychain/src/main/res/layout/import_keys_qr_code_fragment.xml b/OpenPGP-Keychain/src/main/res/layout/import_keys_qr_code_fragment.xml
new file mode 100644
index 000000000..5229e7cf5
--- /dev/null
+++ b/OpenPGP-Keychain/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="60dp"
+ 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/OpenPGP-Keychain/src/main/res/layout/key_list_item.xml b/OpenPGP-Keychain/src/main/res/layout/key_list_item.xml
new file mode 100644
index 000000000..2571bb6e7
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/layout/key_list_item.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="?android:attr/listPreferredItemHeight"
+ android:layout_marginRight="?android:attr/scrollbarSize"
+ android:orientation="vertical"
+ android:paddingLeft="8dp"
+ android:singleLine="true" >
+
+ <TextView
+ android:id="@+id/mainUserId"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="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> \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/res/layout/key_list_public_activity.xml b/OpenPGP-Keychain/src/main/res/layout/key_list_public_activity.xml
new file mode 100644
index 000000000..f0e843e56
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/layout/key_list_public_activity.xml
@@ -0,0 +1,20 @@
+<?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" >
+
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" >
+
+ <fragment
+ android:id="@+id/key_list_public_fragment"
+ android:name="org.sufficientlysecure.keychain.ui.KeyListPublicFragment"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+ </FrameLayout>
+
+ <include layout="@layout/drawer_list" />
+
+</android.support.v4.widget.DrawerLayout> \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/res/layout/key_list_public_fragment.xml b/OpenPGP-Keychain/src/main/res/layout/key_list_public_fragment.xml
new file mode 100644
index 000000000..f3a21e1bf
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/layout/key_list_public_fragment.xml
@@ -0,0 +1,77 @@
+<?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" >
+
+ <se.emilsjolander.stickylistheaders.StickyListHeadersListView
+ android:id="@+id/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/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> \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/res/layout/key_list_public_header.xml b/OpenPGP-Keychain/src/main/res/layout/key_list_public_header.xml
new file mode 100644
index 000000000..5768e4153
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/layout/key_list_public_header.xml
@@ -0,0 +1,16 @@
+<?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" />
+
+</RelativeLayout> \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/res/layout/key_list_secret_activity.xml b/OpenPGP-Keychain/src/main/res/layout/key_list_secret_activity.xml
new file mode 100644
index 000000000..13370f2e5
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/layout/key_list_secret_activity.xml
@@ -0,0 +1,20 @@
+<?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" >
+
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" >
+
+ <fragment
+ android:id="@+id/key_list_secret_fragment"
+ android:name="org.sufficientlysecure.keychain.ui.KeyListSecretFragment"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+ </FrameLayout>
+
+ <include layout="@layout/drawer_list" />
+
+</android.support.v4.widget.DrawerLayout> \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/res/layout/key_server_editor.xml b/OpenPGP-Keychain/src/main/res/layout/key_server_editor.xml
new file mode 100644
index 000000000..058a43eaf
--- /dev/null
+++ b/OpenPGP-Keychain/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="match_parent"
+ 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/OpenPGP-Keychain/src/main/res/layout/key_server_export.xml b/OpenPGP-Keychain/src/main/res/layout/key_server_export.xml
new file mode 100644
index 000000000..cfbf7d79d
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/layout/key_server_export.xml
@@ -0,0 +1,41 @@
+<?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="match_parent"
+ android:layout_height="match_parent" >
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="16dp"
+ android:layout_marginRight="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/OpenPGP-Keychain/src/main/res/layout/key_server_preference.xml b/OpenPGP-Keychain/src/main/res/layout/key_server_preference.xml
new file mode 100644
index 000000000..8b99e5d2f
--- /dev/null
+++ b/OpenPGP-Keychain/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="@+android: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="wrap_content"
+ 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="fill_parent"
+ android:orientation="vertical" />
+ </ScrollView>
+
+</LinearLayout> \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/res/layout/key_server_query.xml b/OpenPGP-Keychain/src/main/res/layout/key_server_query.xml
new file mode 100644
index 000000000..5ba90de47
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/layout/key_server_query.xml
@@ -0,0 +1,40 @@
+<?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" >
+
+ <Spinner
+ android:id="@+id/sign_key_keyserver"
+ 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/query"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:hint="@string/hint_public_keys"
+ android:imeOptions="actionSearch"
+ android:inputType="textNoSuggestions"
+ android:singleLine="true" />
+
+ <Button
+ android:id="@+id/btn_search"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/btn_search" />
+ </LinearLayout>
+
+ <ListView
+ android:id="@+id/list"
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1" />
+
+</LinearLayout> \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/res/layout/key_server_query_result_item.xml b/OpenPGP-Keychain/src/main/res/layout/key_server_query_result_item.xml
new file mode 100644
index 000000000..6d883d26d
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/layout/key_server_query_result_item.xml
@@ -0,0 +1,77 @@
+<?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"
+ 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" >
+
+ <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="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="match_parent"
+ android:text="BBBBBBBB"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:typeface="monospace" />
+
+ <TextView
+ android:id="@+id/algorithm"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ 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="30dip"
+ android:orientation="vertical" >
+ </LinearLayout>
+
+</LinearLayout> \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/res/layout/key_server_query_result_user_id.xml b/OpenPGP-Keychain/src/main/res/layout/key_server_query_result_user_id.xml
new file mode 100644
index 000000000..c69735e67
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/layout/key_server_query_result_user_id.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginRight="?android:attr/scrollbarSize"
+ android:paddingRight="3dip"
+ android:singleLine="true"
+ android:textAppearance="?android:attr/textAppearanceSmall" >
+
+</TextView> \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/res/layout/passphrase_dialog.xml b/OpenPGP-Keychain/src/main/res/layout/passphrase_dialog.xml
new file mode 100644
index 000000000..4b331f0f2
--- /dev/null
+++ b/OpenPGP-Keychain/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/OpenPGP-Keychain/src/main/res/layout/passphrase_repeat_dialog.xml b/OpenPGP-Keychain/src/main/res/layout/passphrase_repeat_dialog.xml
new file mode 100644
index 000000000..2bdd231ee
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/layout/passphrase_repeat_dialog.xml
@@ -0,0 +1,46 @@
+<?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>
+
+ <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>
+
+ <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/OpenPGP-Keychain/src/main/res/layout/select_key_item.xml b/OpenPGP-Keychain/src/main/res/layout/select_key_item.xml
new file mode 100644
index 000000000..bbfe17c44
--- /dev/null
+++ b/OpenPGP-Keychain/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="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/OpenPGP-Keychain/src/main/res/layout/select_public_key_activity.xml b/OpenPGP-Keychain/src/main/res/layout/select_public_key_activity.xml
new file mode 100644
index 000000000..a18ce46fc
--- /dev/null
+++ b/OpenPGP-Keychain/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/OpenPGP-Keychain/src/main/res/layout/select_secret_key_activity.xml b/OpenPGP-Keychain/src/main/res/layout/select_secret_key_activity.xml
new file mode 100644
index 000000000..c4cdd7576
--- /dev/null
+++ b/OpenPGP-Keychain/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/OpenPGP-Keychain/src/main/res/layout/select_secret_key_layout_fragment.xml b/OpenPGP-Keychain/src/main/res/layout/select_secret_key_layout_fragment.xml
new file mode 100644
index 000000000..4a3cd3d28
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/layout/select_secret_key_layout_fragment.xml
@@ -0,0 +1,55 @@
+<?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="16dp" >
+
+ <!-- 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="right"
+ android:ellipsize="end"
+ android:focusable="true"
+ android:focusableInTouchMode="true"
+ android:singleLine="true"
+ android:text="@string/api_settings_no_key"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+ <TextView
+ android:id="@+id/select_secret_key_user_id_rest"
+ 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> \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/res/layout/share_qr_code_dialog.xml b/OpenPGP-Keychain/src/main/res/layout/share_qr_code_dialog.xml
new file mode 100644
index 000000000..0b58ae72f
--- /dev/null
+++ b/OpenPGP-Keychain/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/OpenPGP-Keychain/src/main/res/layout/sign_key_activity.xml b/OpenPGP-Keychain/src/main/res/layout/sign_key_activity.xml
new file mode 100644
index 000000000..98c602e88
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/layout/sign_key_activity.xml
@@ -0,0 +1,68 @@
+<?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="match_parent"
+ android:layout_height="match_parent" >
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="16dp"
+ android:layout_marginRight="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_signing_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="@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_sign"
+ bootstrapbutton:bb_icon_left="fa-pencil"
+ bootstrapbutton:bb_type="info" />
+ </LinearLayout>
+
+</ScrollView> \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/res/layout/view_key_activity.xml b/OpenPGP-Keychain/src/main/res/layout/view_key_activity.xml
new file mode 100644
index 000000000..6fde11e94
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/layout/view_key_activity.xml
@@ -0,0 +1,220 @@
+<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:orientation="vertical"
+ android:paddingLeft="16dp"
+ android:paddingRight="16dp" >
+
+ <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="center_vertical"
+ android:paddingRight="10dip"
+ android:text="@string/label_name" />
+
+ <TextView
+ android:id="@+id/name"
+ android:layout_width="wrap_content"
+ 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="center_vertical"
+ android:paddingRight="10dip"
+ android:text="@string/label_email" />
+
+ <TextView
+ android:id="@+id/email"
+ android:layout_width="wrap_content"
+ 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="center_vertical"
+ android:paddingRight="10dip"
+ android:text="@string/label_comment" />
+
+ <TextView
+ android:id="@+id/comment"
+ android:layout_width="wrap_content"
+ 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: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/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>
+
+ <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_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_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_marginBottom="4dp"
+ android:layout_marginTop="14dp"
+ android:text="@string/section_actions" />
+
+ <com.beardedhen.androidbootstrap.BootstrapButton
+ android:id="@+id/action_encrypt"
+ android:layout_width="match_parent"
+ android:layout_height="60dp"
+ android:padding="4dp"
+ android:text="@string/key_view_action_encrypt"
+ bootstrapbutton:bb_icon_left="fa-lock"
+ bootstrapbutton:bb_type="info" />
+ </LinearLayout>
+
+</ScrollView> \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/res/layout/view_key_keys_item.xml b/OpenPGP-Keychain/src/main/res/layout/view_key_keys_item.xml
new file mode 100644
index 000000000..b50253980
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/layout/view_key_keys_item.xml
@@ -0,0 +1,63 @@
+<?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:layout_marginRight="?android:attr/scrollbarSize"
+ android:orientation="horizontal"
+ android:paddingLeft="8dip"
+ android:paddingRight="3dip"
+ android:singleLine="true" >
+
+ <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" />
+
+ <TextView
+ android:id="@+id/keyId"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingRight="5dip"
+ android:text="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:text="(RSA, 1024bit)"
+ android:textAppearance="?android:attr/textAppearanceSmall" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center_vertical"
+ android:gravity="right"
+ android:orientation="horizontal"
+ android:paddingBottom="2dip"
+ android:paddingTop="2dip" >
+
+ <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> \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/res/layout/view_key_userids_item.xml b/OpenPGP-Keychain/src/main/res/layout/view_key_userids_item.xml
new file mode 100644
index 000000000..2d022ba13
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/layout/view_key_userids_item.xml
@@ -0,0 +1,17 @@
+<?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:layout_marginRight="?android:attr/scrollbarSize"
+ android:orientation="vertical"
+ android:singleLine="true" >
+
+ <TextView
+ android:id="@+id/userId"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingRight="3dip"
+ android:text="User ID"
+ android:textAppearance="?android:attr/textAppearanceSmall" />
+
+</LinearLayout> \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/res/menu/api_app_settings.xml b/OpenPGP-Keychain/src/main/res/menu/api_app_settings.xml
new file mode 100644
index 000000000..1ee05f5de
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/menu/api_app_settings.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <item
+ android:id="@+id/menu_api_settings_revoke"
+ android:showAsAction="never"
+ android:title="@string/api_settings_revoke"/>
+ <item
+ android:id="@+id/menu_api_settings_cancel"
+ android:showAsAction="never"
+ android:title="@string/api_settings_cancel"/>
+
+</menu> \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/res/menu/key_edit.xml b/OpenPGP-Keychain/src/main/res/menu/key_edit.xml
new file mode 100644
index 000000000..38c52e7f0
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/menu/key_edit.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <item
+ android:id="@+id/menu_key_edit_export_file"
+ android:showAsAction="never"
+ android:title="@string/menu_export_key"/>
+ <item
+ android:id="@+id/menu_key_edit_delete"
+ android:showAsAction="never"
+ android:title="@string/menu_delete_key"/>
+ <item
+ android:id="@+id/menu_key_edit_cancel"
+ android:showAsAction="never"
+ android:title="@string/menu_key_edit_cancel"/>
+
+</menu> \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/res/menu/key_list_public.xml b/OpenPGP-Keychain/src/main/res/menu/key_list_public.xml
new file mode 100644
index 000000000..72ebe0216
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/menu/key_list_public.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <item
+ android:id="@+id/menu_key_list_public_import"
+ android:showAsAction="always|withText"
+ android:icon="@drawable/ic_action_add_person"
+ android:title="@string/menu_import"/>
+ <item
+ android:id="@+id/menu_key_list_public_export"
+ android:showAsAction="never"
+ android:title="@string/menu_export_keys"/>
+
+</menu> \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/res/menu/key_list_public_multi.xml b/OpenPGP-Keychain/src/main/res/menu/key_list_public_multi.xml
new file mode 100644
index 000000000..e750c1259
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/menu/key_list_public_multi.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <item
+ android:id="@+id/menu_key_list_public_multi_encrypt"
+ android:title="@string/menu_encrypt_to"/>
+ <item
+ android:id="@+id/menu_key_list_public_multi_delete"
+ android:icon="@drawable/ic_action_discard"
+ android:title="@string/menu_delete_key"/>
+
+</menu> \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/res/menu/key_list_secret.xml b/OpenPGP-Keychain/src/main/res/menu/key_list_secret.xml
new file mode 100644
index 000000000..c610eda35
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/menu/key_list_secret.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <item
+ android:id="@+id/menu_key_list_secret_create"
+ android:showAsAction="always|withText"
+ android:title="@string/menu_create_key"/>
+ <item
+ android:id="@+id/menu_key_list_secret_create_expert"
+ android:showAsAction="never"
+ android:title="@string/menu_create_key_expert"/>
+ <item
+ android:id="@+id/menu_key_list_secret_import"
+ android:showAsAction="never"
+ android:title="@string/menu_import"/>
+ <item
+ android:id="@+id/menu_key_list_secret_export"
+ android:showAsAction="never"
+ android:title="@string/menu_export_keys"/>
+
+</menu> \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/res/menu/key_list_secret_multi.xml b/OpenPGP-Keychain/src/main/res/menu/key_list_secret_multi.xml
new file mode 100644
index 000000000..8cd181e16
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/menu/key_list_secret_multi.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <item
+ android:id="@+id/menu_key_list_public_multi_delete"
+ android:icon="@drawable/ic_action_discard"
+ android:title="@string/menu_delete_key"/>
+
+</menu> \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/res/menu/key_view.xml b/OpenPGP-Keychain/src/main/res/menu/key_view.xml
new file mode 100644
index 000000000..6d7a06ece
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/menu/key_view.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <item
+ android:id="@+id/menu_key_view_share"
+ android:icon="@drawable/ic_action_share"
+ android:showAsAction="always"
+ android:title="@string/menu_share">
+ <menu>
+ <item
+ android:id="@+id/menu_key_view_share_fingerprint_title"
+ android:showAsAction="never"
+ android:title="@string/menu_share_title_fingerprint">
+ <menu>
+ <item
+ android:id="@+id/menu_key_view_share_default_fingerprint"
+ android:showAsAction="never"
+ android:title="@string/menu_share_default_fingerprint"/>
+ <item
+ android:id="@+id/menu_key_view_share_qr_code_fingerprint"
+ android:showAsAction="never"
+ android:title="@string/menu_share_qr_code_fingerprint"/>
+ </menu>
+ </item>
+ <item
+ android:id="@+id/menu_key_view_share_title"
+ android:showAsAction="never"
+ android:title="@string/menu_share_title">
+ <menu>
+ <item
+ android:id="@+id/menu_key_view_share_default"
+ android:showAsAction="never"
+ android:title="@string/menu_share_default"/>
+ <item
+ android:id="@+id/menu_key_view_share_qr_code"
+ android:showAsAction="never"
+ android:title="@string/menu_share_qr_code"/>
+ <item
+ android:id="@+id/menu_key_view_share_nfc"
+ android:showAsAction="never"
+ android:title="@string/menu_share_nfc"/>
+ <item
+ android:id="@+id/menu_key_view_share_clipboard"
+ android: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_import_export"
+ android:showAsAction="always"
+ android:title="@string/menu_share">
+ <menu>
+ <item
+ android:id="@+id/menu_key_view_update"
+ android:showAsAction="never"
+ android:title="@string/menu_update_key"/>
+ <item
+ android:id="@+id/menu_key_view_export_keyserver"
+ android:showAsAction="never"
+ android:title="@string/menu_export_key_to_server"/>
+ </menu>
+ </item>
+ <item
+ android:id="@+id/menu_key_view_sign"
+ android:showAsAction="ifRoom"
+ android:title="@string/menu_sign_key"/>
+ <item
+ android:id="@+id/menu_key_view_export_file"
+ android:showAsAction="never"
+ android:title="@string/menu_export_key"/>
+ <item
+ android:id="@+id/menu_key_view_delete"
+ android:showAsAction="never"
+ android:title="@string/menu_delete_key"/>
+
+</menu> \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/res/raw-de/help_about.html b/OpenPGP-Keychain/src/main/res/raw-de/help_about.html
new file mode 100644
index 000000000..03d8289c6
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/raw-de/help_about.html
@@ -0,0 +1,41 @@
+<html>
+<head></head>
+<body>
+<p><a href="http://sufficientlysecure.org/keychain">http://sufficientlysecure.org/keychain</a></p>
+<p><a href="http://sufficientlysecure.org/keychain">OpenPGP Keychain</a> is an OpenPGP implementation for Android. The development began as a fork of Android Privacy Guard (APG).</p>
+<p>License: GPLv3+</p>
+
+<h2>Entwickler OpenPGP Keychain</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>
+
+</ul>
+<h2>Developers APG 1.x</h2>
+<ul>
+<li>'Thialfihar' (Leitender Entwickler)</li>
+<li>'Senecaso' (QRCode, sign key, upload key)</li>
+<li>Oliver Runge</li>
+<li>Markus Doits</li>
+</ul>
+<h2>Libraries</h2>
+<ul>
+<li>
+<a href="http://actionbarsherlock.com">ActionBarSherlock</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>Icons from <a href="http://rrze-icon-set.berlios.de/">RRZE Icon Set</a> (Creative Commons Attribution Share-Alike licence 3.0)</li>
+<li>Icons from <a href="http://tango.freedesktop.org/">Tango Icon Set</a> (Public Domain)</li>
+</ul>
+</body>
+</html>
diff --git a/OpenPGP-Keychain/src/main/res/raw-de/help_changelog.html b/OpenPGP-Keychain/src/main/res/raw-de/help_changelog.html
new file mode 100644
index 000000000..b588dff08
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/raw-de/help_changelog.html
@@ -0,0 +1,96 @@
+<html>
+<head></head>
+<body>
+<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, Vorbereitung für k9mail Integration</li>
+</ul>
+<h2>2.1</h2>
+<ul>
+<li>Viele Fehlerbehebungen</li>
+<li>Neue API für Entwickler</li>
+<li>PRNG bug fix by Google</li>
+</ul>
+<h2>2.0</h2>
+<ul>
+<li>Komplette Neugestaltung</li>
+<li>Share public keys via qr codes, nfc beam</li>
+<li>SChlüssel signieren</li>
+<li>Zum Schlüsselserver hochladen</li>
+<li>Imprtproblem behoben</li>
+<li>New AIDL API</li>
+</ul>
+<h2>1.0.8</h2>
+<ul>
+<li>basic key server support</li>
+<li>app2sd (untested, let me know if there are problems)</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>Fehlerbehebungen</li>
+<li>Optimierungen</li>
+</ul>
+<h2>1.0.7</h2>
+<ul>
+<li>clear sign problem with lacking trailing newline fixed</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>Fehlerbehebungen</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 preselection 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 k9, 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>k9mail integration, APG supporting beta build of k9mail</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>Fehlerbehebungen</li>
+</ul>
+</body>
+</html>
diff --git a/OpenPGP-Keychain/src/main/res/raw-de/help_nfc_beam.html b/OpenPGP-Keychain/src/main/res/raw-de/help_nfc_beam.html
new file mode 100644
index 000000000..2e7e637e5
--- /dev/null
+++ b/OpenPGP-Keychain/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 'Manage Public Keys' and long press on the key 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/OpenPGP-Keychain/src/main/res/raw-de/help_start.html b/OpenPGP-Keychain/src/main/res/raw-de/help_start.html
new file mode 100644
index 000000000..1386e8cc1
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/raw-de/help_start.html
@@ -0,0 +1,22 @@
+<html>
+<head></head>
+<body>
+<h2>EXPERIMENTAL software</h2>
+<p>This is EXPERIMENTAL software. Use at your own risk!</p>
+
+<h2>Getting started</h2>
+<p>First you need some keys. Import or create them via the option menus in "My Secret Keys".<!--<p>Install K-9 Mail for the best integration, it will supports OpenPGP Keychain for PGP/INLINE and lets you directly encrypt/decrypt emails.-->
+<br>It is recommended that you install OI File Manager to be able to use the browse button for file selection in OpenPGP Keychain.</p>
+
+<h2>Big ToDos</h2>
+<ul>
+<li>K9 Mail integration not published</li>
+<li>Importing existing keys will be stripped of certificates right now</li>
+<li>PGP/MIME in K9 Mail is missing</li>
+</ul>
+<p>If you want to contribute, fork it and do a pull request on Github: <a href="https://github.com/dschuermann/openpgp-keychain">https://github.com/dschuermann/openpgp-keychain</a></p>
+
+<h2>I found a bug in OpenPGP Keychain!</h2>
+<p>Please report it in the <a href="https://github.com/dschuermann/openpgp-keychain/issues">issue tracker of OpenPGP Keychain</a>.</p>
+</body>
+</html>
diff --git a/OpenPGP-Keychain/src/main/res/raw-de/nfc_beam_share.html b/OpenPGP-Keychain/src/main/res/raw-de/nfc_beam_share.html
new file mode 100644
index 000000000..453d435e3
--- /dev/null
+++ b/OpenPGP-Keychain/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/OpenPGP-Keychain/src/main/res/raw-es-rCO/help_about.html b/OpenPGP-Keychain/src/main/res/raw-es-rCO/help_about.html
new file mode 100644
index 000000000..7db2f83de
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/raw-es-rCO/help_about.html
@@ -0,0 +1,41 @@
+<html>
+<head></head>
+<body>
+<p><a href="http://sufficientlysecure.org/keychain">http://sufficientlysecure.org/keychain</a></p>
+<p><a href="http://sufficientlysecure.org/keychain">OpenPGP Keychain</a> is an OpenPGP implementation for Android. The development began as a fork of Android Privacy Guard (APG).</p>
+<p>License: GPLv3+</p>
+
+<h2>Developers OpenPGP Keychain</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>
+
+</ul>
+<h2>Developers APG 1.x</h2>
+<ul>
+<li>'Thialfihar' (Lead developer)</li>
+<li>'Senecaso' (QRCode, sign key, upload key)</li>
+<li>Oliver Runge</li>
+<li>Markus Doits</li>
+</ul>
+<h2>Libraries</h2>
+<ul>
+<li>
+<a href="http://actionbarsherlock.com">ActionBarSherlock</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>Icons from <a href="http://rrze-icon-set.berlios.de/">RRZE Icon Set</a> (Creative Commons Attribution Share-Alike licence 3.0)</li>
+<li>Icons from <a href="http://tango.freedesktop.org/">Tango Icon Set</a> (Public Domain)</li>
+</ul>
+</body>
+</html>
diff --git a/OpenPGP-Keychain/src/main/res/raw-es-rCO/help_changelog.html b/OpenPGP-Keychain/src/main/res/raw-es-rCO/help_changelog.html
new file mode 100644
index 000000000..433b3c343
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/raw-es-rCO/help_changelog.html
@@ -0,0 +1,96 @@
+<html>
+<head></head>
+<body>
+<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 k9mail 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 key server support</li>
+<li>app2sd (untested, let me know if there are problems)</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>clear sign problem with lacking trailing newline fixed</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 preselection 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 k9, 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>k9mail integration, APG supporting beta build of k9mail</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/OpenPGP-Keychain/src/main/res/raw-es-rCO/help_nfc_beam.html b/OpenPGP-Keychain/src/main/res/raw-es-rCO/help_nfc_beam.html
new file mode 100644
index 000000000..2e7e637e5
--- /dev/null
+++ b/OpenPGP-Keychain/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 'Manage Public Keys' and long press on the key 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/OpenPGP-Keychain/src/main/res/raw-es-rCO/help_start.html b/OpenPGP-Keychain/src/main/res/raw-es-rCO/help_start.html
new file mode 100644
index 000000000..1386e8cc1
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/raw-es-rCO/help_start.html
@@ -0,0 +1,22 @@
+<html>
+<head></head>
+<body>
+<h2>EXPERIMENTAL software</h2>
+<p>This is EXPERIMENTAL software. Use at your own risk!</p>
+
+<h2>Getting started</h2>
+<p>First you need some keys. Import or create them via the option menus in "My Secret Keys".<!--<p>Install K-9 Mail for the best integration, it will supports OpenPGP Keychain for PGP/INLINE and lets you directly encrypt/decrypt emails.-->
+<br>It is recommended that you install OI File Manager to be able to use the browse button for file selection in OpenPGP Keychain.</p>
+
+<h2>Big ToDos</h2>
+<ul>
+<li>K9 Mail integration not published</li>
+<li>Importing existing keys will be stripped of certificates right now</li>
+<li>PGP/MIME in K9 Mail is missing</li>
+</ul>
+<p>If you want to contribute, fork it and do a pull request on Github: <a href="https://github.com/dschuermann/openpgp-keychain">https://github.com/dschuermann/openpgp-keychain</a></p>
+
+<h2>I found a bug in OpenPGP Keychain!</h2>
+<p>Please report it in the <a href="https://github.com/dschuermann/openpgp-keychain/issues">issue tracker of OpenPGP Keychain</a>.</p>
+</body>
+</html>
diff --git a/OpenPGP-Keychain/src/main/res/raw-es-rCO/nfc_beam_share.html b/OpenPGP-Keychain/src/main/res/raw-es-rCO/nfc_beam_share.html
new file mode 100644
index 000000000..453d435e3
--- /dev/null
+++ b/OpenPGP-Keychain/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/OpenPGP-Keychain/src/main/res/raw-fr/help_about.html b/OpenPGP-Keychain/src/main/res/raw-fr/help_about.html
new file mode 100644
index 000000000..6bede7bd6
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/raw-fr/help_about.html
@@ -0,0 +1,41 @@
+<html>
+<head></head>
+<body>
+<p><a href="http://sufficientlysecure.org/keychain">http://sufficientlysecure.org/keychain</a></p>
+<p><a href="http://sufficientlysecure.org/keychain">Le porte-clefs OpenPGP</a> est une implémentation d'OpenPGP pour Android. Le développement a commencé comme bifurcation d'Android Privacy Guard (APG).</p>
+<p>Licence : GPLv3+</p>
+
+<h2>Les développeurs du Porte-clefs OpenPGP</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>
+
+</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>Oliver Runge</li>
+<li>Markus Doits</li>
+</ul>
+<h2>Bibliothèques</h2>
+<ul>
+<li>
+<a href="http://actionbarsherlock.com">ActionBarSherlock</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>Icônes du <a href="http://rrze-icon-set.berlios.de/">jeu d'icônes RRZE</a> (Licence Creative Commons Paternité - Partage des Conditions Initiales à l'Identique 3.0)</li>
+<li>Icônes du <a href="http://tango.freedesktop.org/">jeu d'icônes Tango</a> (domaine public)</li>
+</ul>
+</body>
+</html>
diff --git a/OpenPGP-Keychain/src/main/res/raw-fr/help_changelog.html b/OpenPGP-Keychain/src/main/res/raw-fr/help_changelog.html
new file mode 100644
index 000000000..3b545fc20
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/raw-fr/help_changelog.html
@@ -0,0 +1,96 @@
+<html>
+<head></head>
+<body>
+<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 publiques</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, NFC Beam</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 (non-testé, me signaler tout problème)</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>clear sign problem with lacking trailing newline fixed</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, 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/OpenPGP-Keychain/src/main/res/raw-fr/help_nfc_beam.html b/OpenPGP-Keychain/src/main/res/raw-fr/help_nfc_beam.html
new file mode 100644
index 000000000..6d6e7d693
--- /dev/null
+++ b/OpenPGP-Keychain/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>Aller à la « Gestion des clefs publiques » de votre partenaire et appuyer longuement sur la clef 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/OpenPGP-Keychain/src/main/res/raw-fr/help_start.html b/OpenPGP-Keychain/src/main/res/raw-fr/help_start.html
new file mode 100644
index 000000000..bec69318b
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/raw-fr/help_start.html
@@ -0,0 +1,22 @@
+<html>
+<head></head>
+<body>
+<h2>Logiciel EXPÉRIMENTAL</h2>
+<p>Ce logiciel est EXPÉRIMENTAL. À utiliser à vos propres risques !</p>
+
+<h2>Commencer</h2>
+<p>Il vous faut d'abord des clefs. Importez ou créez-les depuis le menu des options de « Mes clefs secrètes ».<!--<p>Install K-9 Mail for the best integration, it will supports OpenPGP Keychain for PGP/INLINE and lets you directly encrypt/decrypt emails.-->
+<br>Il est recommandé que vous installiez le Gestionnaire de fichiers OI afin de pouvoir utiliser le bouton Parcourir pour choisir des fichiers depuis le Porte-clefs OpenPGP.</p>
+
+<h2>Les gros morceaux à faire</h2>
+<ul>
+<li>L'intégration à K-9 Mail n'est pas publiée</li>
+<li>L'importation de clefs existantes sera dépouillé de certificats pour l'instant</li>
+<li>PGP/MIME est manquant dans K-9 Mail</li>
+</ul>
+<p>Si vous voulez contribuer, bifurquer et faire une demande d'extraction sur Github : <a href="https://github.com/dschuermann/openpgp-keychain">https://github.com/dschuermann/openpgp-keychain</a></p>
+
+<h2>J'ai trouvé un bogue dans le Porte-clefs OpenPGP !</h2>
+<p>Veuillez le rapporter avec le <a href="https://github.com/dschuermann/openpgp-keychain/issues">gestionnaire de bogues du Porte-clefs OpenPGP</a>.</p>
+</body>
+</html>
diff --git a/OpenPGP-Keychain/src/main/res/raw-fr/nfc_beam_share.html b/OpenPGP-Keychain/src/main/res/raw-fr/nfc_beam_share.html
new file mode 100644
index 000000000..b2592fd8e
--- /dev/null
+++ b/OpenPGP-Keychain/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 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/OpenPGP-Keychain/src/main/res/raw-it-rIT/help_about.html b/OpenPGP-Keychain/src/main/res/raw-it-rIT/help_about.html
new file mode 100644
index 000000000..7db2f83de
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/raw-it-rIT/help_about.html
@@ -0,0 +1,41 @@
+<html>
+<head></head>
+<body>
+<p><a href="http://sufficientlysecure.org/keychain">http://sufficientlysecure.org/keychain</a></p>
+<p><a href="http://sufficientlysecure.org/keychain">OpenPGP Keychain</a> is an OpenPGP implementation for Android. The development began as a fork of Android Privacy Guard (APG).</p>
+<p>License: GPLv3+</p>
+
+<h2>Developers OpenPGP Keychain</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>
+
+</ul>
+<h2>Developers APG 1.x</h2>
+<ul>
+<li>'Thialfihar' (Lead developer)</li>
+<li>'Senecaso' (QRCode, sign key, upload key)</li>
+<li>Oliver Runge</li>
+<li>Markus Doits</li>
+</ul>
+<h2>Libraries</h2>
+<ul>
+<li>
+<a href="http://actionbarsherlock.com">ActionBarSherlock</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>Icons from <a href="http://rrze-icon-set.berlios.de/">RRZE Icon Set</a> (Creative Commons Attribution Share-Alike licence 3.0)</li>
+<li>Icons from <a href="http://tango.freedesktop.org/">Tango Icon Set</a> (Public Domain)</li>
+</ul>
+</body>
+</html>
diff --git a/OpenPGP-Keychain/src/main/res/raw-it-rIT/help_changelog.html b/OpenPGP-Keychain/src/main/res/raw-it-rIT/help_changelog.html
new file mode 100644
index 000000000..433b3c343
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/raw-it-rIT/help_changelog.html
@@ -0,0 +1,96 @@
+<html>
+<head></head>
+<body>
+<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 k9mail 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 key server support</li>
+<li>app2sd (untested, let me know if there are problems)</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>clear sign problem with lacking trailing newline fixed</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 preselection 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 k9, 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>k9mail integration, APG supporting beta build of k9mail</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/OpenPGP-Keychain/src/main/res/raw-it-rIT/help_nfc_beam.html b/OpenPGP-Keychain/src/main/res/raw-it-rIT/help_nfc_beam.html
new file mode 100644
index 000000000..2e7e637e5
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/raw-it-rIT/help_nfc_beam.html
@@ -0,0 +1,12 @@
+<html>
+<head></head>
+<body>
+<h2>How to receive keys</h2>
+<ol>
+<li>Go to your partners 'Manage Public Keys' and long press on the key 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/OpenPGP-Keychain/src/main/res/raw-it-rIT/help_start.html b/OpenPGP-Keychain/src/main/res/raw-it-rIT/help_start.html
new file mode 100644
index 000000000..1386e8cc1
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/raw-it-rIT/help_start.html
@@ -0,0 +1,22 @@
+<html>
+<head></head>
+<body>
+<h2>EXPERIMENTAL software</h2>
+<p>This is EXPERIMENTAL software. Use at your own risk!</p>
+
+<h2>Getting started</h2>
+<p>First you need some keys. Import or create them via the option menus in "My Secret Keys".<!--<p>Install K-9 Mail for the best integration, it will supports OpenPGP Keychain for PGP/INLINE and lets you directly encrypt/decrypt emails.-->
+<br>It is recommended that you install OI File Manager to be able to use the browse button for file selection in OpenPGP Keychain.</p>
+
+<h2>Big ToDos</h2>
+<ul>
+<li>K9 Mail integration not published</li>
+<li>Importing existing keys will be stripped of certificates right now</li>
+<li>PGP/MIME in K9 Mail is missing</li>
+</ul>
+<p>If you want to contribute, fork it and do a pull request on Github: <a href="https://github.com/dschuermann/openpgp-keychain">https://github.com/dschuermann/openpgp-keychain</a></p>
+
+<h2>I found a bug in OpenPGP Keychain!</h2>
+<p>Please report it in the <a href="https://github.com/dschuermann/openpgp-keychain/issues">issue tracker of OpenPGP Keychain</a>.</p>
+</body>
+</html>
diff --git a/OpenPGP-Keychain/src/main/res/raw-it-rIT/nfc_beam_share.html b/OpenPGP-Keychain/src/main/res/raw-it-rIT/nfc_beam_share.html
new file mode 100644
index 000000000..453d435e3
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/raw-it-rIT/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/OpenPGP-Keychain/src/main/res/raw-nl-rNL/help_about.html b/OpenPGP-Keychain/src/main/res/raw-nl-rNL/help_about.html
new file mode 100644
index 000000000..7db2f83de
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/raw-nl-rNL/help_about.html
@@ -0,0 +1,41 @@
+<html>
+<head></head>
+<body>
+<p><a href="http://sufficientlysecure.org/keychain">http://sufficientlysecure.org/keychain</a></p>
+<p><a href="http://sufficientlysecure.org/keychain">OpenPGP Keychain</a> is an OpenPGP implementation for Android. The development began as a fork of Android Privacy Guard (APG).</p>
+<p>License: GPLv3+</p>
+
+<h2>Developers OpenPGP Keychain</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>
+
+</ul>
+<h2>Developers APG 1.x</h2>
+<ul>
+<li>'Thialfihar' (Lead developer)</li>
+<li>'Senecaso' (QRCode, sign key, upload key)</li>
+<li>Oliver Runge</li>
+<li>Markus Doits</li>
+</ul>
+<h2>Libraries</h2>
+<ul>
+<li>
+<a href="http://actionbarsherlock.com">ActionBarSherlock</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>Icons from <a href="http://rrze-icon-set.berlios.de/">RRZE Icon Set</a> (Creative Commons Attribution Share-Alike licence 3.0)</li>
+<li>Icons from <a href="http://tango.freedesktop.org/">Tango Icon Set</a> (Public Domain)</li>
+</ul>
+</body>
+</html>
diff --git a/OpenPGP-Keychain/src/main/res/raw-nl-rNL/help_changelog.html b/OpenPGP-Keychain/src/main/res/raw-nl-rNL/help_changelog.html
new file mode 100644
index 000000000..433b3c343
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/raw-nl-rNL/help_changelog.html
@@ -0,0 +1,96 @@
+<html>
+<head></head>
+<body>
+<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 k9mail 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 key server support</li>
+<li>app2sd (untested, let me know if there are problems)</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>clear sign problem with lacking trailing newline fixed</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 preselection 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 k9, 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>k9mail integration, APG supporting beta build of k9mail</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/OpenPGP-Keychain/src/main/res/raw-nl-rNL/help_nfc_beam.html b/OpenPGP-Keychain/src/main/res/raw-nl-rNL/help_nfc_beam.html
new file mode 100644
index 000000000..2e7e637e5
--- /dev/null
+++ b/OpenPGP-Keychain/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 'Manage Public Keys' and long press on the key 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/OpenPGP-Keychain/src/main/res/raw-nl-rNL/help_start.html b/OpenPGP-Keychain/src/main/res/raw-nl-rNL/help_start.html
new file mode 100644
index 000000000..1386e8cc1
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/raw-nl-rNL/help_start.html
@@ -0,0 +1,22 @@
+<html>
+<head></head>
+<body>
+<h2>EXPERIMENTAL software</h2>
+<p>This is EXPERIMENTAL software. Use at your own risk!</p>
+
+<h2>Getting started</h2>
+<p>First you need some keys. Import or create them via the option menus in "My Secret Keys".<!--<p>Install K-9 Mail for the best integration, it will supports OpenPGP Keychain for PGP/INLINE and lets you directly encrypt/decrypt emails.-->
+<br>It is recommended that you install OI File Manager to be able to use the browse button for file selection in OpenPGP Keychain.</p>
+
+<h2>Big ToDos</h2>
+<ul>
+<li>K9 Mail integration not published</li>
+<li>Importing existing keys will be stripped of certificates right now</li>
+<li>PGP/MIME in K9 Mail is missing</li>
+</ul>
+<p>If you want to contribute, fork it and do a pull request on Github: <a href="https://github.com/dschuermann/openpgp-keychain">https://github.com/dschuermann/openpgp-keychain</a></p>
+
+<h2>I found a bug in OpenPGP Keychain!</h2>
+<p>Please report it in the <a href="https://github.com/dschuermann/openpgp-keychain/issues">issue tracker of OpenPGP Keychain</a>.</p>
+</body>
+</html>
diff --git a/OpenPGP-Keychain/src/main/res/raw-nl-rNL/nfc_beam_share.html b/OpenPGP-Keychain/src/main/res/raw-nl-rNL/nfc_beam_share.html
new file mode 100644
index 000000000..453d435e3
--- /dev/null
+++ b/OpenPGP-Keychain/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/OpenPGP-Keychain/src/main/res/raw-pt-rBR/help_about.html b/OpenPGP-Keychain/src/main/res/raw-pt-rBR/help_about.html
new file mode 100644
index 000000000..7db2f83de
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/raw-pt-rBR/help_about.html
@@ -0,0 +1,41 @@
+<html>
+<head></head>
+<body>
+<p><a href="http://sufficientlysecure.org/keychain">http://sufficientlysecure.org/keychain</a></p>
+<p><a href="http://sufficientlysecure.org/keychain">OpenPGP Keychain</a> is an OpenPGP implementation for Android. The development began as a fork of Android Privacy Guard (APG).</p>
+<p>License: GPLv3+</p>
+
+<h2>Developers OpenPGP Keychain</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>
+
+</ul>
+<h2>Developers APG 1.x</h2>
+<ul>
+<li>'Thialfihar' (Lead developer)</li>
+<li>'Senecaso' (QRCode, sign key, upload key)</li>
+<li>Oliver Runge</li>
+<li>Markus Doits</li>
+</ul>
+<h2>Libraries</h2>
+<ul>
+<li>
+<a href="http://actionbarsherlock.com">ActionBarSherlock</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>Icons from <a href="http://rrze-icon-set.berlios.de/">RRZE Icon Set</a> (Creative Commons Attribution Share-Alike licence 3.0)</li>
+<li>Icons from <a href="http://tango.freedesktop.org/">Tango Icon Set</a> (Public Domain)</li>
+</ul>
+</body>
+</html>
diff --git a/OpenPGP-Keychain/src/main/res/raw-pt-rBR/help_changelog.html b/OpenPGP-Keychain/src/main/res/raw-pt-rBR/help_changelog.html
new file mode 100644
index 000000000..433b3c343
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/raw-pt-rBR/help_changelog.html
@@ -0,0 +1,96 @@
+<html>
+<head></head>
+<body>
+<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 k9mail 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 key server support</li>
+<li>app2sd (untested, let me know if there are problems)</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>clear sign problem with lacking trailing newline fixed</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 preselection 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 k9, 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>k9mail integration, APG supporting beta build of k9mail</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/OpenPGP-Keychain/src/main/res/raw-pt-rBR/help_nfc_beam.html b/OpenPGP-Keychain/src/main/res/raw-pt-rBR/help_nfc_beam.html
new file mode 100644
index 000000000..2e7e637e5
--- /dev/null
+++ b/OpenPGP-Keychain/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 'Manage Public Keys' and long press on the key 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/OpenPGP-Keychain/src/main/res/raw-pt-rBR/help_start.html b/OpenPGP-Keychain/src/main/res/raw-pt-rBR/help_start.html
new file mode 100644
index 000000000..1386e8cc1
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/raw-pt-rBR/help_start.html
@@ -0,0 +1,22 @@
+<html>
+<head></head>
+<body>
+<h2>EXPERIMENTAL software</h2>
+<p>This is EXPERIMENTAL software. Use at your own risk!</p>
+
+<h2>Getting started</h2>
+<p>First you need some keys. Import or create them via the option menus in "My Secret Keys".<!--<p>Install K-9 Mail for the best integration, it will supports OpenPGP Keychain for PGP/INLINE and lets you directly encrypt/decrypt emails.-->
+<br>It is recommended that you install OI File Manager to be able to use the browse button for file selection in OpenPGP Keychain.</p>
+
+<h2>Big ToDos</h2>
+<ul>
+<li>K9 Mail integration not published</li>
+<li>Importing existing keys will be stripped of certificates right now</li>
+<li>PGP/MIME in K9 Mail is missing</li>
+</ul>
+<p>If you want to contribute, fork it and do a pull request on Github: <a href="https://github.com/dschuermann/openpgp-keychain">https://github.com/dschuermann/openpgp-keychain</a></p>
+
+<h2>I found a bug in OpenPGP Keychain!</h2>
+<p>Please report it in the <a href="https://github.com/dschuermann/openpgp-keychain/issues">issue tracker of OpenPGP Keychain</a>.</p>
+</body>
+</html>
diff --git a/OpenPGP-Keychain/src/main/res/raw-pt-rBR/nfc_beam_share.html b/OpenPGP-Keychain/src/main/res/raw-pt-rBR/nfc_beam_share.html
new file mode 100644
index 000000000..453d435e3
--- /dev/null
+++ b/OpenPGP-Keychain/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/OpenPGP-Keychain/src/main/res/raw-ru/help_about.html b/OpenPGP-Keychain/src/main/res/raw-ru/help_about.html
new file mode 100644
index 000000000..be687d431
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/raw-ru/help_about.html
@@ -0,0 +1,41 @@
+<html>
+<head></head>
+<body>
+<p><a href="http://sufficientlysecure.org/keychain">http://sufficientlysecure.org/keychain</a></p>
+<p><a href="http://sufficientlysecure.org/keychain">OpenPGP Keychain</a> - свободная реализация OpenPGP для Android. Разработка начиналась как ответвление Android Privacy Guard (APG).</p>
+<p>Лицензия: GPLv3+</p>
+
+<h2>Разработчики OpenPGP Keychain</h2>
+<ul>
+<li>Dominik Schürmann (главный разработчик)</li>
+<li>Ash Hughes (патчи криптографии)</li>
+<li>Brian C. Barnes</li>
+<li>Bahtiar 'kalkin' Gadimov (UI)</li>
+
+</ul>
+<h2>Разработчики APG 1.x</h2>
+<ul>
+<li>'Thialfihar' (главный разработчик)</li>
+<li>'Senecaso' (QR коды, подписание и загрузка ключей)</li>
+<li>Oliver Runge</li>
+<li>Markus Doits</li>
+</ul>
+<h2>Используемые библиотеки</h2>
+<ul>
+<li>
+<a href="http://actionbarsherlock.com">ActionBarSherlock</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>Icons from <a href="http://rrze-icon-set.berlios.de/">RRZE Icon Set</a> (Creative Commons Attribution Share-Alike licence 3.0)</li>
+<li>Icons from <a href="http://tango.freedesktop.org/">Tango Icon Set</a> (Public Domain)</li>
+</ul>
+</body>
+</html>
diff --git a/OpenPGP-Keychain/src/main/res/raw-ru/help_changelog.html b/OpenPGP-Keychain/src/main/res/raw-ru/help_changelog.html
new file mode 100644
index 000000000..1f306d330
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/raw-ru/help_changelog.html
@@ -0,0 +1,96 @@
+<html>
+<head></head>
+<body>
+<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, подготовка к интеграции с k9mail</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>исправления ошибок и новые возможности для интеграции с k9</li>
+</ul>
+<h2>1.0.1</h2>
+<ul>
+<li>исправление выбора учетной записи GMail, сломанного в 1.0.0</li>
+</ul>
+<h2>1.0.0</h2>
+<ul>
+<li>интеграция с k9mail, APG поддерживает beta-версию k9mail</li>
+<li>поддержка сторонних файловых менеджеров (в т.ч. ASTRO)</li>
+<li>Словенский перевод</li>
+<li>новая база данных, еще быстрее и компактнее</li>
+<li>добавлены обработчики для взаимодействия с другими приложениями</li>
+<li>исправления ошибок</li>
+</ul>
+</body>
+</html>
diff --git a/OpenPGP-Keychain/src/main/res/raw-ru/help_nfc_beam.html b/OpenPGP-Keychain/src/main/res/raw-ru/help_nfc_beam.html
new file mode 100644
index 000000000..fcf55cdaf
--- /dev/null
+++ b/OpenPGP-Keychain/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/OpenPGP-Keychain/src/main/res/raw-ru/help_start.html b/OpenPGP-Keychain/src/main/res/raw-ru/help_start.html
new file mode 100644
index 000000000..d55f3296b
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/raw-ru/help_start.html
@@ -0,0 +1,22 @@
+<html>
+<head></head>
+<body>
+<h2>ЭКСПЕРИМЕНТАЛЬНАЯ программа</h2>
+<p>Это ЭКСПЕРИМЕНТАЛЬНАЯ версия. Используйте на свой страх и риск!</p>
+
+<h2>Приступая</h2>
+<p>Для начала Вам потребуются ключи. Воспользуйтесь функцией Импорт в меню раздела "Мои ключи".<!--<p>Install K-9 Mail for the best integration, it will supports OpenPGP Keychain for PGP/INLINE and lets you directly encrypt/decrypt emails.-->
+<br>Для удобства выбора файлов рекомендуется установить OI File Manager. Он будет вызываться при нажатии кнопки выбора файла.</p>
+
+<h2>Над чем еще ведётся работа</h2>
+<ul>
+<li>Интеграция с почтовой программой K9 Mail.</li>
+<li>При импорте сущестующих ключей теряются подписи</li>
+<li>Отсутсвует поддержка PGP/MIME в K9 Mail</li>
+</ul>
+<p>Если вы ходите принять участие в доработке и развитии проекта, посетите страницу проекта на Github: <a href="https://github.com/dschuermann/openpgp-keychain">https://github.com/dschuermann/openpgp-keychain</a></p>
+
+<h2>Я нашел ошибку в OpenPGP Keychain!</h2>
+<p>Пожалуйста, сообщите о ней в разделе '<a href="https://github.com/dschuermann/openpgp-keychain/issues">Проблемы с OpenPGP Keychain</a>'.</p>
+</body>
+</html>
diff --git a/OpenPGP-Keychain/src/main/res/raw-ru/nfc_beam_share.html b/OpenPGP-Keychain/src/main/res/raw-ru/nfc_beam_share.html
new file mode 100644
index 000000000..584353da4
--- /dev/null
+++ b/OpenPGP-Keychain/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/OpenPGP-Keychain/src/main/res/raw-sl-rSI/help_about.html b/OpenPGP-Keychain/src/main/res/raw-sl-rSI/help_about.html
new file mode 100644
index 000000000..7db2f83de
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/raw-sl-rSI/help_about.html
@@ -0,0 +1,41 @@
+<html>
+<head></head>
+<body>
+<p><a href="http://sufficientlysecure.org/keychain">http://sufficientlysecure.org/keychain</a></p>
+<p><a href="http://sufficientlysecure.org/keychain">OpenPGP Keychain</a> is an OpenPGP implementation for Android. The development began as a fork of Android Privacy Guard (APG).</p>
+<p>License: GPLv3+</p>
+
+<h2>Developers OpenPGP Keychain</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>
+
+</ul>
+<h2>Developers APG 1.x</h2>
+<ul>
+<li>'Thialfihar' (Lead developer)</li>
+<li>'Senecaso' (QRCode, sign key, upload key)</li>
+<li>Oliver Runge</li>
+<li>Markus Doits</li>
+</ul>
+<h2>Libraries</h2>
+<ul>
+<li>
+<a href="http://actionbarsherlock.com">ActionBarSherlock</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>Icons from <a href="http://rrze-icon-set.berlios.de/">RRZE Icon Set</a> (Creative Commons Attribution Share-Alike licence 3.0)</li>
+<li>Icons from <a href="http://tango.freedesktop.org/">Tango Icon Set</a> (Public Domain)</li>
+</ul>
+</body>
+</html>
diff --git a/OpenPGP-Keychain/src/main/res/raw-sl-rSI/help_changelog.html b/OpenPGP-Keychain/src/main/res/raw-sl-rSI/help_changelog.html
new file mode 100644
index 000000000..433b3c343
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/raw-sl-rSI/help_changelog.html
@@ -0,0 +1,96 @@
+<html>
+<head></head>
+<body>
+<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 k9mail 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 key server support</li>
+<li>app2sd (untested, let me know if there are problems)</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>clear sign problem with lacking trailing newline fixed</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 preselection 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 k9, 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>k9mail integration, APG supporting beta build of k9mail</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/OpenPGP-Keychain/src/main/res/raw-sl-rSI/help_nfc_beam.html b/OpenPGP-Keychain/src/main/res/raw-sl-rSI/help_nfc_beam.html
new file mode 100644
index 000000000..2e7e637e5
--- /dev/null
+++ b/OpenPGP-Keychain/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 'Manage Public Keys' and long press on the key 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/OpenPGP-Keychain/src/main/res/raw-sl-rSI/help_start.html b/OpenPGP-Keychain/src/main/res/raw-sl-rSI/help_start.html
new file mode 100644
index 000000000..1386e8cc1
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/raw-sl-rSI/help_start.html
@@ -0,0 +1,22 @@
+<html>
+<head></head>
+<body>
+<h2>EXPERIMENTAL software</h2>
+<p>This is EXPERIMENTAL software. Use at your own risk!</p>
+
+<h2>Getting started</h2>
+<p>First you need some keys. Import or create them via the option menus in "My Secret Keys".<!--<p>Install K-9 Mail for the best integration, it will supports OpenPGP Keychain for PGP/INLINE and lets you directly encrypt/decrypt emails.-->
+<br>It is recommended that you install OI File Manager to be able to use the browse button for file selection in OpenPGP Keychain.</p>
+
+<h2>Big ToDos</h2>
+<ul>
+<li>K9 Mail integration not published</li>
+<li>Importing existing keys will be stripped of certificates right now</li>
+<li>PGP/MIME in K9 Mail is missing</li>
+</ul>
+<p>If you want to contribute, fork it and do a pull request on Github: <a href="https://github.com/dschuermann/openpgp-keychain">https://github.com/dschuermann/openpgp-keychain</a></p>
+
+<h2>I found a bug in OpenPGP Keychain!</h2>
+<p>Please report it in the <a href="https://github.com/dschuermann/openpgp-keychain/issues">issue tracker of OpenPGP Keychain</a>.</p>
+</body>
+</html>
diff --git a/OpenPGP-Keychain/src/main/res/raw-sl-rSI/nfc_beam_share.html b/OpenPGP-Keychain/src/main/res/raw-sl-rSI/nfc_beam_share.html
new file mode 100644
index 000000000..453d435e3
--- /dev/null
+++ b/OpenPGP-Keychain/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/OpenPGP-Keychain/src/main/res/raw-tr/help_about.html b/OpenPGP-Keychain/src/main/res/raw-tr/help_about.html
new file mode 100644
index 000000000..3d4549cbd
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/raw-tr/help_about.html
@@ -0,0 +1,41 @@
+<html>
+<head></head>
+<body>
+<p><a href="http://sufficientlysecure.org/keychain">http://sufficientlysecure.org/keychain</a></p>
+<p><a href="http://sufficientlysecure.org/keychain">OpenPGP Keychain</a> Android için bir OpenPGP uygulamasıdır. </p>
+<p>Lisans: GPLv3+</p>
+
+<h2>Geliştiriciler OpenPGP Keychain</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>
+
+</ul>
+<h2>Geliştiriciler APG 1.x</h2>
+<ul>
+<li>'Thialfihar' (Baş geliştirici)</li>
+<li>'Senecaso' (QR Kodu, anahtar imzalama, anahtar yükleme)</li>
+<li>Oliver Runge</li>
+<li>Markus Doits</li>
+</ul>
+<h2>Kütüphaneler</h2>
+<ul>
+<li>
+<a href="http://actionbarsherlock.com">ActionBarSherlock</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>İkonlar <a href="http://rrze-icon-set.berlios.de/">RRZE Icon Set</a> (Creative Commons Attribution Share-Alike licence 3.0)</li>
+<li>İkonlar <a href="http://tango.freedesktop.org/">Tango Icon Set</a> (Public Domain)</li>
+</ul>
+</body>
+</html>
diff --git a/OpenPGP-Keychain/src/main/res/raw-tr/help_changelog.html b/OpenPGP-Keychain/src/main/res/raw-tr/help_changelog.html
new file mode 100644
index 000000000..433b3c343
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/raw-tr/help_changelog.html
@@ -0,0 +1,96 @@
+<html>
+<head></head>
+<body>
+<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 k9mail 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 key server support</li>
+<li>app2sd (untested, let me know if there are problems)</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>clear sign problem with lacking trailing newline fixed</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 preselection 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 k9, 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>k9mail integration, APG supporting beta build of k9mail</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/OpenPGP-Keychain/src/main/res/raw-tr/help_nfc_beam.html b/OpenPGP-Keychain/src/main/res/raw-tr/help_nfc_beam.html
new file mode 100644
index 000000000..2e7e637e5
--- /dev/null
+++ b/OpenPGP-Keychain/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 'Manage Public Keys' and long press on the key 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/OpenPGP-Keychain/src/main/res/raw-tr/help_start.html b/OpenPGP-Keychain/src/main/res/raw-tr/help_start.html
new file mode 100644
index 000000000..1386e8cc1
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/raw-tr/help_start.html
@@ -0,0 +1,22 @@
+<html>
+<head></head>
+<body>
+<h2>EXPERIMENTAL software</h2>
+<p>This is EXPERIMENTAL software. Use at your own risk!</p>
+
+<h2>Getting started</h2>
+<p>First you need some keys. Import or create them via the option menus in "My Secret Keys".<!--<p>Install K-9 Mail for the best integration, it will supports OpenPGP Keychain for PGP/INLINE and lets you directly encrypt/decrypt emails.-->
+<br>It is recommended that you install OI File Manager to be able to use the browse button for file selection in OpenPGP Keychain.</p>
+
+<h2>Big ToDos</h2>
+<ul>
+<li>K9 Mail integration not published</li>
+<li>Importing existing keys will be stripped of certificates right now</li>
+<li>PGP/MIME in K9 Mail is missing</li>
+</ul>
+<p>If you want to contribute, fork it and do a pull request on Github: <a href="https://github.com/dschuermann/openpgp-keychain">https://github.com/dschuermann/openpgp-keychain</a></p>
+
+<h2>I found a bug in OpenPGP Keychain!</h2>
+<p>Please report it in the <a href="https://github.com/dschuermann/openpgp-keychain/issues">issue tracker of OpenPGP Keychain</a>.</p>
+</body>
+</html>
diff --git a/OpenPGP-Keychain/src/main/res/raw-tr/nfc_beam_share.html b/OpenPGP-Keychain/src/main/res/raw-tr/nfc_beam_share.html
new file mode 100644
index 000000000..453d435e3
--- /dev/null
+++ b/OpenPGP-Keychain/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/OpenPGP-Keychain/src/main/res/raw-uk/help_about.html b/OpenPGP-Keychain/src/main/res/raw-uk/help_about.html
new file mode 100644
index 000000000..7db2f83de
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/raw-uk/help_about.html
@@ -0,0 +1,41 @@
+<html>
+<head></head>
+<body>
+<p><a href="http://sufficientlysecure.org/keychain">http://sufficientlysecure.org/keychain</a></p>
+<p><a href="http://sufficientlysecure.org/keychain">OpenPGP Keychain</a> is an OpenPGP implementation for Android. The development began as a fork of Android Privacy Guard (APG).</p>
+<p>License: GPLv3+</p>
+
+<h2>Developers OpenPGP Keychain</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>
+
+</ul>
+<h2>Developers APG 1.x</h2>
+<ul>
+<li>'Thialfihar' (Lead developer)</li>
+<li>'Senecaso' (QRCode, sign key, upload key)</li>
+<li>Oliver Runge</li>
+<li>Markus Doits</li>
+</ul>
+<h2>Libraries</h2>
+<ul>
+<li>
+<a href="http://actionbarsherlock.com">ActionBarSherlock</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>Icons from <a href="http://rrze-icon-set.berlios.de/">RRZE Icon Set</a> (Creative Commons Attribution Share-Alike licence 3.0)</li>
+<li>Icons from <a href="http://tango.freedesktop.org/">Tango Icon Set</a> (Public Domain)</li>
+</ul>
+</body>
+</html>
diff --git a/OpenPGP-Keychain/src/main/res/raw-uk/help_changelog.html b/OpenPGP-Keychain/src/main/res/raw-uk/help_changelog.html
new file mode 100644
index 000000000..433b3c343
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/raw-uk/help_changelog.html
@@ -0,0 +1,96 @@
+<html>
+<head></head>
+<body>
+<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 k9mail 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 key server support</li>
+<li>app2sd (untested, let me know if there are problems)</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>clear sign problem with lacking trailing newline fixed</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 preselection 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 k9, 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>k9mail integration, APG supporting beta build of k9mail</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/OpenPGP-Keychain/src/main/res/raw-uk/help_nfc_beam.html b/OpenPGP-Keychain/src/main/res/raw-uk/help_nfc_beam.html
new file mode 100644
index 000000000..2e7e637e5
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/raw-uk/help_nfc_beam.html
@@ -0,0 +1,12 @@
+<html>
+<head></head>
+<body>
+<h2>How to receive keys</h2>
+<ol>
+<li>Go to your partners 'Manage Public Keys' and long press on the key 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/OpenPGP-Keychain/src/main/res/raw-uk/help_start.html b/OpenPGP-Keychain/src/main/res/raw-uk/help_start.html
new file mode 100644
index 000000000..1386e8cc1
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/raw-uk/help_start.html
@@ -0,0 +1,22 @@
+<html>
+<head></head>
+<body>
+<h2>EXPERIMENTAL software</h2>
+<p>This is EXPERIMENTAL software. Use at your own risk!</p>
+
+<h2>Getting started</h2>
+<p>First you need some keys. Import or create them via the option menus in "My Secret Keys".<!--<p>Install K-9 Mail for the best integration, it will supports OpenPGP Keychain for PGP/INLINE and lets you directly encrypt/decrypt emails.-->
+<br>It is recommended that you install OI File Manager to be able to use the browse button for file selection in OpenPGP Keychain.</p>
+
+<h2>Big ToDos</h2>
+<ul>
+<li>K9 Mail integration not published</li>
+<li>Importing existing keys will be stripped of certificates right now</li>
+<li>PGP/MIME in K9 Mail is missing</li>
+</ul>
+<p>If you want to contribute, fork it and do a pull request on Github: <a href="https://github.com/dschuermann/openpgp-keychain">https://github.com/dschuermann/openpgp-keychain</a></p>
+
+<h2>I found a bug in OpenPGP Keychain!</h2>
+<p>Please report it in the <a href="https://github.com/dschuermann/openpgp-keychain/issues">issue tracker of OpenPGP Keychain</a>.</p>
+</body>
+</html>
diff --git a/OpenPGP-Keychain/src/main/res/raw-uk/nfc_beam_share.html b/OpenPGP-Keychain/src/main/res/raw-uk/nfc_beam_share.html
new file mode 100644
index 000000000..453d435e3
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/raw-uk/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/OpenPGP-Keychain/src/main/res/raw-zh/help_about.html b/OpenPGP-Keychain/src/main/res/raw-zh/help_about.html
new file mode 100644
index 000000000..7db2f83de
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/raw-zh/help_about.html
@@ -0,0 +1,41 @@
+<html>
+<head></head>
+<body>
+<p><a href="http://sufficientlysecure.org/keychain">http://sufficientlysecure.org/keychain</a></p>
+<p><a href="http://sufficientlysecure.org/keychain">OpenPGP Keychain</a> is an OpenPGP implementation for Android. The development began as a fork of Android Privacy Guard (APG).</p>
+<p>License: GPLv3+</p>
+
+<h2>Developers OpenPGP Keychain</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>
+
+</ul>
+<h2>Developers APG 1.x</h2>
+<ul>
+<li>'Thialfihar' (Lead developer)</li>
+<li>'Senecaso' (QRCode, sign key, upload key)</li>
+<li>Oliver Runge</li>
+<li>Markus Doits</li>
+</ul>
+<h2>Libraries</h2>
+<ul>
+<li>
+<a href="http://actionbarsherlock.com">ActionBarSherlock</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>Icons from <a href="http://rrze-icon-set.berlios.de/">RRZE Icon Set</a> (Creative Commons Attribution Share-Alike licence 3.0)</li>
+<li>Icons from <a href="http://tango.freedesktop.org/">Tango Icon Set</a> (Public Domain)</li>
+</ul>
+</body>
+</html>
diff --git a/OpenPGP-Keychain/src/main/res/raw-zh/help_changelog.html b/OpenPGP-Keychain/src/main/res/raw-zh/help_changelog.html
new file mode 100644
index 000000000..433b3c343
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/raw-zh/help_changelog.html
@@ -0,0 +1,96 @@
+<html>
+<head></head>
+<body>
+<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 k9mail 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 key server support</li>
+<li>app2sd (untested, let me know if there are problems)</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>clear sign problem with lacking trailing newline fixed</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 preselection 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 k9, 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>k9mail integration, APG supporting beta build of k9mail</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/OpenPGP-Keychain/src/main/res/raw-zh/help_nfc_beam.html b/OpenPGP-Keychain/src/main/res/raw-zh/help_nfc_beam.html
new file mode 100644
index 000000000..2e7e637e5
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/raw-zh/help_nfc_beam.html
@@ -0,0 +1,12 @@
+<html>
+<head></head>
+<body>
+<h2>How to receive keys</h2>
+<ol>
+<li>Go to your partners 'Manage Public Keys' and long press on the key 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/OpenPGP-Keychain/src/main/res/raw-zh/help_start.html b/OpenPGP-Keychain/src/main/res/raw-zh/help_start.html
new file mode 100644
index 000000000..1386e8cc1
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/raw-zh/help_start.html
@@ -0,0 +1,22 @@
+<html>
+<head></head>
+<body>
+<h2>EXPERIMENTAL software</h2>
+<p>This is EXPERIMENTAL software. Use at your own risk!</p>
+
+<h2>Getting started</h2>
+<p>First you need some keys. Import or create them via the option menus in "My Secret Keys".<!--<p>Install K-9 Mail for the best integration, it will supports OpenPGP Keychain for PGP/INLINE and lets you directly encrypt/decrypt emails.-->
+<br>It is recommended that you install OI File Manager to be able to use the browse button for file selection in OpenPGP Keychain.</p>
+
+<h2>Big ToDos</h2>
+<ul>
+<li>K9 Mail integration not published</li>
+<li>Importing existing keys will be stripped of certificates right now</li>
+<li>PGP/MIME in K9 Mail is missing</li>
+</ul>
+<p>If you want to contribute, fork it and do a pull request on Github: <a href="https://github.com/dschuermann/openpgp-keychain">https://github.com/dschuermann/openpgp-keychain</a></p>
+
+<h2>I found a bug in OpenPGP Keychain!</h2>
+<p>Please report it in the <a href="https://github.com/dschuermann/openpgp-keychain/issues">issue tracker of OpenPGP Keychain</a>.</p>
+</body>
+</html>
diff --git a/OpenPGP-Keychain/src/main/res/raw-zh/nfc_beam_share.html b/OpenPGP-Keychain/src/main/res/raw-zh/nfc_beam_share.html
new file mode 100644
index 000000000..453d435e3
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/raw-zh/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/OpenPGP-Keychain/src/main/res/raw/help_about.html b/OpenPGP-Keychain/src/main/res/raw/help_about.html
new file mode 100644
index 000000000..54af42b16
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/raw/help_about.html
@@ -0,0 +1,40 @@
+<!-- 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://sufficientlysecure.org/keychain">http://sufficientlysecure.org/keychain</a></p>
+<p><a href="http://sufficientlysecure.org/keychain">OpenPGP Keychain</a> is an OpenPGP implementation for Android. The development began as a fork of Android Privacy Guard (APG).</p>
+<p>License: GPLv3+</p>
+
+<h2>Developers OpenPGP Keychain</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>
+
+</ul>
+<h2>Developers APG 1.x</h2>
+<ul>
+<li>'Thialfihar' (Lead developer)</li>
+<li>'Senecaso' (QRCode, sign key, upload key)</li>
+<li>Oliver Runge</li>
+<li>Markus Doits</li>
+</ul>
+
+<h2>Libraries</h2>
+<ul>
+<li><a href="http://actionbarsherlock.com">ActionBarSherlock</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>Icons from <a href="http://rrze-icon-set.berlios.de/">RRZE Icon Set</a> (Creative Commons Attribution Share-Alike licence 3.0)</li>
+<li>Icons from <a href="http://tango.freedesktop.org/">Tango Icon Set</a> (Public Domain)</li>
+</ul>
+</body>
+</html> \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/res/raw/help_changelog.html b/OpenPGP-Keychain/src/main/res/raw/help_changelog.html
new file mode 100644
index 000000000..dee9d366e
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/raw/help_changelog.html
@@ -0,0 +1,112 @@
+<!-- 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.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 key server 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 k9mail</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/OpenPGP-Keychain/src/main/res/raw/help_nfc_beam.html b/OpenPGP-Keychain/src/main/res/raw/help_nfc_beam.html
new file mode 100644
index 000000000..301c04267
--- /dev/null
+++ b/OpenPGP-Keychain/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 'Manage Public Keys' and long press on the key 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/OpenPGP-Keychain/src/main/res/raw/help_start.html b/OpenPGP-Keychain/src/main/res/raw/help_start.html
new file mode 100644
index 000000000..097e22ba8
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/raw/help_start.html
@@ -0,0 +1,27 @@
+<!-- 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>EXPERIMENTAL software</h2>
+<p>This is EXPERIMENTAL software. Use at your own risk!</p>
+
+<h2>Getting started</h2>
+<p>First you need some keys. Import or create them via the option menus in "My Secret Keys".
+<!--<p>Install K-9 Mail for the best integration, it will supports OpenPGP Keychain for PGP/INLINE and lets you directly encrypt/decrypt emails.-->
+<br/>It is recommended that you install OI File Manager to be able to use the browse button for file selection in OpenPGP Keychain.</p>
+
+<h2>Big ToDos</h2>
+<ul>
+<li>K9 Mail integration not published</li>
+<li>Importing existing keys will be stripped of certificates right now</li>
+<li>PGP/MIME in K9 Mail is missing</li>
+</ul>
+<p>If you want to contribute, fork it and do a pull request on Github: <a href="https://github.com/dschuermann/openpgp-keychain">https://github.com/dschuermann/openpgp-keychain</a></p>
+
+<h2>I found a bug in OpenPGP Keychain!</h2>
+<p>Please report it in the <a href="https://github.com/dschuermann/openpgp-keychain/issues">issue tracker of OpenPGP Keychain</a>.</p>
+</body>
+</html>
diff --git a/OpenPGP-Keychain/src/main/res/raw/nfc_beam_share.html b/OpenPGP-Keychain/src/main/res/raw/nfc_beam_share.html
new file mode 100644
index 000000000..734e69fc5
--- /dev/null
+++ b/OpenPGP-Keychain/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/OpenPGP-Keychain/src/main/res/values-de/strings.xml b/OpenPGP-Keychain/src/main/res/values-de/strings.xml
new file mode 100644
index 000000000..e0b621013
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/values-de/strings.xml
@@ -0,0 +1,376 @@
+<?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">Einstellung der Schlüsselserver</string>
+ <string name="title_change_pass_phrase">Passwort ändern</string>
+ <string name="title_set_passphrase">Passwort setzen</string>
+ <string name="title_send_email">E-Mail 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_sign_key">Schlüssel signieren</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_signing_key">Dein Signaturschlüssel</string>
+ <string name="section_upload_key">Schlüssel hochladen</string>
+ <string name="section_key_server">Schlüsselserver</string>
+ <!--button-->
+ <string name="btn_sign_to_clipboard">Signieren (Zwischenablage)</string>
+ <string name="btn_encrypt_to_clipboard">In die Zwischenablage verschlüsseln</string>
+ <string name="btn_encrypt_and_send">Verschlüsseln und senden...</string>
+ <string name="btn_sign_and_send">Signieren und senden...</string>
+ <string name="btn_sign">Signieren</string>
+ <string name="btn_decrypt">Entschlüsseln</string>
+ <string name="btn_verify">Signatur prüfen</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">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>
+ <!--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_keys">Alle Schlüssel exportieren</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_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>
+ <!--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">Öffentliche Schlüssel</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_encryption_algorithm">Verschlüsselungsalgorithmus</string>
+ <string name="label_hash_algorithm">Hash-Algorithmus</string>
+ <string name="label_asymmetric">Öffentlicher Schlüssel</string>
+ <string name="label_symmetric">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">OpenPGPv3-Signaturen 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">Lade Schlüssel nach dem Signieren auf den ausgewählten Schlüsselserver hoch</string>
+ <string name="label_fingerprint">Fingerabdruck</string>
+ <string name="select_keys_button_default">Auswählen</string>
+ <plurals name="select_keys_button">
+ <item quantity="one">%d ausgewählt</item>
+ <item quantity="other">%d ausgewählt</item>
+ </plurals>
+ <string name="unknown_user_id">&lt;unbekannt&gt;</string>
+ <string name="none">&lt;keine&gt;</string>
+ <string name="no_key">&lt;kein Schlüssel&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>
+ <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_sign_only">Nur Signieren</string>
+ <string name="choice_encrypt_only">Nur Verschlüsseln</string>
+ <string name="choice_sign_and_encrypt">Signieren und Verschlüsseln</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="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>
+ <!--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_must_not_be_empty">Leere Passwörter sind nicht erlaubt.</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="decryption_successful">Erfolgreich entschlüsselt.</string>
+ <string name="encryption_successful">Erfolgreich verschlüsselt.</string>
+ <string name="encryption_to_clipboard_successful">Erfolgreich in die Zwischenablage verschlüsselt.</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>
+ <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_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_key_touch_to_look_up">Unbekannte Unterschrift, zum Suchen berühren. </string>
+ <string name="lookup_unknown_key">Unbekannter Schlüssel %s, soll dieser auf einem Schlüsselserver gesucht werden?</string>
+ <string name="key_send_success">Schlüssel erfolgreich zum Server geschickt.</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_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_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>
+ <!--progress dialogs, usually ending in '…'-->
+ <string name="progress_done">fertig.</string>
+ <string name="progress_saving">speichern...</string>
+ <string name="progress_importing">importieren...</string>
+ <string name="progress_exporting">exportieren...</string>
+ <string name="progress_generating">erstelle Schlüssel, dies kann eine Weile dauern...</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>
+ <string name="progress_importing_secret_keys">importiere private Schlüssel...</string>
+ <plurals name="progress_exporting_key">
+ <item quantity="one">Schlüssel wird exportiert…</item>
+ <item quantity="other">Schlüssel werden exportiert…</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_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 in diesem QR-Code 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">OpenPGP: Datei entschlüsseln</string>
+ <string name="intent_import_key">OpenPGP: Schlüssel importieren</string>
+ <string name="intent_send_encrypt">OpenPGP: Verschlüsseln</string>
+ <string name="intent_send_decrypt">OpenPGP: Entschlüsseln</string>
+ <!--Remote API-->
+ <string name="api_no_apps">Nicht registrierte Anwendungen!</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">Zugang widerufen</string>
+ <string name="api_settings_package_name">Paketname</string>
+ <string name="api_settings_package_signature">SHA-256 der Paketsignatur</string>
+ <string name="api_register_text">Folgende Anwendung beantragt Zugriff zur API von OpenPGP Keychain.\n\nZugriff dauerhaft erlauben?</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 OpenPGP Keychain 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_encrypt">Für diesen Kontakt verschlüsseln</string>
+ <!--Navigation Drawer-->
+ <string name="nav_contacts">Kontakte</string>
+ <string name="nav_encrypt">Verschlüsseln</string>
+ <string name="nav_decrypt">Entschlüsseln</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>
+</resources>
diff --git a/OpenPGP-Keychain/src/main/res/values-es-rCO/strings.xml b/OpenPGP-Keychain/src/main/res/values-es-rCO/strings.xml
new file mode 100644
index 000000000..420c0fa9b
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/values-es-rCO/strings.xml
@@ -0,0 +1,113 @@
+<?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_key_server_preference">Preferencias del servidor de claves</string>
+ <string name="title_change_pass_phrase">Cambiar contraseña</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_key_server_query">Solicitar al servidor de claves</string>
+ <string name="title_unknown_signature_key">Clave de firma desconocida</string>
+ <string name="title_sign_key">Clave de firma</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_to_clipboard">Firmar (Portapapeles)</string>
+ <string name="btn_encrypt_to_clipboard">Cifrar a portapapeles</string>
+ <string name="btn_encrypt_and_send">Cifrar y enviar</string>
+ <string name="btn_sign_and_send">Firmar y enviar</string>
+ <string name="btn_sign">Firmar</string>
+ <string name="btn_decrypt">Descifrar</string>
+ <string name="btn_verify">Verificar</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_change_passphrase">Cambiar contraseña</string>
+ <string name="btn_set_passphrase">Establecer contraseña</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_keys">Exportar todas las claves</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_key_server">Importar desde servidor de claves</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_asymmetric">Clave pública</string>
+ <string name="label_symmetric">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 V3</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">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_user_id">&lt;desconocido&gt;</string>
+ <string name="unknown_status"></string>
+ <!--choice-->
+ <!--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-->
+</resources>
diff --git a/OpenPGP-Keychain/src/main/res/values-fr/strings.xml b/OpenPGP-Keychain/src/main/res/values-fr/strings.xml
new file mode 100644
index 000000000..e18af1819
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/values-fr/strings.xml
@@ -0,0 +1,377 @@
+<?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">Serveurs de clefs préférés</string>
+ <string name="title_change_pass_phrase">Changer 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_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_sign_key">Signer 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_signing_key">Votre clef utilisée pour la signature</string>
+ <string name="section_upload_key">Téléverser la clef</string>
+ <string name="section_key_server">Serveur de clefs</string>
+ <!--button-->
+ <string name="btn_sign_to_clipboard">Signer (presse-papiers)</string>
+ <string name="btn_encrypt_to_clipboard">Chiffrer vers le presse-papiers</string>
+ <string name="btn_encrypt_and_send">Chiffrer et envoyer...</string>
+ <string name="btn_sign_and_send">Signer et envoyer...</string>
+ <string name="btn_sign">Signer</string>
+ <string name="btn_decrypt">Déchiffrer</string>
+ <string name="btn_verify">Vérifier</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 phrase de passe</string>
+ <string name="btn_set_passphrase">Définir la 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>
+ <!--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_keys">Exporter toutes les clefs</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_key_server">Importer depuis le 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>
+ <!--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">Clefs publiques</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_encryption_algorithm">Algorithme de chiffrement</string>
+ <string name="label_hash_algorithm">Algorithme de hachage</string>
+ <string name="label_asymmetric">Clef publique</string>
+ <string name="label_symmetric">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 signatures 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 signature</string>
+ <string name="label_fingerprint">Empreinte</string>
+ <string name="select_keys_button_default">Choisir</string>
+ <plurals name="select_keys_button">
+ <item quantity="one">%d choisie</item>
+ <item quantity="other">%d choisies</item>
+ </plurals>
+ <string name="unknown_user_id">&lt;inconnue&gt;</string>
+ <string name="none">&lt;aucune&gt;</string>
+ <string name="no_key">&lt;pas de clef&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>
+ <plurals name="n_key_servers">
+ <item quantity="one">%d serveur de clef</item>
+ <item quantity="other">%d serveurs de clef</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_sign_only">Signer seulement</string>
+ <string name="choice_encrypt_only">Chiffrer seulement</string>
+ <string name="choice_sign_and_encrypt">Signer et chiffrer</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="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>
+ <!--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">Les phrases de passe vides ne sont pas autorisées.</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é avec succès.</string>
+ <string name="encryption_successful">Chiffré avec succès.</string>
+ <string name="encryption_to_clipboard_successful">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>
+ <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_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_key_touch_to_look_up">Signature inconnue. Toucher pour rechercher la clef.</string>
+ <string name="bad_keys_encountered">%d mauvaise(s) clef(s) ignorée(s). Vous avez peut-être exporté avec l\'option\n --export-secret-subkeys\nAssurez-vous d\'exporter plutôt avec\n --export-secret-keys.</string>
+ <string name="lookup_unknown_key">Clef %s inconnue, voulez-vous essayer de la trouver sur un serveur de clefs ?</string>
+ <string name="key_send_success">Clef envoyé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 clé 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_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_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>
+ <!--progress dialogs, usually ending in '…'-->
+ <string name="progress_done">fait.</string>
+ <string name="progress_saving">sauvegarde...</string>
+ <string name="progress_importing">importation...</string>
+ <string name="progress_exporting">exportation...</string>
+ <string name="progress_generating">génération de la clef, ceci peut prendre un moment...</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>
+ <string name="progress_importing_secret_keys">Importation des clefs secrètes...</string>
+ <plurals name="progress_exporting_key">
+ <item quantity="one">exportation de la clef...</item>
+ <item quantity="other">exportation des clefs...</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_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 contenue dans ce code QR 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">OpenPGP : déchiffrer le ficher</string>
+ <string name="intent_import_key">OpenPGP : importer la clef</string>
+ <string name="intent_send_encrypt">OpenPGP : chiffrer</string>
+ <string name="intent_send_decrypt">OpenPGP : déchiffrer</string>
+ <!--Remote API-->
+ <string name="api_no_apps">Aucune application enregistrée !</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_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_package_name">Nom du paquet</string>
+ <string name="api_settings_package_signature">SHA-256 de la signature du paquet</string>
+ <string name="api_register_text">L\'application suivante demande l\'accès à l\'API du Porte-clefs OpenPGP.\n\nPermettre l\'accès permanent ?</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évoquer l\'enregistrement de cette appli dans le Porte-clefs OpenPGP et l\'enregistrer à 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_encrypt">Chiffrer vers ce contact</string>
+ <!--Navigation Drawer-->
+ <string name="nav_contacts">Contacts</string>
+ <string name="nav_encrypt">Chiffrer</string>
+ <string name="nav_decrypt">Déchiffrer</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>
+</resources>
diff --git a/OpenPGP-Keychain/src/main/res/values-it-rIT/strings.xml b/OpenPGP-Keychain/src/main/res/values-it-rIT/strings.xml
new file mode 100644
index 000000000..44424561b
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/values-it-rIT/strings.xml
@@ -0,0 +1,92 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<resources>
+ <!--title-->
+ <string name="title_select_recipients">Selezionare Chiave Pubblica</string>
+ <string name="title_select_secret_key">Selezionare Chiave Privata</string>
+ <string name="title_encrypt">Cifrare</string>
+ <string name="title_decrypt">Decifrare</string>
+ <string name="title_authentication">Passphrase</string>
+ <string name="title_create_key">Creare Chiave</string>
+ <string name="title_edit_key">Modificare Chiave</string>
+ <string name="title_preferences">Preferenze</string>
+ <string name="title_set_passphrase">Impostare Passphrase</string>
+ <string name="title_send_email">Inviare Mail...</string>
+ <string name="title_import_keys">Importare Chiavi</string>
+ <string name="title_export_key">Esportare Chiave</string>
+ <string name="title_export_keys">Esportare Chiavi</string>
+ <string name="title_key_not_found">Chiave Non Trovata</string>
+ <string name="title_sign_key">Firma 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_advanced">Avanzato</string>
+ <!--button-->
+ <string name="btn_sign_and_send">Firmare ed inviare...</string>
+ <string name="btn_sign">Firmare</string>
+ <string name="btn_decrypt">Decifrare</string>
+ <string name="btn_verify">Verificare</string>
+ <string name="btn_encrypt_file">Cifrare File</string>
+ <string name="btn_save">Salva</string>
+ <string name="btn_do_not_save">Cancella</string>
+ <string name="btn_delete">Eliminare</string>
+ <string name="btn_no_date">Nessuno</string>
+ <string name="btn_okay">Okay</string>
+ <string name="btn_change_passphrase">Cambiare passphrase</string>
+ <string name="btn_search">Cercare</string>
+ <!--menu-->
+ <string name="menu_preferences">Impostazioni</string>
+ <string name="menu_import_from_file">Importare da file</string>
+ <string name="menu_import_from_qr_code">Importare da QR Code</string>
+ <string name="menu_delete_key">Eliminare chiave</string>
+ <string name="menu_create_key">Creare chiave</string>
+ <string name="menu_search">Cercare</string>
+ <!--label-->
+ <string name="unknown_status"></string>
+ <!--choice-->
+ <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="dsa">DSA</string>
+ <string name="elgamal">ElGamal</string>
+ <string name="rsa">RSA</string>
+ <string name="warning">Attenzione</string>
+ <string name="error">Errore</string>
+ <string name="error_message">Errore: %s</string>
+ <!--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-->
+ <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-->
+ <!--Help-->
+ <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">Versione:</string>
+ <!--Import-->
+ <!--Intent labels-->
+ <string name="intent_send_encrypt">OpenPGP: Cifrare</string>
+ <string name="intent_send_decrypt">OpenPGP: Decifrare</string>
+ <!--Remote API-->
+ <string name="api_settings_no_key">Nessuna chiave selezionata</string>
+ <string name="api_settings_select_key">Selezionare chiave</string>
+ <string name="api_settings_save">Salvare</string>
+ <string name="api_settings_cancel">Cancellare</string>
+ <string name="api_settings_revoke">Revocare l\'accesso</string>
+ <string name="api_register_allow">Permettere l\'accesso</string>
+ <string name="api_register_disallow">Disabilitare l\'accesso</string>
+ <string name="api_register_error_select_key">Per favore selezionare una chiave!</string>
+ <!--Share-->
+ <!--Key list-->
+ <!--Key view-->
+ <!--Navigation Drawer-->
+</resources>
diff --git a/OpenPGP-Keychain/src/main/res/values-nl-rNL/strings.xml b/OpenPGP-Keychain/src/main/res/values-nl-rNL/strings.xml
new file mode 100644
index 000000000..41288eb10
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/values-nl-rNL/strings.xml
@@ -0,0 +1,263 @@
+<?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_key_server_preference">Instellingen sleutelserver</string>
+ <string name="title_change_pass_phrase">Wachtwoord wijzigen</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_key_server_query">Sleutelserver verzoek zenden</string>
+ <string name="title_unknown_signature_key">Onbekende handtekeningssleutel</string>
+ <string name="title_sign_key">Sleutel ondertekenen</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_to_clipboard">Ondertekenen (klembord)</string>
+ <string name="btn_encrypt_to_clipboard">Versleutelen naar klembord</string>
+ <string name="btn_encrypt_and_send">Versleutelen en verzenden...</string>
+ <string name="btn_sign_and_send">Ondertekenen en verzenden...</string>
+ <string name="btn_sign">Ondertekenen</string>
+ <string name="btn_decrypt">Ontsleutelen</string>
+ <string name="btn_verify">Verifiëren</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_change_passphrase">Wachtwoord wijzigen</string>
+ <string name="btn_set_passphrase">Wachtwoord instellen</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_keys">Alle sleutels exporteren</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_key_server">Importeren uit sleutelserver</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_asymmetric">Publieke sleutel</string>
+ <string name="label_symmetric">Wachtwoord</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_force_v3_signature">V3-handtekeningen afdwingen</string>
+ <string name="label_key_servers">Sleutelservers</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="unknown_user_id">&lt;onbekend&gt;</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_sign_only">Alleen ondertekenen</string>
+ <string name="choice_encrypt_only">Alleen versleutelen</string>
+ <string name="choice_sign_and_encrypt">Ondertekenen en versleutelen</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>
+ <!--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_must_not_be_empty">Lege wachtwoorden zijn niet toegestaand.</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="decryption_successful">Succesvol ontsleuteld.</string>
+ <string name="encryption_successful">Succesvol versleuteld.</string>
+ <string name="encryption_to_clipboard_successful">Succesvol versleuteld naar klembord.</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="unknown_signature_key_touch_to_look_up">Onbekende handtekening, tik om sleutel op te zoeken.</string>
+ <string name="lookup_unknown_key">Onbekende sleutel %s, wilt u het bij een sleutelserver opvragen?</string>
+ <string name="key_send_success">Sleutel succesvol verzonden naar server</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_done">gereed.</string>
+ <string name="progress_saving">opslaan...</string>
+ <string name="progress_importing">importeren...</string>
+ <string name="progress_exporting">exporteren...</string>
+ <string name="progress_generating">sleutel genereren, een ogenblik geduld...</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_importing_secret_keys">privésleutels importeren...</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-->
+ <string name="intent_decrypt_file">OpenPGP: bestand ontsleutelen</string>
+ <string name="intent_import_key">OpenPGP: sleutel importeren</string>
+ <string name="intent_send_encrypt">OpenPGP: versleutelen</string>
+ <string name="intent_send_decrypt">OpenPGP: ontsleutelen</string>
+ <!--Remote API-->
+ <string name="api_no_apps">Geen geregistreerde apps</string>
+ <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_text">De volgende app vraagt toegang to de KeyChain-API van OpenPGP\n\nAltijd toestaan?</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-->
+</resources>
diff --git a/OpenPGP-Keychain/src/main/res/values-pt-rBR/strings.xml b/OpenPGP-Keychain/src/main/res/values-pt-rBR/strings.xml
new file mode 100644
index 000000000..6bb115049
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/values-pt-rBR/strings.xml
@@ -0,0 +1,26 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<resources>
+ <!--title-->
+ <!--section-->
+ <!--button-->
+ <!--menu-->
+ <!--label-->
+ <string name="unknown_status"></string>
+ <!--choice-->
+ <!--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-->
+</resources>
diff --git a/OpenPGP-Keychain/src/main/res/values-ru/strings.xml b/OpenPGP-Keychain/src/main/res/values-ru/strings.xml
new file mode 100644
index 000000000..6ab9676a2
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/values-ru/strings.xml
@@ -0,0 +1,387 @@
+<?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_pass_phrase">Изменить пароль</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_sign_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_signing_key">Ваш ключ, используемый для подписания</string>
+ <string name="section_upload_key">Загрузить ключ</string>
+ <string name="section_key_server">Сервер ключей</string>
+ <!--button-->
+ <string name="btn_sign_to_clipboard">Подписать (Буфер обмена)</string>
+ <string name="btn_encrypt_to_clipboard">Зашифровать в Буфер обмена</string>
+ <string name="btn_encrypt_and_send">Зашифровать и отправить...</string>
+ <string name="btn_sign_and_send">Подписать и отправить...</string>
+ <string name="btn_sign">Подписать</string>
+ <string name="btn_decrypt">Расшифровать</string>
+ <string name="btn_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_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>
+ <!--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_keys">Экспорт всех ключей</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_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>
+ <!--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_encryption_algorithm">Алгоритм шифрования</string>
+ <string name="label_hash_algorithm">Hash-алгоритм</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">Использовать V3 подписи</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>
+ <plurals name="select_keys_button">
+ <item quantity="one">%d выбран</item>
+ <item quantity="few">%d выбрано</item>
+ <item quantity="other">%d выбрано</item>
+ </plurals>
+ <string name="unknown_user_id">&lt;неизв.&gt;</string>
+ <string name="none">&lt;нет&gt;</string>
+ <string name="no_key">&lt;нет ключа&gt;</string>
+ <string name="unknown_status"></string>
+ <string name="can_encrypt">шифрование</string>
+ <string name="can_sign">подпись</string>
+ <string name="expired">годен до</string>
+ <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_sign_only">Только подпись</string>
+ <string name="choice_encrypt_only">Только шифрование</string>
+ <string name="choice_sign_and_encrypt">Шифрование и подпись</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="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>
+ <!--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>
+ <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_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_key_touch_to_look_up">Неизвестная подпись. Нажмите для выбора ключа.</string>
+ <string name="bad_keys_encountered">%d секретных ключей проигнорировано.\nВозможно, вы экспортируете с параметром\n--export-secret-subkeys\nВместо этого используйте\n--export-secret-keys\n</string>
+ <string name="lookup_unknown_key">Неизвестный ключ %s. Хотите искать на сервере ключей?</string>
+ <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_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_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>
+ <!--progress dialogs, usually ending in '…'-->
+ <string name="progress_done">готово.</string>
+ <string name="progress_saving">сохранение...</string>
+ <string name="progress_importing">импорт...</string>
+ <string name="progress_exporting">экспорт...</string>
+ <string name="progress_generating">создание ключа... на это нужно время...</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>
+ <string name="progress_importing_secret_keys">импорт секретных ключей...</string>
+ <plurals name="progress_exporting_key">
+ <item quantity="one">экспорт ключа...</item>
+ <item quantity="few">экспорт ключей...</item>
+ <item quantity="other">экспорт ключей...</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_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_code_too_short_fingerprint">QR код содержит слишком короткий отпечаток (&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">OpenPGP: Расшифровать файл</string>
+ <string name="intent_import_key">OpenPGP: Импортировать ключ</string>
+ <string name="intent_send_encrypt">OpenPGP: Зашифровать</string>
+ <string name="intent_send_decrypt">OpenPGP: Расшифровать</string>
+ <!--Remote API-->
+ <string name="api_no_apps">Нет связанных приложений!</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_save">Сохранить</string>
+ <string name="api_settings_cancel">Отмена</string>
+ <string name="api_settings_revoke">Отозвать доступ</string>
+ <string name="api_settings_package_name">Наименование пакета</string>
+ <string name="api_settings_package_signature">SHA-256 подписи пакета</string>
+ <string name="api_register_text">Приложение запрашивает доступ к OpenPGP Keychain API.\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_encrypt">Зашифровать для этого получателя</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>
+</resources>
diff --git a/OpenPGP-Keychain/src/main/res/values-sl-rSI/strings.xml b/OpenPGP-Keychain/src/main/res/values-sl-rSI/strings.xml
new file mode 100644
index 000000000..6bb115049
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/values-sl-rSI/strings.xml
@@ -0,0 +1,26 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<resources>
+ <!--title-->
+ <!--section-->
+ <!--button-->
+ <!--menu-->
+ <!--label-->
+ <string name="unknown_status"></string>
+ <!--choice-->
+ <!--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-->
+</resources>
diff --git a/OpenPGP-Keychain/src/main/res/values-tr/strings.xml b/OpenPGP-Keychain/src/main/res/values-tr/strings.xml
new file mode 100644
index 000000000..d9fe8713e
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/values-tr/strings.xml
@@ -0,0 +1,152 @@
+<?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_key_server_preference">Anahtar Sunucusu Seçenekleri</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_key_server_query">Anahtar Sunucusunu Sorgula</string>
+ <string name="title_unknown_signature_key">Bilinmeyen İmza Anahtarı</string>
+ <string name="title_sign_key">Anahtarı İmzala</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>
+ <string name="section_key_server">Anahtar Sunucusu</string>
+ <!--button-->
+ <string name="btn_sign_to_clipboard">İmzala (Pano)</string>
+ <string name="btn_encrypt_to_clipboard">Panoya Şifrele</string>
+ <string name="btn_encrypt_and_send">Şifrele ve gönder...</string>
+ <string name="btn_sign_and_send">İmzala ve gönder...</string>
+ <string name="btn_sign">İmzala</string>
+ <string name="btn_verify">Doğrula</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_export_to_server">Sunucuya Anahtar Yükle</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_key_server">Anahtar sunucusundan al</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_asymmetric">Açık Anahtar</string>
+ <string name="label_key_servers">Anahtar Sunucuları</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>
+ <!--sentences-->
+ <string name="file_delete_successful">Başarıyla silindi.</string>
+ <string name="no_file_selected">Önce bir dosya seçin.</string>
+ <string name="encryption_successful">Başarıyla şifrelendi.</string>
+ <string name="key_not_found">Anahtar %08X bulunamadı.</string>
+ <string name="key_send_success">Anahtar sunucuya başarıyla gönderildi</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_done">bitti.</string>
+ <string name="progress_saving">kaydediliyor...</string>
+ <string name="progress_importing">alıyor...</string>
+ <string name="progress_exporting">veriyor...</string>
+ <string name="progress_generating">anahtar üretiliyor, bu biraz süre alabilir...</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-->
+</resources>
diff --git a/OpenPGP-Keychain/src/main/res/values-uk/strings.xml b/OpenPGP-Keychain/src/main/res/values-uk/strings.xml
new file mode 100644
index 000000000..f556d73d0
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/values-uk/strings.xml
@@ -0,0 +1,263 @@
+<?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_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_pass_phrase">Змінити парольну фразу</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_unknown_signature_key">Невідомий підпис ключа</string>
+ <string name="title_sign_key">Підписати ключ</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>
+ <!--button-->
+ <string name="btn_sign_to_clipboard">Підпис (буфер обміну)</string>
+ <string name="btn_encrypt_to_clipboard">Зашифрувати у буфер обміну</string>
+ <string name="btn_encrypt_and_send">Шифрувати і надіслати…</string>
+ <string name="btn_sign_and_send">Підписати і надіслати…</string>
+ <string name="btn_sign">Підписати</string>
+ <string name="btn_decrypt">Розшифрувати</string>
+ <string name="btn_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_okay">Гаразд</string>
+ <string name="btn_change_passphrase">Змінити парольну фразу</string>
+ <string name="btn_set_passphrase">Задати парольну фразу</string>
+ <string name="btn_search">Пошук</string>
+ <string name="btn_next">Далі</string>
+ <string name="btn_back">Назад</string>
+ <!--menu-->
+ <string name="menu_preferences">Параметри</string>
+ <string name="menu_import_from_file">Імпорт з файлу</string>
+ <string name="menu_import_from_qr_code">Імпорт з штрих-коду</string>
+ <string name="menu_import_from_nfc">Імпорт з NFC</string>
+ <string name="menu_export_keys">Експортувати усі ключі</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_key_server">Імпорт з сервера ключів</string>
+ <string name="menu_sign_key">Підписати ключ</string>
+ <string name="menu_beam_preferences">Налаштування променя</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_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_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">Примусові підписи V3</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="unknown_user_id">&lt;невідомий&gt;</string>
+ <string name="none">&lt;жоден&gt;</string>
+ <string name="no_key">&lt;без ключа&gt;</string>
+ <string name="unknown_status"></string>
+ <string name="can_encrypt">можна зашифрувати</string>
+ <string name="can_sign">можна підписати</string>
+ <string name="expired">закінчився</string>
+ <string name="fingerprint">Відбиток:</string>
+ <string name="secret_key">Секретний ключ:</string>
+ <!--choice-->
+ <string name="choice_none">Жоден</string>
+ <string name="choice_sign_only">Підписати лише</string>
+ <string name="choice_encrypt_only">Шифрувати тільки</string>
+ <string name="choice_sign_and_encrypt">Шифрувати і розшифрувати</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="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>
+ <!--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="key_deletion_confirmation">Ви справді хочете вилучити ключ \'%s\'?\nВи не зможете це відмінити!</string>
+ <string name="secret_key_deletion_confirmation">Ви справді хочете вилучити секретний ключ \'%s\'?\nВи не зможете це відмінити!</string>
+ <string name="no_keys_added_or_updated">Жодного ключа не додано та не оновлено.</string>
+ <string name="key_exported">Успішно експортовано 1 ключ.</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_not_found">Не можливо знайти ключ %08X.</string>
+ <string name="unknown_signature_key_touch_to_look_up">Невідомий підпис, натисніть, щоб шукати ключ.</string>
+ <string name="lookup_unknown_key">Невідомий ключ %s, ви хочете віднайти його на сервері ключів?</string>
+ <string name="key_send_success">Успішно надіслано ключ на сервер</string>
+ <string name="key_sign_success">Успішно підписаний ключ</string>
+ <string name="list_empty">Цей список - порожній!</string>
+ <string name="nfc_successfull">Успішно відправлений ключ з NFC променем!</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_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_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_no_symmetric_encryption_packet">не знайдено пакунок з симетричним шифруванням</string>
+ <string name="error_wrong_passphrase">помилкова парольна фраза</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>
+ <!--progress dialogs, usually ending in '…'-->
+ <string name="progress_done">готово.</string>
+ <string name="progress_saving">збереження…</string>
+ <string name="progress_importing">імпортується…</string>
+ <string name="progress_exporting">експортується…</string>
+ <string name="progress_generating">генерується ключ, зачекайте…</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_importing_secret_keys">імпортуються секретні ключі…</string>
+ <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_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_qr_code_wrong">Невірний штрих-код! Спробуйте знову!</string>
+ <string name="import_qr_code_finished">Сканування штрих-коду завершено!</string>
+ <!--Intent labels-->
+ <string name="intent_decrypt_file">OpenPGP: розшифрувати файл</string>
+ <string name="intent_import_key">OpenPGP: імпортувати ключ</string>
+ <string name="intent_send_encrypt">OpenPGP: зашифрувати</string>
+ <string name="intent_send_decrypt">OpenPGP: розшифрувати</string>
+ <!--Remote API-->
+ <string name="api_no_apps">Немає зареєстрованих програм!</string>
+ <string name="api_settings_no_key">Не вибрано ключа</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_text">Наступна програм запитала доступ до OpenPGP Keychain API.\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>
+ <!--Share-->
+ <string name="share_qr_code_dialog_start">Пройдіть через усі штрих-коди за допомогою \"Далі\", а також проскануйте їх по одному.</string>
+ <!--Key list-->
+ <!--Key view-->
+ <!--Navigation Drawer-->
+</resources>
diff --git a/OpenPGP-Keychain/src/main/res/values-zh/strings.xml b/OpenPGP-Keychain/src/main/res/values-zh/strings.xml
new file mode 100644
index 000000000..6bb115049
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/values-zh/strings.xml
@@ -0,0 +1,26 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<resources>
+ <!--title-->
+ <!--section-->
+ <!--button-->
+ <!--menu-->
+ <!--label-->
+ <string name="unknown_status"></string>
+ <!--choice-->
+ <!--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-->
+</resources>
diff --git a/OpenPGP-Keychain/src/main/res/values/arrays.xml b/OpenPGP-Keychain/src/main/res/values/arrays.xml
new file mode 100644
index 000000000..974239110
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/values/arrays.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <string-array name="pass_phrase_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="pass_phrase_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_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/OpenPGP-Keychain/src/main/res/values/colors.xml b/OpenPGP-Keychain/src/main/res/values/colors.xml
new file mode 100644
index 000000000..780137181
--- /dev/null
+++ b/OpenPGP-Keychain/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/OpenPGP-Keychain/src/main/res/values/static_strings.xml b/OpenPGP-Keychain/src/main/res/values/static_strings.xml
new file mode 100644
index 000000000..3c9cf6673
--- /dev/null
+++ b/OpenPGP-Keychain/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">OpenPGP Keychain</string>
+
+</resources> \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/res/values/strings.xml b/OpenPGP-Keychain/src/main/res/values/strings.xml
new file mode 100644
index 000000000..8e555df5a
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/values/strings.xml
@@ -0,0 +1,419 @@
+<?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">Key Server Preference</string>
+ <string name="title_change_pass_phrase">Change Passphrase</string>
+ <string name="title_set_passphrase">Set Passphrase</string>
+ <string name="title_send_email">"Send Mail…"</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 Key Server</string>
+ <string name="title_send_key">Upload to Key Server</string>
+ <string name="title_unknown_signature_key">Unknown Signature Key</string>
+ <string name="title_sign_key">Sign 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_signing_key">Your Key used for Signing</string>
+ <string name="section_upload_key">Upload Key</string>
+ <string name="section_key_server">Key Server</string>
+
+ <!-- button -->
+ <string name="btn_sign_to_clipboard">Sign (Clipboard)</string>
+ <string name="btn_encrypt_to_clipboard">Encrypt to Clipboard</string>
+ <string name="btn_encrypt_and_send">Encrypt and send…</string>
+ <string name="btn_sign_and_send">Sign and send…</string>
+ <string name="btn_sign">Sign</string>
+ <string name="btn_decrypt">Decrypt</string>
+ <string name="btn_verify">Verify</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 Passphrase</string>
+ <string name="btn_set_passphrase">Set Passphrase</string>
+ <string name="btn_search">Search</string>
+ <string name="btn_export_to_server">Upload To Key Server</string>
+ <string name="btn_next">Next</string>
+ <string name="btn_back">Back</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_keys">Export all keys</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_key_server">Import from key server</string>
+ <string name="menu_update_key">Update from key server</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>
+
+ <!-- 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">Public Keys</string>
+ <string name="label_delete_after_encryption">Delete After Encryption</string>
+ <string name="label_delete_after_decryption">Delete After Decryption</string>
+ <string name="label_encryption_algorithm">Encryption Algorithm</string>
+ <string name="label_hash_algorithm">Hash Algorithm</string>
+ <string name="label_asymmetric">Public Key</string>
+ <string name="label_symmetric">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 V3 Signatures</string>
+ <string name="label_key_servers">Key Servers</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 key server after signing</string>
+ <string name="label_fingerprint">Fingerprint</string>
+ <string name="select_keys_button_default">Select</string>
+
+ <plurals name="select_keys_button">
+ <item quantity="one">%d selected</item>
+ <item quantity="other">%d selected</item>
+ </plurals>
+
+ <string name="unknown_user_id">&lt;unknown&gt;</string>
+ <string name="none">&lt;none&gt;</string>
+ <string name="no_key">&lt;no key&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>
+
+ <plurals name="n_key_servers">
+ <item quantity="one">%d key server</item>
+ <item quantity="other">%d key servers</item>
+ </plurals>
+
+ <string name="fingerprint">Fingerprint:</string>
+ <string name="secret_key">Secret Key:</string>
+
+ <!-- choice -->
+ <string name="choice_none">None</string>
+ <string name="choice_sign_only">Sign only</string>
+ <string name="choice_encrypt_only">Encrypt only</string>
+ <string name="choice_sign_and_encrypt">Sign and Encrypt</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>
+
+ <!-- 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">Empty passphrases are not allowed.</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.</string>
+ <string name="encryption_successful">Successfully encrypted.</string>
+ <string name="encryption_to_clipboard_successful">Successfully 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>
+
+ <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_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_key_touch_to_look_up">Unknown signature, touch to look up 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="lookup_unknown_key">Unknown key %s, do you want to try finding it on a keyserver?</string>
+ <string name="key_send_success">Successfully sent 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_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_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>
+
+ <!-- progress dialogs, usually ending in '…' -->
+ <string name="progress_done">done.</string>
+ <string name="progress_saving">saving…</string>
+ <string name="progress_importing">importing…</string>
+ <string name="progress_exporting">exporting…</string>
+ <string name="progress_generating">generating key, this can take a while…</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>
+ <string name="progress_importing_secret_keys">importing secret keys…</string>
+
+ <plurals name="progress_exporting_key">
+ <item quantity="one">exporting key…</item>
+ <item quantity="other">exporting keys…</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_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 contained in this QR Code 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">OpenPGP: Decrypt File</string>
+ <string name="intent_import_key">OpenPGP: Import Key</string>
+ <string name="intent_send_encrypt">OpenPGP: Encrypt</string>
+ <string name="intent_send_decrypt">OpenPGP: Decrypt</string>
+
+ <!-- Remote API -->
+ <string name="api_no_apps">No registered applications!</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_save">Save</string>
+ <string name="api_settings_cancel">Cancel</string>
+ <string name="api_settings_revoke">Revoke access</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_register_text">The following application requests access to OpenPGP Keychain\'s API.\n\nAllow permanent access?</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 OpenPGP Keychain 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_encrypt">Encrypt to this contact</string>
+
+ <!-- Navigation Drawer -->
+ <string name="nav_contacts">Contacts</string>
+ <string name="nav_encrypt">Encrypt</string>
+ <string name="nav_decrypt">Decrypt</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>
+
+</resources>
diff --git a/OpenPGP-Keychain/src/main/res/values/styles.xml b/OpenPGP-Keychain/src/main/res/values/styles.xml
new file mode 100644
index 000000000..9e61bbef4
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/values/styles.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <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/OpenPGP-Keychain/src/main/res/xml/preferences.xml b/OpenPGP-Keychain/src/main/res/xml/preferences.xml
new file mode 100644
index 000000000..f5b46c232
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/res/xml/preferences.xml
@@ -0,0 +1,63 @@
+<?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/pass_phrase_cache_ttl_entries"
+ android:entryValues="@array/pass_phrase_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>
+ <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="defaultAsciiArmour"
+ 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> \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/main/res/xml/searchable_public_keys.xml b/OpenPGP-Keychain/src/main/res/xml/searchable_public_keys.xml
new file mode 100644
index 000000000..f07a809a0
--- /dev/null
+++ b/OpenPGP-Keychain/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/OpenPGP-Keychain/src/main/res/xml/searchable_secret_keys.xml b/OpenPGP-Keychain/src/main/res/xml/searchable_secret_keys.xml
new file mode 100644
index 000000000..59b9841db
--- /dev/null
+++ b/OpenPGP-Keychain/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