aboutsummaryrefslogtreecommitdiffstats
path: root/org_apg
diff options
context:
space:
mode:
Diffstat (limited to 'org_apg')
-rw-r--r--org_apg/.gitignore23
-rw-r--r--org_apg/AndroidManifest.xml232
-rw-r--r--org_apg/build.xml85
-rw-r--r--org_apg/libs/android-integration-supportv4.jarbin0 -> 8878 bytes
-rw-r--r--org_apg/libs/android-support-v4.jarbin0 -> 247894 bytes
-rw-r--r--org_apg/libs/bcprov-jdk16-146.jarbin0 -> 3759267 bytes
-rw-r--r--org_apg/pom.xml67
-rw-r--r--org_apg/project.properties11
-rw-r--r--org_apg/res/anim/push_left_in.xml20
-rw-r--r--org_apg/res/anim/push_left_out.xml20
-rw-r--r--org_apg/res/anim/push_right_in.xml20
-rw-r--r--org_apg/res/anim/push_right_out.xml20
-rw-r--r--org_apg/res/drawable-finger/btn_circle.xml32
-rw-r--r--org_apg/res/drawable-hdpi-finger/btn_circle_disable.pngbin0 -> 2631 bytes
-rw-r--r--org_apg/res/drawable-hdpi-finger/btn_circle_disable_focused.pngbin0 -> 3001 bytes
-rw-r--r--org_apg/res/drawable-hdpi-finger/btn_circle_normal.pngbin0 -> 1974 bytes
-rw-r--r--org_apg/res/drawable-hdpi-finger/btn_circle_pressed.pngbin0 -> 2624 bytes
-rw-r--r--org_apg/res/drawable-hdpi-finger/btn_circle_selected.pngbin0 -> 2554 bytes
-rw-r--r--org_apg/res/drawable-hdpi-finger/ic_btn_round_minus.pngbin0 -> 536 bytes
-rw-r--r--org_apg/res/drawable-hdpi-finger/ic_btn_round_plus.pngbin0 -> 1316 bytes
-rw-r--r--org_apg/res/drawable-hdpi/encrypted.pngbin0 -> 3561 bytes
-rw-r--r--org_apg/res/drawable-hdpi/encrypted_large.pngbin0 -> 5244 bytes
-rw-r--r--org_apg/res/drawable-hdpi/encrypted_small.pngbin0 -> 2187 bytes
-rw-r--r--org_apg/res/drawable-hdpi/ic_next.pngbin0 -> 1722 bytes
-rw-r--r--org_apg/res/drawable-hdpi/ic_previous.pngbin0 -> 1712 bytes
-rw-r--r--org_apg/res/drawable-hdpi/icon.pngbin0 -> 3949 bytes
-rw-r--r--org_apg/res/drawable-hdpi/key.pngbin0 -> 3675 bytes
-rw-r--r--org_apg/res/drawable-hdpi/key_large.pngbin0 -> 5550 bytes
-rw-r--r--org_apg/res/drawable-hdpi/key_small.pngbin0 -> 2088 bytes
-rw-r--r--org_apg/res/drawable-hdpi/overlay_error.pngbin0 -> 1986 bytes
-rw-r--r--org_apg/res/drawable-hdpi/overlay_ok.pngbin0 -> 1702 bytes
-rw-r--r--org_apg/res/drawable-hdpi/signed.pngbin0 -> 3858 bytes
-rw-r--r--org_apg/res/drawable-hdpi/signed_large.pngbin0 -> 5928 bytes
-rw-r--r--org_apg/res/drawable-hdpi/signed_small.pngbin0 -> 2219 bytes
-rw-r--r--org_apg/res/drawable-ldpi/encrypted.pngbin0 -> 1513 bytes
-rw-r--r--org_apg/res/drawable-ldpi/encrypted_large.pngbin0 -> 2486 bytes
-rw-r--r--org_apg/res/drawable-ldpi/encrypted_small.pngbin0 -> 1176 bytes
-rw-r--r--org_apg/res/drawable-ldpi/ic_next.pngbin0 -> 916 bytes
-rw-r--r--org_apg/res/drawable-ldpi/ic_previous.pngbin0 -> 922 bytes
-rw-r--r--org_apg/res/drawable-ldpi/icon.pngbin0 -> 1795 bytes
-rw-r--r--org_apg/res/drawable-ldpi/key.pngbin0 -> 1484 bytes
-rw-r--r--org_apg/res/drawable-ldpi/key_large.pngbin0 -> 2462 bytes
-rw-r--r--org_apg/res/drawable-ldpi/key_small.pngbin0 -> 1074 bytes
-rw-r--r--org_apg/res/drawable-ldpi/overlay_error.pngbin0 -> 1192 bytes
-rw-r--r--org_apg/res/drawable-ldpi/overlay_ok.pngbin0 -> 1038 bytes
-rw-r--r--org_apg/res/drawable-ldpi/signed.pngbin0 -> 1576 bytes
-rw-r--r--org_apg/res/drawable-ldpi/signed_large.pngbin0 -> 2611 bytes
-rw-r--r--org_apg/res/drawable-ldpi/signed_small.pngbin0 -> 1149 bytes
-rw-r--r--org_apg/res/drawable-mdpi-finger/btn_circle_disable.pngbin0 -> 938 bytes
-rw-r--r--org_apg/res/drawable-mdpi-finger/btn_circle_disable_focused.pngbin0 -> 1436 bytes
-rw-r--r--org_apg/res/drawable-mdpi-finger/btn_circle_normal.pngbin0 -> 1249 bytes
-rw-r--r--org_apg/res/drawable-mdpi-finger/btn_circle_pressed.pngbin0 -> 1613 bytes
-rw-r--r--org_apg/res/drawable-mdpi-finger/btn_circle_selected.pngbin0 -> 1645 bytes
-rw-r--r--org_apg/res/drawable-mdpi-finger/ic_btn_round_minus.pngbin0 -> 288 bytes
-rw-r--r--org_apg/res/drawable-mdpi-finger/ic_btn_round_plus.pngbin0 -> 526 bytes
-rw-r--r--org_apg/res/drawable-mdpi/encrypted.pngbin0 -> 2486 bytes
-rw-r--r--org_apg/res/drawable-mdpi/encrypted_large.pngbin0 -> 3561 bytes
-rw-r--r--org_apg/res/drawable-mdpi/encrypted_small.pngbin0 -> 1513 bytes
-rw-r--r--org_apg/res/drawable-mdpi/ic_next.pngbin0 -> 1360 bytes
-rw-r--r--org_apg/res/drawable-mdpi/ic_previous.pngbin0 -> 1352 bytes
-rw-r--r--org_apg/res/drawable-mdpi/icon.pngbin0 -> 2516 bytes
-rw-r--r--org_apg/res/drawable-mdpi/key.pngbin0 -> 2462 bytes
-rw-r--r--org_apg/res/drawable-mdpi/key_large.pngbin0 -> 3675 bytes
-rw-r--r--org_apg/res/drawable-mdpi/key_small.pngbin0 -> 1484 bytes
-rw-r--r--org_apg/res/drawable-mdpi/overlay_error.pngbin0 -> 1539 bytes
-rw-r--r--org_apg/res/drawable-mdpi/overlay_ok.pngbin0 -> 1305 bytes
-rw-r--r--org_apg/res/drawable-mdpi/signed.pngbin0 -> 2611 bytes
-rw-r--r--org_apg/res/drawable-mdpi/signed_large.pngbin0 -> 3858 bytes
-rw-r--r--org_apg/res/drawable-mdpi/signed_small.pngbin0 -> 1576 bytes
-rw-r--r--org_apg/res/drawable-xhdpi/icon.pngbin0 -> 5909 bytes
-rw-r--r--org_apg/res/drawable/btn_circle_disable.pngbin0 -> 938 bytes
-rw-r--r--org_apg/res/drawable/btn_circle_disable_focused.pngbin0 -> 1436 bytes
-rw-r--r--org_apg/res/drawable/btn_circle_normal.pngbin0 -> 1249 bytes
-rw-r--r--org_apg/res/drawable/btn_circle_pressed.pngbin0 -> 1613 bytes
-rw-r--r--org_apg/res/drawable/btn_circle_selected.pngbin0 -> 1645 bytes
-rw-r--r--org_apg/res/drawable/encrypted.pngbin0 -> 2486 bytes
-rw-r--r--org_apg/res/drawable/encrypted_large.pngbin0 -> 3561 bytes
-rw-r--r--org_apg/res/drawable/encrypted_small.pngbin0 -> 1513 bytes
-rw-r--r--org_apg/res/drawable/ic_btn_round_minus.pngbin0 -> 288 bytes
-rw-r--r--org_apg/res/drawable/ic_btn_round_plus.pngbin0 -> 526 bytes
-rw-r--r--org_apg/res/drawable/ic_launcher_folder.pngbin0 -> 2235 bytes
-rw-r--r--org_apg/res/drawable/ic_launcher_folder_small.pngbin0 -> 1522 bytes
-rw-r--r--org_apg/res/drawable/ic_next.pngbin0 -> 1360 bytes
-rw-r--r--org_apg/res/drawable/ic_previous.pngbin0 -> 1352 bytes
-rw-r--r--org_apg/res/drawable/key.pngbin0 -> 2462 bytes
-rw-r--r--org_apg/res/drawable/key_large.pngbin0 -> 3675 bytes
-rw-r--r--org_apg/res/drawable/key_small.pngbin0 -> 1484 bytes
-rw-r--r--org_apg/res/drawable/overlay_error.pngbin0 -> 1539 bytes
-rw-r--r--org_apg/res/drawable/overlay_ok.pngbin0 -> 1305 bytes
-rw-r--r--org_apg/res/drawable/signed.pngbin0 -> 2611 bytes
-rw-r--r--org_apg/res/drawable/signed_large.pngbin0 -> 3858 bytes
-rw-r--r--org_apg/res/drawable/signed_small.pngbin0 -> 1576 bytes
-rw-r--r--org_apg/res/layout/about_activity.xml98
-rw-r--r--org_apg/res/layout/account_item.xml35
-rw-r--r--org_apg/res/layout/add_account_dialog.xml31
-rw-r--r--org_apg/res/layout/create_key.xml64
-rw-r--r--org_apg/res/layout/decrypt.xml216
-rw-r--r--org_apg/res/layout/edit_key.xml87
-rw-r--r--org_apg/res/layout/edit_key_key_item.xml143
-rw-r--r--org_apg/res/layout/edit_key_section.xml66
-rw-r--r--org_apg/res/layout/edit_key_user_id_item.xml115
-rw-r--r--org_apg/res/layout/encrypt.xml382
-rw-r--r--org_apg/res/layout/file_dialog.xml50
-rw-r--r--org_apg/res/layout/filter_info.xml38
-rw-r--r--org_apg/res/layout/general.xml59
-rw-r--r--org_apg/res/layout/info.xml30
-rw-r--r--org_apg/res/layout/key_list.xml31
-rw-r--r--org_apg/res/layout/key_list_child_item_master_key.xml80
-rw-r--r--org_apg/res/layout/key_list_child_item_sub_key.xml72
-rw-r--r--org_apg/res/layout/key_list_child_item_user_id.xml33
-rw-r--r--org_apg/res/layout/key_list_group_item.xml53
-rw-r--r--org_apg/res/layout/key_server_editor.xml52
-rw-r--r--org_apg/res/layout/key_server_export_layout.xml42
-rw-r--r--org_apg/res/layout/key_server_preference.xml115
-rw-r--r--org_apg/res/layout/key_server_query_layout.xml53
-rw-r--r--org_apg/res/layout/key_server_query_result_item.xml97
-rw-r--r--org_apg/res/layout/key_server_query_result_user_id.xml26
-rw-r--r--org_apg/res/layout/mailbox_message_item.xml57
-rw-r--r--org_apg/res/layout/main.xml89
-rw-r--r--org_apg/res/layout/pass_phrase.xml37
-rw-r--r--org_apg/res/layout/select_public_key.xml55
-rw-r--r--org_apg/res/layout/select_public_key_item.xml82
-rw-r--r--org_apg/res/layout/select_secret_key.xml31
-rw-r--r--org_apg/res/layout/select_secret_key_item.xml75
-rw-r--r--org_apg/res/layout/sign_key_layout.xml40
-rw-r--r--org_apg/res/values-da/strings.xml261
-rw-r--r--org_apg/res/values-de/strings.xml269
-rw-r--r--org_apg/res/values-es/strings.xml303
-rw-r--r--org_apg/res/values-it/strings.xml314
-rw-r--r--org_apg/res/values-no/strings.xml313
-rw-r--r--org_apg/res/values-pt/strings.xml312
-rw-r--r--org_apg/res/values-sl/strings.xml304
-rw-r--r--org_apg/res/values-zh/strings.xml309
-rw-r--r--org_apg/res/values/arrays.xml214
-rw-r--r--org_apg/res/values/static_strings.xml7
-rw-r--r--org_apg/res/values/strings.xml333
-rw-r--r--org_apg/res/values/styles.xml30
-rw-r--r--org_apg/res/xml/apg_preferences.xml70
-rw-r--r--org_apg/res/xml/searchable_public_keys.xml22
-rw-r--r--org_apg/res/xml/searchable_secret_keys.xml22
-rw-r--r--org_apg/src/org/apg/Apg.java2283
-rw-r--r--org_apg/src/org/apg/ApgService.java643
-rw-r--r--org_apg/src/org/apg/AskForSecretKeyPassPhrase.java115
-rw-r--r--org_apg/src/org/apg/CachedPassPhrase.java48
-rw-r--r--org_apg/src/org/apg/Constants.java54
-rw-r--r--org_apg/src/org/apg/DataDestination.java81
-rw-r--r--org_apg/src/org/apg/DataSource.java104
-rw-r--r--org_apg/src/org/apg/FileDialog.java133
-rw-r--r--org_apg/src/org/apg/HkpKeyServer.java242
-rw-r--r--org_apg/src/org/apg/IApgService.aidl125
-rw-r--r--org_apg/src/org/apg/Id.java185
-rw-r--r--org_apg/src/org/apg/InputData.java25
-rw-r--r--org_apg/src/org/apg/KeyServer.java46
-rw-r--r--org_apg/src/org/apg/PausableThread.java35
-rw-r--r--org_apg/src/org/apg/PositionAwareInputStream.java67
-rw-r--r--org_apg/src/org/apg/Preferences.java170
-rw-r--r--org_apg/src/org/apg/Primes.java185
-rw-r--r--org_apg/src/org/apg/ProgressDialogUpdater.java25
-rw-r--r--org_apg/src/org/apg/Service.java78
-rw-r--r--org_apg/src/org/apg/provider/Accounts.java27
-rw-r--r--org_apg/src/org/apg/provider/ApgServiceBlobDatabase.java54
-rw-r--r--org_apg/src/org/apg/provider/ApgServiceBlobProvider.java138
-rw-r--r--org_apg/src/org/apg/provider/DataProvider.java381
-rw-r--r--org_apg/src/org/apg/provider/Database.java605
-rw-r--r--org_apg/src/org/apg/provider/KeyRings.java33
-rw-r--r--org_apg/src/org/apg/provider/Keys.java51
-rw-r--r--org_apg/src/org/apg/provider/UserIds.java31
-rw-r--r--org_apg/src/org/apg/ui/AboutActivity.java51
-rw-r--r--org_apg/src/org/apg/ui/BaseActivity.java436
-rw-r--r--org_apg/src/org/apg/ui/DecryptActivity.java807
-rw-r--r--org_apg/src/org/apg/ui/EditKeyActivity.java292
-rw-r--r--org_apg/src/org/apg/ui/EncryptActivity.java998
-rw-r--r--org_apg/src/org/apg/ui/GeneralActivity.java177
-rw-r--r--org_apg/src/org/apg/ui/ImportFromQRCodeActivity.java138
-rw-r--r--org_apg/src/org/apg/ui/KeyListActivity.java768
-rw-r--r--org_apg/src/org/apg/ui/KeyServerPreferenceActivity.java125
-rw-r--r--org_apg/src/org/apg/ui/KeyServerQueryActivity.java297
-rw-r--r--org_apg/src/org/apg/ui/MailListActivity.java222
-rw-r--r--org_apg/src/org/apg/ui/MainActivity.java419
-rw-r--r--org_apg/src/org/apg/ui/PreferencesActivity.java274
-rw-r--r--org_apg/src/org/apg/ui/PublicKeyListActivity.java191
-rw-r--r--org_apg/src/org/apg/ui/SecretKeyListActivity.java203
-rw-r--r--org_apg/src/org/apg/ui/SelectPublicKeyListActivity.java172
-rw-r--r--org_apg/src/org/apg/ui/SelectPublicKeyListAdapter.java226
-rw-r--r--org_apg/src/org/apg/ui/SelectSecretKeyListActivity.java115
-rw-r--r--org_apg/src/org/apg/ui/SelectSecretKeyListAdapter.java176
-rw-r--r--org_apg/src/org/apg/ui/SendKeyActivity.java100
-rw-r--r--org_apg/src/org/apg/ui/SignKeyActivity.java294
-rw-r--r--org_apg/src/org/apg/ui/widget/Editor.java25
-rw-r--r--org_apg/src/org/apg/ui/widget/IntegerListPreference.java95
-rw-r--r--org_apg/src/org/apg/ui/widget/KeyEditor.java233
-rw-r--r--org_apg/src/org/apg/ui/widget/KeyServerEditor.java78
-rw-r--r--org_apg/src/org/apg/ui/widget/SectionView.java335
-rw-r--r--org_apg/src/org/apg/ui/widget/UserIdEditor.java192
-rw-r--r--org_apg/src/org/apg/util/ApgCon.java836
-rw-r--r--org_apg/src/org/apg/util/ApgConInterface.java7
-rw-r--r--org_apg/src/org/apg/util/Choice.java45
-rw-r--r--org_apg/src/org/apg/util/Compatibility.java79
-rw-r--r--org_apg/src/org/apg/util/IterableIterator.java31
199 files changed, 20548 insertions, 0 deletions
diff --git a/org_apg/.gitignore b/org_apg/.gitignore
new file mode 100644
index 000000000..2e423e1a3
--- /dev/null
+++ b/org_apg/.gitignore
@@ -0,0 +1,23 @@
+#Android generated
+bin
+gen
+obj
+libs/armeabi
+lint.xml
+local.properties
+
+#Eclipse
+.project
+.classpath
+.settings
+
+#IntelliJ IDEA
+.idea
+*.iml
+
+#Maven
+target
+release.properties
+
+#Mac
+.DS_Store \ No newline at end of file
diff --git a/org_apg/AndroidManifest.xml b/org_apg/AndroidManifest.xml
new file mode 100644
index 000000000..4ab346b7d
--- /dev/null
+++ b/org_apg/AndroidManifest.xml
@@ -0,0 +1,232 @@
+<?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. -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ android:installLocation="auto"
+ package="org.apg"
+ android:versionCode="11000"
+ android:versionName="1.1" >
+
+ <uses-sdk
+ android:minSdkVersion="4"
+ android:targetSdkVersion="14" />
+
+ <permission
+ android:name="org.thialfihar.android.apg.permission.READ_KEY_DETAILS"
+ android:description="@string/permission_read_key_details_description"
+ android:label="@string/permission_read_key_details_label"
+ android:protectionLevel="dangerous" />
+ <permission
+ android:name="org.thialfihar.android.apg.permission.STORE_BLOBS"
+ android:description="@string/permission_store_blobs_description"
+ android:label="@string/permission_store_blobs_label"
+ android:protectionLevel="dangerous" />
+
+ <uses-permission android:name="com.google.android.providers.gmail.permission.READ_GMAIL" />
+ <uses-permission android:name="com.google.android.gm.permission.READ_GMAIL" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="com.fsck.k9.permission.READ_ATTACHMENT" />
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="org.thialfihar.android.apg.permission.STORE_BLOBS" />
+
+ <application
+ android:icon="@drawable/icon"
+ android:label="@string/app_name" >
+ <activity
+ android:name=".ui.MainActivity"
+ android:configChanges="keyboardHidden|orientation|keyboard"
+ android:label="@string/app_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ <activity
+ android:name=".ui.PublicKeyListActivity"
+ android:configChanges="keyboardHidden|orientation|keyboard"
+ android:label="@string/title_managePublicKeys"
+ 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_public_keys" />
+ </activity>
+ <activity
+ android:name=".ui.SecretKeyListActivity"
+ android:configChanges="keyboardHidden|orientation|keyboard"
+ android:label="@string/title_manageSecretKeys"
+ 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="keyboardHidden|orientation|keyboard"
+ android:label="@string/title_editKey" />
+ <activity
+ android:name=".ui.SelectPublicKeyListActivity"
+ android:configChanges="keyboardHidden|orientation|keyboard"
+ android:label="@string/title_selectRecipients"
+ android:launchMode="singleTop" >
+ <intent-filter>
+ <action android:name="org.thialfihar.android.apg.intent.SELECT_PUBLIC_KEYS" />
+
+ <category android:name="android.intent.category.DEFAULT" />
+ </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.SelectSecretKeyListActivity"
+ android:configChanges="keyboardHidden|orientation|keyboard"
+ android:label="@string/title_selectSignature"
+ android:launchMode="singleTop" >
+ <intent-filter>
+ <action android:name="org.thialfihar.android.apg.intent.SELECT_SECRET_KEY" />
+
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <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="keyboardHidden|orientation|keyboard"
+ android:label="@string/title_encrypt" >
+ <intent-filter>
+ <action android:name="org.thialfihar.android.apg.intent.ENCRYPT" />
+ <action android:name="org.thialfihar.android.apg.intent.ENCRYPT_FILE" />
+ <action android:name="org.thialfihar.android.apg.intent.ENCRYPT_AND_RETURN" />
+ <action android:name="org.thialfihar.android.apg.intent.GENERATE_SIGNATURE" />
+
+ <category android:name="android.intent.category.DEFAULT" />
+
+ <data android:mimeType="*/*" />
+ </intent-filter>
+ </activity>
+ <activity
+ android:name=".ui.DecryptActivity"
+ android:configChanges="keyboardHidden|orientation|keyboard"
+ android:label="@string/title_decrypt" >
+ <intent-filter>
+ <action android:name="org.thialfihar.android.apg.intent.DECRYPT" />
+ <action android:name="org.thialfihar.android.apg.intent.DECRYPT_FILE" />
+ <action android:name="org.thialfihar.android.apg.intent.DECRYPT_AND_RETURN" />
+
+ <category android:name="android.intent.category.DEFAULT" />
+
+ <data android:mimeType="*/*" />
+ </intent-filter>
+ </activity>
+ <activity
+ android:name=".ui.GeneralActivity"
+ android:configChanges="keyboardHidden|orientation|keyboard"
+ android:label="@string/app_name"
+ android:theme="@android:style/Theme.Dialog" >
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+ <action android:name="android.intent.action.SEND" />
+
+ <category android:name="android.intent.category.DEFAULT" />
+
+ <data
+ android:mimeType="*/*"
+ android:scheme="file" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+ <action android:name="android.intent.action.SEND" />
+
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+ <action android:name="android.intent.action.SEND" />
+
+ <category android:name="android.intent.category.DEFAULT" />
+
+ <data
+ android:mimeType="text/*"
+ android:scheme="" />
+ </intent-filter>
+ </activity>
+ <activity
+ android:name=".ui.MailListActivity"
+ android:configChanges="keyboardHidden|orientation|keyboard"
+ android:label="@string/title_mailInbox" />
+ <activity
+ android:name=".ui.KeyServerQueryActivity"
+ android:configChanges="keyboardHidden|orientation|keyboard"
+ android:label="@string/title_keyServerQuery" />
+ <activity
+ android:name=".ui.SendKeyActivity"
+ android:configChanges="keyboardHidden|orientation|keyboard"
+ android:label="@string/title_sendKey" />
+ <activity
+ android:name=".ui.PreferencesActivity"
+ android:configChanges="keyboardHidden|orientation|keyboard"
+ android:label="@string/title_preferences" />
+ <activity
+ android:name=".ui.KeyServerPreferenceActivity"
+ android:configChanges="keyboardHidden|orientation|keyboard"
+ android:label="@string/title_keyServerPreference" />
+ <activity
+ android:name=".ui.SignKeyActivity"
+ android:configChanges="keyboardHidden|orientation|keyboard"
+ android:label="@string/title_signKey" />
+ <activity
+ android:name=".ui.ImportFromQRCodeActivity"
+ android:configChanges="keyboardHidden|orientation|keyboard"
+ android:label="@string/title_importFromQRCode" />
+ <activity
+ android:name=".ui.AboutActivity"
+ android:excludeFromRecents="true"
+ android:label="@string/title_about"
+ android:theme="@android:style/Theme.Dialog" />
+
+ <service android:name=".Service" />
+ <service
+ android:name=".ApgService"
+ android:enabled="true"
+ android:exported="true"
+ android:permission="org.thialfihar.android.apg.permission.READ_KEY_DETAILS"
+ android:process=":remote" >
+ <intent-filter>
+ <action android:name="org.thialfihar.android.apg.IApgService" />
+ </intent-filter>
+
+ <meta-data
+ android:name="api_version"
+ android:value="2" />
+ </service>
+
+ <provider
+ android:name=".provider.DataProvider"
+ android:authorities="org.thialfihar.android.apg.provider"
+ android:readPermission="org.thialfihar.android.apg.permission.READ_KEY_DETAILS" />
+ <provider
+ android:name=".provider.ApgServiceBlobProvider"
+ android:authorities="org.thialfihar.android.apg.provider.apgserviceblobprovider"
+ android:permission="org.thialfihar.android.apg.permission.STORE_BLOBS" />
+ </application>
+
+</manifest> \ No newline at end of file
diff --git a/org_apg/build.xml b/org_apg/build.xml
new file mode 100644
index 000000000..f05ca7cf7
--- /dev/null
+++ b/org_apg/build.xml
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project name="APG" default="help">
+
+ <!-- The local.properties file is created and updated by the 'android' tool.
+ It contains the path to the SDK. It should *NOT* be checked into
+ Version Control Systems. -->
+ <property file="local.properties" />
+
+ <!-- The ant.properties file can be created by you. It is only edited by the
+ 'android' tool to add properties to it.
+ This is the place to change some Ant specific build properties.
+ Here are some properties you may want to change/update:
+
+ source.dir
+ The name of the source directory. Default is 'src'.
+ out.dir
+ The name of the output directory. Default is 'bin'.
+
+ For other overridable properties, look at the beginning of the rules
+ files in the SDK, at tools/ant/build.xml
+
+ Properties related to the SDK location or the project target should
+ be updated using the 'android' tool with the 'update' action.
+
+ This file is an integral part of the build system for your
+ application and should be checked into Version Control Systems.
+
+ -->
+ <property file="ant.properties" />
+
+ <!-- The project.properties file is created and updated by the 'android'
+ tool, as well as ADT.
+
+ This contains project specific properties such as project target, and library
+ dependencies. Lower level build properties are stored in ant.properties
+ (or in .classpath for Eclipse projects).
+
+ This file is an integral part of the build system for your
+ application and should be checked into Version Control Systems. -->
+ <loadproperties srcFile="project.properties" />
+
+ <!-- quick check on sdk.dir -->
+ <fail
+ message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through an env var"
+ unless="sdk.dir"
+ />
+
+
+<!-- extension targets. Uncomment the ones where you want to do custom work
+ in between standard targets -->
+<!--
+ <target name="-pre-build">
+ </target>
+ <target name="-pre-compile">
+ </target>
+
+ /* This is typically used for code obfuscation.
+ Compiled code location: ${out.classes.absolute.dir}
+ If this is not done in place, override ${out.dex.input.absolute.dir} */
+ <target name="-post-compile">
+ </target>
+-->
+
+ <!-- Import the actual build file.
+
+ To customize existing targets, there are two options:
+ - Customize only one target:
+ - copy/paste the target into this file, *before* the
+ <import> task.
+ - customize it to your needs.
+ - Customize the whole content of build.xml
+ - copy/paste the content of the rules files (minus the top node)
+ into this file, replacing the <import> task.
+ - customize to your needs.
+
+ ***********************
+ ****** IMPORTANT ******
+ ***********************
+ In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
+ in order to avoid having your file be overridden by tools such as "android update project"
+ -->
+ <!-- version-tag: 1 -->
+ <import file="${sdk.dir}/tools/ant/build.xml" />
+
+</project>
diff --git a/org_apg/libs/android-integration-supportv4.jar b/org_apg/libs/android-integration-supportv4.jar
new file mode 100644
index 000000000..4a7f1a39c
--- /dev/null
+++ b/org_apg/libs/android-integration-supportv4.jar
Binary files differ
diff --git a/org_apg/libs/android-support-v4.jar b/org_apg/libs/android-support-v4.jar
new file mode 100644
index 000000000..d006198e6
--- /dev/null
+++ b/org_apg/libs/android-support-v4.jar
Binary files differ
diff --git a/org_apg/libs/bcprov-jdk16-146.jar b/org_apg/libs/bcprov-jdk16-146.jar
new file mode 100644
index 000000000..66cb1a73b
--- /dev/null
+++ b/org_apg/libs/bcprov-jdk16-146.jar
Binary files differ
diff --git a/org_apg/pom.xml b/org_apg/pom.xml
new file mode 100644
index 000000000..cadb2a38d
--- /dev/null
+++ b/org_apg/pom.xml
@@ -0,0 +1,67 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <version>1.0.9-SNAPSHOT</version>
+ <groupId>org.thialfihar.android</groupId>
+ <artifactId>apg</artifactId>
+ <packaging>apk</packaging>
+ <name>APG</name>
+ <description>
+ </description>
+ <url>http://code.google.com/p/android-privacy-guard/</url>
+
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <android.version>1.6_r2</android.version>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>com.google.android</groupId>
+ <artifactId>android</artifactId>
+ <scope>provided</scope>
+ <version>1.6_r2</version>
+ </dependency>
+ <dependency>
+ <groupId>com.madgag</groupId>
+ <artifactId>scprov-jdk15</artifactId>
+ <version>1.46.99.4-UNOFFICIAL-ROBERTO-RELEASE-SNAPSHOT</version>
+ <type>jar</type>
+ </dependency>
+ <dependency>
+ <groupId>com.google.zxing</groupId>
+ <artifactId>android-integration</artifactId>
+ <version>1.6-SNAPSHOT</version>
+ <type>jar</type>
+ </dependency>
+ </dependencies>
+ <build>
+ <sourceDirectory>src</sourceDirectory>
+ <testSourceDirectory>test</testSourceDirectory>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>2.3.2</version>
+ <configuration>
+ <target>1.6</target>
+ <source>1.6</source>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>com.jayway.maven.plugins.android.generation2</groupId>
+ <artifactId>android-maven-plugin</artifactId>
+ <version>3.0.0-alpha-11</version>
+ <configuration>
+ <sdk>
+ <path>${user.home}/android-sdk-linux_x86/</path>
+ <platform>4</platform>
+ </sdk>
+ <deleteConflictingFiles>true</deleteConflictingFiles>
+ <extractDuplicates>true</extractDuplicates>
+ </configuration>
+ <extensions>true</extensions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/org_apg/project.properties b/org_apg/project.properties
new file mode 100644
index 000000000..8da376af8
--- /dev/null
+++ b/org_apg/project.properties
@@ -0,0 +1,11 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system use,
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+
+# Project target.
+target=android-15
diff --git a/org_apg/res/anim/push_left_in.xml b/org_apg/res/anim/push_left_in.xml
new file mode 100644
index 000000000..45fb4875a
--- /dev/null
+++ b/org_apg/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/org_apg/res/anim/push_left_out.xml b/org_apg/res/anim/push_left_out.xml
new file mode 100644
index 000000000..845679f16
--- /dev/null
+++ b/org_apg/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/org_apg/res/anim/push_right_in.xml b/org_apg/res/anim/push_right_in.xml
new file mode 100644
index 000000000..09a244406
--- /dev/null
+++ b/org_apg/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/org_apg/res/anim/push_right_out.xml b/org_apg/res/anim/push_right_out.xml
new file mode 100644
index 000000000..e8893a69a
--- /dev/null
+++ b/org_apg/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/org_apg/res/drawable-finger/btn_circle.xml b/org_apg/res/drawable-finger/btn_circle.xml
new file mode 100644
index 000000000..6c3c7fc1a
--- /dev/null
+++ b/org_apg/res/drawable-finger/btn_circle.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_window_focused="false" android:state_enabled="true"
+ android:drawable="@drawable/btn_circle_normal" />
+ <item android:state_window_focused="false" android:state_enabled="false"
+ android:drawable="@drawable/btn_circle_disable" />
+ <item android:state_pressed="true"
+ android:drawable="@drawable/btn_circle_pressed" />
+ <item android:state_focused="true" android:state_enabled="true"
+ android:drawable="@drawable/btn_circle_selected" />
+ <item android:state_enabled="true"
+ android:drawable="@drawable/btn_circle_normal" />
+ <item android:state_focused="true"
+ android:drawable="@drawable/btn_circle_disable_focused" />
+ <item
+ android:drawable="@drawable/btn_circle_disable" />
+</selector>
diff --git a/org_apg/res/drawable-hdpi-finger/btn_circle_disable.png b/org_apg/res/drawable-hdpi-finger/btn_circle_disable.png
new file mode 100644
index 000000000..ae063b545
--- /dev/null
+++ b/org_apg/res/drawable-hdpi-finger/btn_circle_disable.png
Binary files differ
diff --git a/org_apg/res/drawable-hdpi-finger/btn_circle_disable_focused.png b/org_apg/res/drawable-hdpi-finger/btn_circle_disable_focused.png
new file mode 100644
index 000000000..7a5d4fe4e
--- /dev/null
+++ b/org_apg/res/drawable-hdpi-finger/btn_circle_disable_focused.png
Binary files differ
diff --git a/org_apg/res/drawable-hdpi-finger/btn_circle_normal.png b/org_apg/res/drawable-hdpi-finger/btn_circle_normal.png
new file mode 100644
index 000000000..5eda66883
--- /dev/null
+++ b/org_apg/res/drawable-hdpi-finger/btn_circle_normal.png
Binary files differ
diff --git a/org_apg/res/drawable-hdpi-finger/btn_circle_pressed.png b/org_apg/res/drawable-hdpi-finger/btn_circle_pressed.png
new file mode 100644
index 000000000..88848bac2
--- /dev/null
+++ b/org_apg/res/drawable-hdpi-finger/btn_circle_pressed.png
Binary files differ
diff --git a/org_apg/res/drawable-hdpi-finger/btn_circle_selected.png b/org_apg/res/drawable-hdpi-finger/btn_circle_selected.png
new file mode 100644
index 000000000..74690705f
--- /dev/null
+++ b/org_apg/res/drawable-hdpi-finger/btn_circle_selected.png
Binary files differ
diff --git a/org_apg/res/drawable-hdpi-finger/ic_btn_round_minus.png b/org_apg/res/drawable-hdpi-finger/ic_btn_round_minus.png
new file mode 100644
index 000000000..27af3faf4
--- /dev/null
+++ b/org_apg/res/drawable-hdpi-finger/ic_btn_round_minus.png
Binary files differ
diff --git a/org_apg/res/drawable-hdpi-finger/ic_btn_round_plus.png b/org_apg/res/drawable-hdpi-finger/ic_btn_round_plus.png
new file mode 100644
index 000000000..b24168c32
--- /dev/null
+++ b/org_apg/res/drawable-hdpi-finger/ic_btn_round_plus.png
Binary files differ
diff --git a/org_apg/res/drawable-hdpi/encrypted.png b/org_apg/res/drawable-hdpi/encrypted.png
new file mode 100644
index 000000000..541781cd1
--- /dev/null
+++ b/org_apg/res/drawable-hdpi/encrypted.png
Binary files differ
diff --git a/org_apg/res/drawable-hdpi/encrypted_large.png b/org_apg/res/drawable-hdpi/encrypted_large.png
new file mode 100644
index 000000000..209278377
--- /dev/null
+++ b/org_apg/res/drawable-hdpi/encrypted_large.png
Binary files differ
diff --git a/org_apg/res/drawable-hdpi/encrypted_small.png b/org_apg/res/drawable-hdpi/encrypted_small.png
new file mode 100644
index 000000000..3ff8e9b97
--- /dev/null
+++ b/org_apg/res/drawable-hdpi/encrypted_small.png
Binary files differ
diff --git a/org_apg/res/drawable-hdpi/ic_next.png b/org_apg/res/drawable-hdpi/ic_next.png
new file mode 100644
index 000000000..d71058055
--- /dev/null
+++ b/org_apg/res/drawable-hdpi/ic_next.png
Binary files differ
diff --git a/org_apg/res/drawable-hdpi/ic_previous.png b/org_apg/res/drawable-hdpi/ic_previous.png
new file mode 100644
index 000000000..d610e4667
--- /dev/null
+++ b/org_apg/res/drawable-hdpi/ic_previous.png
Binary files differ
diff --git a/org_apg/res/drawable-hdpi/icon.png b/org_apg/res/drawable-hdpi/icon.png
new file mode 100644
index 000000000..d36080251
--- /dev/null
+++ b/org_apg/res/drawable-hdpi/icon.png
Binary files differ
diff --git a/org_apg/res/drawable-hdpi/key.png b/org_apg/res/drawable-hdpi/key.png
new file mode 100644
index 000000000..af4742ec0
--- /dev/null
+++ b/org_apg/res/drawable-hdpi/key.png
Binary files differ
diff --git a/org_apg/res/drawable-hdpi/key_large.png b/org_apg/res/drawable-hdpi/key_large.png
new file mode 100644
index 000000000..590f7d5a4
--- /dev/null
+++ b/org_apg/res/drawable-hdpi/key_large.png
Binary files differ
diff --git a/org_apg/res/drawable-hdpi/key_small.png b/org_apg/res/drawable-hdpi/key_small.png
new file mode 100644
index 000000000..6966048a1
--- /dev/null
+++ b/org_apg/res/drawable-hdpi/key_small.png
Binary files differ
diff --git a/org_apg/res/drawable-hdpi/overlay_error.png b/org_apg/res/drawable-hdpi/overlay_error.png
new file mode 100644
index 000000000..e6d7e60ba
--- /dev/null
+++ b/org_apg/res/drawable-hdpi/overlay_error.png
Binary files differ
diff --git a/org_apg/res/drawable-hdpi/overlay_ok.png b/org_apg/res/drawable-hdpi/overlay_ok.png
new file mode 100644
index 000000000..0672f869d
--- /dev/null
+++ b/org_apg/res/drawable-hdpi/overlay_ok.png
Binary files differ
diff --git a/org_apg/res/drawable-hdpi/signed.png b/org_apg/res/drawable-hdpi/signed.png
new file mode 100644
index 000000000..ab9495e7b
--- /dev/null
+++ b/org_apg/res/drawable-hdpi/signed.png
Binary files differ
diff --git a/org_apg/res/drawable-hdpi/signed_large.png b/org_apg/res/drawable-hdpi/signed_large.png
new file mode 100644
index 000000000..c209f4167
--- /dev/null
+++ b/org_apg/res/drawable-hdpi/signed_large.png
Binary files differ
diff --git a/org_apg/res/drawable-hdpi/signed_small.png b/org_apg/res/drawable-hdpi/signed_small.png
new file mode 100644
index 000000000..54c4906e8
--- /dev/null
+++ b/org_apg/res/drawable-hdpi/signed_small.png
Binary files differ
diff --git a/org_apg/res/drawable-ldpi/encrypted.png b/org_apg/res/drawable-ldpi/encrypted.png
new file mode 100644
index 000000000..bcd8cfc8e
--- /dev/null
+++ b/org_apg/res/drawable-ldpi/encrypted.png
Binary files differ
diff --git a/org_apg/res/drawable-ldpi/encrypted_large.png b/org_apg/res/drawable-ldpi/encrypted_large.png
new file mode 100644
index 000000000..34c3d3f97
--- /dev/null
+++ b/org_apg/res/drawable-ldpi/encrypted_large.png
Binary files differ
diff --git a/org_apg/res/drawable-ldpi/encrypted_small.png b/org_apg/res/drawable-ldpi/encrypted_small.png
new file mode 100644
index 000000000..5e7294a4b
--- /dev/null
+++ b/org_apg/res/drawable-ldpi/encrypted_small.png
Binary files differ
diff --git a/org_apg/res/drawable-ldpi/ic_next.png b/org_apg/res/drawable-ldpi/ic_next.png
new file mode 100644
index 000000000..474ed8faa
--- /dev/null
+++ b/org_apg/res/drawable-ldpi/ic_next.png
Binary files differ
diff --git a/org_apg/res/drawable-ldpi/ic_previous.png b/org_apg/res/drawable-ldpi/ic_previous.png
new file mode 100644
index 000000000..6fd885e6b
--- /dev/null
+++ b/org_apg/res/drawable-ldpi/ic_previous.png
Binary files differ
diff --git a/org_apg/res/drawable-ldpi/icon.png b/org_apg/res/drawable-ldpi/icon.png
new file mode 100644
index 000000000..5b9c33f0c
--- /dev/null
+++ b/org_apg/res/drawable-ldpi/icon.png
Binary files differ
diff --git a/org_apg/res/drawable-ldpi/key.png b/org_apg/res/drawable-ldpi/key.png
new file mode 100644
index 000000000..c806b6041
--- /dev/null
+++ b/org_apg/res/drawable-ldpi/key.png
Binary files differ
diff --git a/org_apg/res/drawable-ldpi/key_large.png b/org_apg/res/drawable-ldpi/key_large.png
new file mode 100644
index 000000000..aa499a5e1
--- /dev/null
+++ b/org_apg/res/drawable-ldpi/key_large.png
Binary files differ
diff --git a/org_apg/res/drawable-ldpi/key_small.png b/org_apg/res/drawable-ldpi/key_small.png
new file mode 100644
index 000000000..073b95029
--- /dev/null
+++ b/org_apg/res/drawable-ldpi/key_small.png
Binary files differ
diff --git a/org_apg/res/drawable-ldpi/overlay_error.png b/org_apg/res/drawable-ldpi/overlay_error.png
new file mode 100644
index 000000000..e5a88e18f
--- /dev/null
+++ b/org_apg/res/drawable-ldpi/overlay_error.png
Binary files differ
diff --git a/org_apg/res/drawable-ldpi/overlay_ok.png b/org_apg/res/drawable-ldpi/overlay_ok.png
new file mode 100644
index 000000000..63374d47f
--- /dev/null
+++ b/org_apg/res/drawable-ldpi/overlay_ok.png
Binary files differ
diff --git a/org_apg/res/drawable-ldpi/signed.png b/org_apg/res/drawable-ldpi/signed.png
new file mode 100644
index 000000000..4202c3f97
--- /dev/null
+++ b/org_apg/res/drawable-ldpi/signed.png
Binary files differ
diff --git a/org_apg/res/drawable-ldpi/signed_large.png b/org_apg/res/drawable-ldpi/signed_large.png
new file mode 100644
index 000000000..d2917644c
--- /dev/null
+++ b/org_apg/res/drawable-ldpi/signed_large.png
Binary files differ
diff --git a/org_apg/res/drawable-ldpi/signed_small.png b/org_apg/res/drawable-ldpi/signed_small.png
new file mode 100644
index 000000000..19d45f8da
--- /dev/null
+++ b/org_apg/res/drawable-ldpi/signed_small.png
Binary files differ
diff --git a/org_apg/res/drawable-mdpi-finger/btn_circle_disable.png b/org_apg/res/drawable-mdpi-finger/btn_circle_disable.png
new file mode 100644
index 000000000..33b74a66c
--- /dev/null
+++ b/org_apg/res/drawable-mdpi-finger/btn_circle_disable.png
Binary files differ
diff --git a/org_apg/res/drawable-mdpi-finger/btn_circle_disable_focused.png b/org_apg/res/drawable-mdpi-finger/btn_circle_disable_focused.png
new file mode 100644
index 000000000..005ad8dca
--- /dev/null
+++ b/org_apg/res/drawable-mdpi-finger/btn_circle_disable_focused.png
Binary files differ
diff --git a/org_apg/res/drawable-mdpi-finger/btn_circle_normal.png b/org_apg/res/drawable-mdpi-finger/btn_circle_normal.png
new file mode 100644
index 000000000..fc5af1c9f
--- /dev/null
+++ b/org_apg/res/drawable-mdpi-finger/btn_circle_normal.png
Binary files differ
diff --git a/org_apg/res/drawable-mdpi-finger/btn_circle_pressed.png b/org_apg/res/drawable-mdpi-finger/btn_circle_pressed.png
new file mode 100644
index 000000000..8f40afdfc
--- /dev/null
+++ b/org_apg/res/drawable-mdpi-finger/btn_circle_pressed.png
Binary files differ
diff --git a/org_apg/res/drawable-mdpi-finger/btn_circle_selected.png b/org_apg/res/drawable-mdpi-finger/btn_circle_selected.png
new file mode 100644
index 000000000..c74fac227
--- /dev/null
+++ b/org_apg/res/drawable-mdpi-finger/btn_circle_selected.png
Binary files differ
diff --git a/org_apg/res/drawable-mdpi-finger/ic_btn_round_minus.png b/org_apg/res/drawable-mdpi-finger/ic_btn_round_minus.png
new file mode 100644
index 000000000..96dbb17d2
--- /dev/null
+++ b/org_apg/res/drawable-mdpi-finger/ic_btn_round_minus.png
Binary files differ
diff --git a/org_apg/res/drawable-mdpi-finger/ic_btn_round_plus.png b/org_apg/res/drawable-mdpi-finger/ic_btn_round_plus.png
new file mode 100644
index 000000000..1ec8a956a
--- /dev/null
+++ b/org_apg/res/drawable-mdpi-finger/ic_btn_round_plus.png
Binary files differ
diff --git a/org_apg/res/drawable-mdpi/encrypted.png b/org_apg/res/drawable-mdpi/encrypted.png
new file mode 100644
index 000000000..34c3d3f97
--- /dev/null
+++ b/org_apg/res/drawable-mdpi/encrypted.png
Binary files differ
diff --git a/org_apg/res/drawable-mdpi/encrypted_large.png b/org_apg/res/drawable-mdpi/encrypted_large.png
new file mode 100644
index 000000000..541781cd1
--- /dev/null
+++ b/org_apg/res/drawable-mdpi/encrypted_large.png
Binary files differ
diff --git a/org_apg/res/drawable-mdpi/encrypted_small.png b/org_apg/res/drawable-mdpi/encrypted_small.png
new file mode 100644
index 000000000..bcd8cfc8e
--- /dev/null
+++ b/org_apg/res/drawable-mdpi/encrypted_small.png
Binary files differ
diff --git a/org_apg/res/drawable-mdpi/ic_next.png b/org_apg/res/drawable-mdpi/ic_next.png
new file mode 100644
index 000000000..8271c1380
--- /dev/null
+++ b/org_apg/res/drawable-mdpi/ic_next.png
Binary files differ
diff --git a/org_apg/res/drawable-mdpi/ic_previous.png b/org_apg/res/drawable-mdpi/ic_previous.png
new file mode 100644
index 000000000..ef90db972
--- /dev/null
+++ b/org_apg/res/drawable-mdpi/ic_previous.png
Binary files differ
diff --git a/org_apg/res/drawable-mdpi/icon.png b/org_apg/res/drawable-mdpi/icon.png
new file mode 100644
index 000000000..fec2d62ce
--- /dev/null
+++ b/org_apg/res/drawable-mdpi/icon.png
Binary files differ
diff --git a/org_apg/res/drawable-mdpi/key.png b/org_apg/res/drawable-mdpi/key.png
new file mode 100644
index 000000000..aa499a5e1
--- /dev/null
+++ b/org_apg/res/drawable-mdpi/key.png
Binary files differ
diff --git a/org_apg/res/drawable-mdpi/key_large.png b/org_apg/res/drawable-mdpi/key_large.png
new file mode 100644
index 000000000..af4742ec0
--- /dev/null
+++ b/org_apg/res/drawable-mdpi/key_large.png
Binary files differ
diff --git a/org_apg/res/drawable-mdpi/key_small.png b/org_apg/res/drawable-mdpi/key_small.png
new file mode 100644
index 000000000..c806b6041
--- /dev/null
+++ b/org_apg/res/drawable-mdpi/key_small.png
Binary files differ
diff --git a/org_apg/res/drawable-mdpi/overlay_error.png b/org_apg/res/drawable-mdpi/overlay_error.png
new file mode 100644
index 000000000..5fe017433
--- /dev/null
+++ b/org_apg/res/drawable-mdpi/overlay_error.png
Binary files differ
diff --git a/org_apg/res/drawable-mdpi/overlay_ok.png b/org_apg/res/drawable-mdpi/overlay_ok.png
new file mode 100644
index 000000000..b4f332260
--- /dev/null
+++ b/org_apg/res/drawable-mdpi/overlay_ok.png
Binary files differ
diff --git a/org_apg/res/drawable-mdpi/signed.png b/org_apg/res/drawable-mdpi/signed.png
new file mode 100644
index 000000000..d2917644c
--- /dev/null
+++ b/org_apg/res/drawable-mdpi/signed.png
Binary files differ
diff --git a/org_apg/res/drawable-mdpi/signed_large.png b/org_apg/res/drawable-mdpi/signed_large.png
new file mode 100644
index 000000000..ab9495e7b
--- /dev/null
+++ b/org_apg/res/drawable-mdpi/signed_large.png
Binary files differ
diff --git a/org_apg/res/drawable-mdpi/signed_small.png b/org_apg/res/drawable-mdpi/signed_small.png
new file mode 100644
index 000000000..4202c3f97
--- /dev/null
+++ b/org_apg/res/drawable-mdpi/signed_small.png
Binary files differ
diff --git a/org_apg/res/drawable-xhdpi/icon.png b/org_apg/res/drawable-xhdpi/icon.png
new file mode 100644
index 000000000..ffeb8cdf3
--- /dev/null
+++ b/org_apg/res/drawable-xhdpi/icon.png
Binary files differ
diff --git a/org_apg/res/drawable/btn_circle_disable.png b/org_apg/res/drawable/btn_circle_disable.png
new file mode 100644
index 000000000..33b74a66c
--- /dev/null
+++ b/org_apg/res/drawable/btn_circle_disable.png
Binary files differ
diff --git a/org_apg/res/drawable/btn_circle_disable_focused.png b/org_apg/res/drawable/btn_circle_disable_focused.png
new file mode 100644
index 000000000..005ad8dca
--- /dev/null
+++ b/org_apg/res/drawable/btn_circle_disable_focused.png
Binary files differ
diff --git a/org_apg/res/drawable/btn_circle_normal.png b/org_apg/res/drawable/btn_circle_normal.png
new file mode 100644
index 000000000..fc5af1c9f
--- /dev/null
+++ b/org_apg/res/drawable/btn_circle_normal.png
Binary files differ
diff --git a/org_apg/res/drawable/btn_circle_pressed.png b/org_apg/res/drawable/btn_circle_pressed.png
new file mode 100644
index 000000000..8f40afdfc
--- /dev/null
+++ b/org_apg/res/drawable/btn_circle_pressed.png
Binary files differ
diff --git a/org_apg/res/drawable/btn_circle_selected.png b/org_apg/res/drawable/btn_circle_selected.png
new file mode 100644
index 000000000..c74fac227
--- /dev/null
+++ b/org_apg/res/drawable/btn_circle_selected.png
Binary files differ
diff --git a/org_apg/res/drawable/encrypted.png b/org_apg/res/drawable/encrypted.png
new file mode 100644
index 000000000..2783804bc
--- /dev/null
+++ b/org_apg/res/drawable/encrypted.png
Binary files differ
diff --git a/org_apg/res/drawable/encrypted_large.png b/org_apg/res/drawable/encrypted_large.png
new file mode 100644
index 000000000..6d7c616a4
--- /dev/null
+++ b/org_apg/res/drawable/encrypted_large.png
Binary files differ
diff --git a/org_apg/res/drawable/encrypted_small.png b/org_apg/res/drawable/encrypted_small.png
new file mode 100644
index 000000000..7f4ab803f
--- /dev/null
+++ b/org_apg/res/drawable/encrypted_small.png
Binary files differ
diff --git a/org_apg/res/drawable/ic_btn_round_minus.png b/org_apg/res/drawable/ic_btn_round_minus.png
new file mode 100644
index 000000000..96dbb17d2
--- /dev/null
+++ b/org_apg/res/drawable/ic_btn_round_minus.png
Binary files differ
diff --git a/org_apg/res/drawable/ic_btn_round_plus.png b/org_apg/res/drawable/ic_btn_round_plus.png
new file mode 100644
index 000000000..1ec8a956a
--- /dev/null
+++ b/org_apg/res/drawable/ic_btn_round_plus.png
Binary files differ
diff --git a/org_apg/res/drawable/ic_launcher_folder.png b/org_apg/res/drawable/ic_launcher_folder.png
new file mode 100644
index 000000000..ed31ba580
--- /dev/null
+++ b/org_apg/res/drawable/ic_launcher_folder.png
Binary files differ
diff --git a/org_apg/res/drawable/ic_launcher_folder_small.png b/org_apg/res/drawable/ic_launcher_folder_small.png
new file mode 100644
index 000000000..5df8d60f0
--- /dev/null
+++ b/org_apg/res/drawable/ic_launcher_folder_small.png
Binary files differ
diff --git a/org_apg/res/drawable/ic_next.png b/org_apg/res/drawable/ic_next.png
new file mode 100644
index 000000000..8271c1380
--- /dev/null
+++ b/org_apg/res/drawable/ic_next.png
Binary files differ
diff --git a/org_apg/res/drawable/ic_previous.png b/org_apg/res/drawable/ic_previous.png
new file mode 100644
index 000000000..ef90db972
--- /dev/null
+++ b/org_apg/res/drawable/ic_previous.png
Binary files differ
diff --git a/org_apg/res/drawable/key.png b/org_apg/res/drawable/key.png
new file mode 100644
index 000000000..de7e72524
--- /dev/null
+++ b/org_apg/res/drawable/key.png
Binary files differ
diff --git a/org_apg/res/drawable/key_large.png b/org_apg/res/drawable/key_large.png
new file mode 100644
index 000000000..6f18c0240
--- /dev/null
+++ b/org_apg/res/drawable/key_large.png
Binary files differ
diff --git a/org_apg/res/drawable/key_small.png b/org_apg/res/drawable/key_small.png
new file mode 100644
index 000000000..121803508
--- /dev/null
+++ b/org_apg/res/drawable/key_small.png
Binary files differ
diff --git a/org_apg/res/drawable/overlay_error.png b/org_apg/res/drawable/overlay_error.png
new file mode 100644
index 000000000..2372de59e
--- /dev/null
+++ b/org_apg/res/drawable/overlay_error.png
Binary files differ
diff --git a/org_apg/res/drawable/overlay_ok.png b/org_apg/res/drawable/overlay_ok.png
new file mode 100644
index 000000000..2f0005898
--- /dev/null
+++ b/org_apg/res/drawable/overlay_ok.png
Binary files differ
diff --git a/org_apg/res/drawable/signed.png b/org_apg/res/drawable/signed.png
new file mode 100644
index 000000000..490e94fbd
--- /dev/null
+++ b/org_apg/res/drawable/signed.png
Binary files differ
diff --git a/org_apg/res/drawable/signed_large.png b/org_apg/res/drawable/signed_large.png
new file mode 100644
index 000000000..92e64dc51
--- /dev/null
+++ b/org_apg/res/drawable/signed_large.png
Binary files differ
diff --git a/org_apg/res/drawable/signed_small.png b/org_apg/res/drawable/signed_small.png
new file mode 100644
index 000000000..590220281
--- /dev/null
+++ b/org_apg/res/drawable/signed_small.png
Binary files differ
diff --git a/org_apg/res/layout/about_activity.xml b/org_apg/res/layout/about_activity.xml
new file mode 100644
index 000000000..a85439ee1
--- /dev/null
+++ b/org_apg/res/layout/about_activity.xml
@@ -0,0 +1,98 @@
+<?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:paddingBottom="15dp"
+ android:paddingLeft="15dp"
+ android:paddingRight="15dp"
+ 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:textStyle="bold" />
+
+ <TextView
+ android:id="@+id/about_version"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@android:style/TextAppearance.Small" />
+ </LinearLayout>
+ </LinearLayout>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ android:autoLink="web"
+ android:text="@string/about_url"
+ android:textAppearance="@android:style/TextAppearance.Small" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ android:text="@string/about_description"
+ android:textAppearance="@android:style/TextAppearance.Small" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ android:text="@string/about_license"
+ android:textAppearance="@android:style/TextAppearance.Small" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ android:text="@string/about_developer"
+ android:textAppearance="@android:style/TextAppearance.Small" />
+
+ <!-- <TextView -->
+ <!-- android:layout_width="wrap_content" -->
+ <!-- android:layout_height="wrap_content" -->
+ <!-- android:layout_marginTop="8dp" -->
+ <!-- android:text="@string/about_icons" -->
+ <!-- android:textAppearance="@android:style/TextAppearance.Small" /> -->
+
+
+ <!-- <TextView -->
+ <!-- android:layout_width="wrap_content" -->
+ <!-- android:layout_height="wrap_content" -->
+ <!-- android:layout_marginTop="8dp" -->
+ <!-- android:text="@string/about_libs" -->
+ <!-- android:textAppearance="@android:style/TextAppearance.Small" /> -->
+ </LinearLayout>
+
+</ScrollView> \ No newline at end of file
diff --git a/org_apg/res/layout/account_item.xml b/org_apg/res/layout/account_item.xml
new file mode 100644
index 000000000..0aa76719a
--- /dev/null
+++ b/org_apg/res/layout/account_item.xml
@@ -0,0 +1,35 @@
+<?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:singleLine="true"
+ android:layout_marginRight="?android:attr/scrollbarSize"
+ android:paddingLeft="6dip"
+ android:paddingTop="6dip"
+ android:paddingBottom="6dip"
+ android:layout_width="fill_parent"
+ android:layout_height="?android:attr/listPreferredItemHeight">
+
+ <TextView
+ android:id="@+id/accountName"
+ android:text="someone@gmail.com"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"/>
+
+</LinearLayout>
diff --git a/org_apg/res/layout/add_account_dialog.xml b/org_apg/res/layout/add_account_dialog.xml
new file mode 100644
index 000000000..d44ce9766
--- /dev/null
+++ b/org_apg/res/layout/add_account_dialog.xml
@@ -0,0 +1,31 @@
+<?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="5dip"
+ android:paddingRight="5dip">
+
+ <EditText
+ android:id="@+id/input"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:inputType="textEmailAddress"/>
+
+</LinearLayout>
diff --git a/org_apg/res/layout/create_key.xml b/org_apg/res/layout/create_key.xml
new file mode 100644
index 000000000..d8ccc73ec
--- /dev/null
+++ b/org_apg/res/layout/create_key.xml
@@ -0,0 +1,64 @@
+<?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"
+ android:layout_height="fill_parent"
+ android:layout_width="fill_parent">
+
+ <TableLayout
+ android:layout_height="fill_parent"
+ android:layout_width="fill_parent"
+ android:stretchColumns="1"
+ android:layout_marginRight="?android:attr/scrollbarSize"
+ android:paddingLeft="6dip">
+
+ <TableRow>
+
+ <TextView android:id="@+id/label_algorithm"
+ android:text="@string/label_algorithm"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:paddingRight="10dip"/>
+
+ <Spinner
+ android:id="@+id/algorithm"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"/>
+
+ </TableRow>
+
+ <TableRow>
+
+ <TextView android:id="@+id/label_size"
+ android:text="@string/label_keySize"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:paddingRight="10dip"/>
+
+ <EditText android:id="@+id/size"
+ android:text="1024"
+ android:layout_height="wrap_content"
+ android:layout_width="fill_parent"
+ android:gravity="right"
+ android:numeric="integer"/>
+ </TableRow>
+
+ </TableLayout>
+
+</ScrollView>
diff --git a/org_apg/res/layout/decrypt.xml b/org_apg/res/layout/decrypt.xml
new file mode 100644
index 000000000..8bfa76f01
--- /dev/null
+++ b/org_apg/res/layout/decrypt.xml
@@ -0,0 +1,216 @@
+<?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:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:fillViewport="true">
+
+ <ScrollView
+ android:layout_width="fill_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1"
+ android:fillViewport="true">
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical"
+ android:layout_marginLeft="5dip">
+
+ <LinearLayout
+ android:layout_width="fill_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"
+ android:layout_width="0dip"
+ android:layout_height="fill_parent"
+ android:layout_weight="1"
+ android:text="@string/label_message"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:gravity="center_horizontal|center_vertical"
+ android:textColor="#ffffffff"/>
+
+ <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="fill_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1">
+
+ <LinearLayout
+ android:id="@+id/sourceMessage"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical">
+
+ <EditText
+ android:id="@+id/message"
+ android:inputType="text|textCapSentences|textMultiLine|textLongMessage"
+ android:scrollHorizontally="true"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:gravity="top"/>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/sourceFile"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:layout_width="fill_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"/>
+
+ <ImageButton
+ android:id="@+id/btn_browse"
+ android:src="@drawable/ic_launcher_folder_small"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"/>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <TextView
+ android:id="@+id/label_deleteAfterDecryption"
+ android:text="@string/label_deleteAfterDecryption"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_gravity="center_vertical"
+ android:paddingRight="10dip"
+ android:layout_height="wrap_content"
+ android:layout_width="0dip"
+ android:layout_weight="1"/>
+
+ <CheckBox
+ android:id="@+id/deleteAfterDecryption"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_gravity="center_vertical"/>
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+ </ViewFlipper>
+
+ </LinearLayout>
+
+ </ScrollView>
+
+ <LinearLayout
+ android:id="@+id/signature"
+ android:orientation="horizontal"
+ android:layout_height="wrap_content"
+ android:layout_width="fill_parent"
+ android:clickable="true">
+
+ <RelativeLayout
+ android:layout_height="wrap_content"
+ android:layout_width="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:orientation="vertical"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:paddingLeft="5dip">
+
+ <TextView
+ android:id="@+id/mainUserId"
+ android:text="Main User Id"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="left"/>
+
+ <TextView
+ android:id="@+id/mainUserIdRest"
+ android:text="Main User Id Rest"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="left"/>
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ style="@android:style/ButtonBar">
+
+ <Button
+ android:id="@+id/btn_reply"
+ android:text="@string/btn_reply"
+ android:layout_width="0dip"
+ android:layout_height="fill_parent"
+ android:layout_weight="1"/>
+
+ <Button
+ android:id="@+id/btn_decrypt"
+ android:text="@string/btn_decrypt"
+ android:layout_width="0dip"
+ android:layout_height="fill_parent"
+ android:layout_weight="1"/>
+
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/org_apg/res/layout/edit_key.xml b/org_apg/res/layout/edit_key.xml
new file mode 100644
index 000000000..88be75d86
--- /dev/null
+++ b/org_apg/res/layout/edit_key.xml
@@ -0,0 +1,87 @@
+<?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:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:paddingTop="5dip"
+ android:fillViewport="true">
+
+ <ScrollView
+ android:layout_width="fill_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1">
+
+ <LinearLayout
+ android:id="@+id/container"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical"
+ android:layout_marginRight="?android:attr/scrollbarSize">
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <View
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"/>
+
+ <Button
+ android:id="@+id/btn_change_pass_phrase"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="2"
+ android:text="@string/btn_setPassPhrase"/>
+
+ <View
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"/>
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+ </ScrollView>
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ style="@android:style/ButtonBar">
+
+ <Button
+ android:id="@+id/btn_save"
+ android:layout_width="0dip"
+ android:layout_height="fill_parent"
+ android:layout_weight="1"
+ android:text="@string/btn_save"/>
+
+ <Button
+ android:id="@+id/btn_discard"
+ android:layout_width="0dip"
+ android:layout_height="fill_parent"
+ android:layout_weight="1"
+ android:text="@string/btn_doNotSave"/>
+
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/org_apg/res/layout/edit_key_key_item.xml b/org_apg/res/layout/edit_key_key_item.xml
new file mode 100644
index 000000000..850879f64
--- /dev/null
+++ b/org_apg/res/layout/edit_key_key_item.xml
@@ -0,0 +1,143 @@
+<?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.
+-->
+
+<org.apg.ui.widget.KeyEditor
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingLeft="5dip">
+
+ <View
+ android:id="@+id/separator"
+ android:layout_width="fill_parent"
+ android:layout_height="1dip"
+ android:background="?android:attr/listDivider"/>
+
+ <LinearLayout
+ android:layout_height="wrap_content"
+ android:layout_width="fill_parent"
+ android:orientation="horizontal">
+
+ <TableLayout
+ android:layout_height="wrap_content"
+ android:layout_width="0dip"
+ android:layout_weight="1"
+ android:layout_marginLeft="16dip"
+ android:stretchColumns="1">
+
+ <TableRow>
+
+ <TextView
+ android:id="@+id/label_keyId"
+ android:text="@string/label_keyId"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:paddingRight="10dip"/>
+
+ <TextView
+ android:id="@+id/keyId"
+ android:text="00000000 00000000"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:paddingRight="5dip"
+ android:typeface="monospace"/>
+
+ </TableRow>
+
+ <TableRow>
+
+ <TextView
+ android:id="@+id/label_algorithm"
+ android:text="@string/label_algorithm"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:paddingRight="10dip"/>
+
+ <TextView
+ android:id="@+id/algorithm"
+ android:text="Name"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:paddingRight="5dip"/>
+
+ </TableRow>
+
+ <TableRow>
+
+ <TextView
+ android:id="@+id/label_creation"
+ android:text="@string/label_creation"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:paddingRight="10dip"/>
+
+ <TextView
+ android:id="@+id/creation"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"/>
+
+ </TableRow>
+
+ <TableRow>
+
+ <TextView
+ android:id="@+id/label_expiry"
+ android:text="@string/label_expiry"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:paddingRight="10dip"/>
+
+ <Button
+ android:id="@+id/expiry"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"/>
+
+ </TableRow>
+
+ <TableRow>
+
+ <TextView
+ android:id="@+id/label_usage"
+ android:text="@string/label_usage"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:paddingRight="10dip"/>
+
+ <Spinner
+ android:id="@+id/usage"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"/>
+
+ </TableRow>
+
+ </TableLayout>
+
+ <ImageButton
+ android:id="@+id/delete"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/MinusButton"
+ android:layout_gravity="center_vertical"/>
+
+ </LinearLayout>
+
+</org.apg.ui.widget.KeyEditor>
diff --git a/org_apg/res/layout/edit_key_section.xml b/org_apg/res/layout/edit_key_section.xml
new file mode 100644
index 000000000..d14748d4b
--- /dev/null
+++ b/org_apg/res/layout/edit_key_section.xml
@@ -0,0 +1,66 @@
+<?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.
+-->
+
+<org.apg.ui.widget.SectionView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <View
+ android:layout_width="fill_parent"
+ android:layout_height="1dip"
+ android:background="?android:attr/listDivider"/>
+
+ <LinearLayout
+ android:id="@+id/header"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="14dip"
+ android:layout_marginTop="2dip"
+ android:layout_marginBottom="2dip"
+ android:orientation="horizontal"
+ android:gravity="center_vertical"
+ android:focusable="true"
+ android:clickable="true">
+
+ <TextView
+ android:id="@+id/title"
+ android:text="Section Name"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:fadingEdge="horizontal"/>
+
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:duplicateParentState="true"
+ style="@style/PlusButton"/>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/editors"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:paddingBottom="6dip"
+ android:orientation="vertical"/>
+
+</org.apg.ui.widget.SectionView>
diff --git a/org_apg/res/layout/edit_key_user_id_item.xml b/org_apg/res/layout/edit_key_user_id_item.xml
new file mode 100644
index 000000000..6e7b552dd
--- /dev/null
+++ b/org_apg/res/layout/edit_key_user_id_item.xml
@@ -0,0 +1,115 @@
+<?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.
+-->
+
+<org.apg.ui.widget.UserIdEditor
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingLeft="5dip">
+
+ <View
+ android:id="@+id/separator"
+ android:layout_width="fill_parent"
+ android:layout_height="1dip"
+ android:background="?android:attr/listDivider"/>
+
+ <RadioButton
+ android:id="@+id/isMainUserId"
+ android:text="@string/label_mainUserId"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_marginLeft="20dip"/>
+
+ <LinearLayout
+ android:layout_height="wrap_content"
+ android:layout_width="fill_parent"
+ android:orientation="horizontal">
+
+ <TableLayout
+ android:layout_height="wrap_content"
+ android:layout_width="0dip"
+ android:layout_weight="1"
+ android:layout_marginLeft="16dip">
+
+ <TableRow>
+
+ <TextView
+ android:id="@+id/label_name"
+ android:text="@string/label_name"
+ android:layout_gravity="center_vertical"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:paddingRight="5dip"/>
+
+ <EditText
+ android:id="@+id/name"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"
+ android:layout_width="fill_parent"
+ android:inputType="textPersonName|textCapWords"/>
+
+ </TableRow>
+
+ <TableRow>
+
+ <TextView
+ android:id="@+id/label_email"
+ android:text="@string/label_email"
+ android:layout_gravity="center_vertical"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:paddingRight="5dip"/>
+
+ <EditText
+ android:id="@+id/email"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"
+ android:layout_width="fill_parent"
+ android:inputType="textEmailAddress"/>
+
+ </TableRow>
+
+ <TableRow>
+
+ <TextView
+ android:id="@+id/label_comment"
+ android:text="@string/label_comment"
+ android:layout_gravity="center_vertical"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:paddingRight="5dip"/>
+
+ <EditText
+ android:id="@+id/comment"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"
+ android:layout_width="fill_parent"/>
+
+ </TableRow>
+
+ </TableLayout>
+
+ <ImageButton
+ android:id="@+id/delete"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/MinusButton"
+ android:layout_gravity="center_vertical"/>
+
+ </LinearLayout>
+
+</org.apg.ui.widget.UserIdEditor>
diff --git a/org_apg/res/layout/encrypt.xml b/org_apg/res/layout/encrypt.xml
new file mode 100644
index 000000000..68808c8bf
--- /dev/null
+++ b/org_apg/res/layout/encrypt.xml
@@ -0,0 +1,382 @@
+<?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="fill_parent"
+ android:orientation="vertical"
+ android:paddingTop="5dip">
+
+ <ScrollView
+ android:layout_width="fill_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1"
+ android:fillViewport="true">
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical"
+ android:layout_marginLeft="5dip">
+
+ <LinearLayout
+ android:layout_width="fill_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"
+ android:layout_width="0dip"
+ android:layout_height="fill_parent"
+ android:layout_weight="1"
+ android:text="@string/label_message"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:gravity="center_horizontal|center_vertical"
+ android:textColor="#ffffffff"/>
+
+ <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="fill_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1">
+
+ <LinearLayout
+ android:id="@+id/sourceMessage"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical">
+
+ <EditText
+ android:id="@+id/message"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:gravity="top"
+ android:inputType="text|textCapSentences|textMultiLine|textLongMessage"/>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/sourceFile"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:layout_width="fill_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"/>
+
+ <ImageButton
+ android:id="@+id/btn_browse"
+ android:src="@drawable/ic_launcher_folder_small"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"/>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <TextView
+ android:id="@+id/label_fileCompression"
+ android:text="@string/label_fileCompression"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_height="wrap_content"
+ android:layout_width="0dip"
+ android:layout_weight="1"
+ android:layout_gravity="center_vertical"
+ android:paddingRight="10dip"/>
+
+ <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="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <TextView
+ android:id="@+id/label_deleteAfterEncryption"
+ android:text="@string/label_deleteAfterEncryption"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_gravity="center_vertical"
+ android:paddingRight="10dip"
+ android:layout_height="wrap_content"
+ android:layout_width="0dip"
+ android:layout_weight="1"/>
+
+ <CheckBox
+ android:id="@+id/deleteAfterEncryption"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_gravity="center_vertical"/>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <TextView
+ android:id="@+id/label_asciiArmour"
+ android:text="@string/label_asciiArmour"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_gravity="center_vertical"
+ android:paddingRight="10dip"
+ android:layout_height="wrap_content"
+ android:layout_width="0dip"
+ android:layout_weight="1"/>
+
+ <CheckBox
+ android:id="@+id/asciiArmour"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_gravity="center_vertical"/>
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+ </ViewFlipper>
+
+ <View
+ android:layout_width="fill_parent"
+ android:layout_height="1dip"
+ android:background="?android:attr/listDivider"
+ android:layout_marginBottom="5dip"/>
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <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"
+ android:layout_width="0dip"
+ android:layout_height="fill_parent"
+ android:layout_weight="1"
+ android:text="@string/label_asymmetric"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:gravity="center_horizontal|center_vertical"
+ android:textColor="#ffffffff"/>
+
+ <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="fill_parent"
+ android:layout_height="wrap_content">
+
+ <LinearLayout
+ android:id="@+id/modeAsymmetric"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <TextView
+ android:id="@+id/label_sign"
+ android:text="@string/label_sign"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_width="0dip"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"/>
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:paddingRight="5dip">
+
+ <TextView
+ android:id="@+id/mainUserId"
+ android:text="Main User Id"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="right"/>
+
+ <TextView
+ android:id="@+id/mainUserIdRest"
+ android:text="Main User Id Rest"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="right"/>
+
+ </LinearLayout>
+
+ <CheckBox
+ android:id="@+id/sign"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"/>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:paddingBottom="3dip"
+ android:orientation="horizontal">
+
+ <TextView
+ android:id="@+id/label_selectPublicKeys"
+ android:text="@string/label_selectPublicKeys"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_height="wrap_content"
+ android:layout_width="0dip"
+ android:layout_weight="1"
+ android:layout_gravity="center_vertical"/>
+
+ <Button
+ android:text="@string/btn_selectEncryptKeys"
+ android:id="@+id/btn_selectEncryptKeys"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"/>
+
+ </LinearLayout>
+ </LinearLayout>
+
+ <TableLayout
+ android:id="@+id/modeSymmetric"
+ android:layout_height="wrap_content"
+ android:layout_width="fill_parent"
+ android:stretchColumns="1">
+
+ <TableRow>
+
+ <TextView
+ android:id="@+id/label_passPhrase"
+ android:text="@string/label_passPhrase"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:paddingRight="10dip"/>
+
+ <EditText
+ android:id="@+id/passPhrase"
+ android:layout_height="wrap_content"
+ android:layout_width="fill_parent"
+ android:inputType="textPassword"/>
+
+ </TableRow>
+
+ <TableRow>
+
+ <TextView
+ android:id="@+id/label_passPhraseAgain"
+ android:text="@string/label_passPhraseAgain"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:paddingRight="10dip"/>
+
+ <EditText
+ android:id="@+id/passPhraseAgain"
+ android:layout_height="wrap_content"
+ android:layout_width="fill_parent"
+ android:inputType="textPassword"/>
+
+ </TableRow>
+
+ </TableLayout>
+
+ </ViewFlipper>
+
+ <View
+ android:layout_width="fill_parent"
+ android:layout_height="1dip"
+ android:background="?android:attr/listDivider"
+ android:layout_marginBottom="5dip"/>
+
+ </LinearLayout>
+
+ </ScrollView>
+
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ style="@android:style/ButtonBar">
+
+ <Button
+ android:id="@+id/btn_encryptToClipboard"
+ android:text="@string/btn_encryptToClipboard"
+ android:layout_weight="1"
+ android:layout_width="0dip"
+ android:layout_height="fill_parent"/>
+
+ <Button
+ android:id="@+id/btn_encrypt"
+ android:text="@string/btn_encrypt"
+ android:layout_weight="1"
+ android:layout_width="0dip"
+ android:layout_height="fill_parent"/>
+
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/org_apg/res/layout/file_dialog.xml b/org_apg/res/layout/file_dialog.xml
new file mode 100644
index 000000000..bc5a2a214
--- /dev/null
+++ b/org_apg/res/layout/file_dialog.xml
@@ -0,0 +1,50 @@
+<?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="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"/>
+
+ <ImageButton
+ android:id="@+id/btn_browse"
+ android:src="@drawable/ic_launcher_folder_small"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_gravity="center_vertical"/>
+
+ </LinearLayout>
+
+ <CheckBox
+ android:id="@+id/checkbox"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"/>
+
+</LinearLayout>
diff --git a/org_apg/res/layout/filter_info.xml b/org_apg/res/layout/filter_info.xml
new file mode 100644
index 000000000..9fef6548e
--- /dev/null
+++ b/org_apg/res/layout/filter_info.xml
@@ -0,0 +1,38 @@
+<?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:paddingBottom="5dip"
+ android:orientation="horizontal">
+
+ <TextView
+ android:id="@+id/filterInfo"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:ellipsize="end"/>
+
+ <Button
+ android:id="@+id/btn_clear"
+ android:text="@string/btn_clearFilter"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+</LinearLayout>
diff --git a/org_apg/res/layout/general.xml b/org_apg/res/layout/general.xml
new file mode 100644
index 000000000..c6702efac
--- /dev/null
+++ b/org_apg/res/layout/general.xml
@@ -0,0 +1,59 @@
+<?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="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <ListView
+ android:id="@+id/options"
+ android:layout_width="fill_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1"
+ android:padding="5dip"/>
+
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ style="@android:style/ButtonBar">
+
+ <Button
+ android:text="dummy"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:visibility="invisible"/>
+
+ <Button
+ android:id="@+id/btn_cancel"
+ android:text="@android:string/cancel"
+ android:layout_width="0dip"
+ android:layout_height="fill_parent"
+ android:layout_weight="2"/>
+
+ <Button
+ android:text="dummy"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:visibility="invisible"/>
+
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/org_apg/res/layout/info.xml b/org_apg/res/layout/info.xml
new file mode 100644
index 000000000..2507029c4
--- /dev/null
+++ b/org_apg/res/layout/info.xml
@@ -0,0 +1,30 @@
+<?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"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:fillViewport="true"
+ android:padding="5dip">
+
+ <TextView
+ android:id="@+id/message"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:textColor="#ffffffff"
+ android:autoLink="all"/>
+</ScrollView>
diff --git a/org_apg/res/layout/key_list.xml b/org_apg/res/layout/key_list.xml
new file mode 100644
index 000000000..ac07801ab
--- /dev/null
+++ b/org_apg/res/layout/key_list.xml
@@ -0,0 +1,31 @@
+<?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="fill_parent"
+ android:orientation="vertical">
+
+ <include android:id="@+id/layout_filter" layout="@layout/filter_info"/>
+
+ <ExpandableListView
+ android:id="@+id/list"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:isScrollContainer="true"/>
+
+</LinearLayout> \ No newline at end of file
diff --git a/org_apg/res/layout/key_list_child_item_master_key.xml b/org_apg/res/layout/key_list_child_item_master_key.xml
new file mode 100644
index 000000000..998ba256f
--- /dev/null
+++ b/org_apg/res/layout/key_list_child_item_master_key.xml
@@ -0,0 +1,80 @@
+<?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:singleLine="true"
+ android:paddingLeft="10dip"
+ android:layout_marginRight="?android:attr/scrollbarSize"
+ android:layout_height="?android:attr/listPreferredItemHeight"
+ android:layout_width="fill_parent">
+
+ <LinearLayout
+ android:layout_height="wrap_content"
+ android:layout_width="fill_parent"
+ android:orientation="horizontal"
+ android:paddingRight="3dip">
+
+ <ImageView
+ android:id="@+id/ic_masterKey"
+ android:src="@drawable/key_small"
+ android:paddingRight="6dip"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"/>
+
+ <TextView
+ android:id="@+id/keyId"
+ android:text="Key ID"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingRight="5dip"
+ android:typeface="monospace"/>
+
+ <TextView
+ android:id="@+id/keyDetails"
+ android:text="(RSA, 1024bit)"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+ <LinearLayout
+ android:gravity="right"
+ android:orientation="horizontal"
+ android:paddingBottom="2dip"
+ android:paddingTop="2dip"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:layout_gravity="center_vertical">
+
+ <ImageView
+ android:id="@+id/ic_encryptKey"
+ android:src="@drawable/encrypted_small"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+ <ImageView
+ android:id="@+id/ic_signKey"
+ android:src="@drawable/signed_small"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/org_apg/res/layout/key_list_child_item_sub_key.xml b/org_apg/res/layout/key_list_child_item_sub_key.xml
new file mode 100644
index 000000000..ac7c217a6
--- /dev/null
+++ b/org_apg/res/layout/key_list_child_item_sub_key.xml
@@ -0,0 +1,72 @@
+<?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:singleLine="true"
+ android:paddingLeft="40dip"
+ android:layout_marginRight="?android:attr/scrollbarSize"
+ android:layout_height="?android:attr/listPreferredItemHeight"
+ android:layout_width="fill_parent">
+
+ <LinearLayout
+ android:layout_height="wrap_content"
+ android:layout_width="fill_parent"
+ android:orientation="horizontal"
+ android:paddingRight="3dip">
+
+ <TextView
+ android:id="@+id/keyId"
+ android:text="Key ID"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingRight="5dip"
+ android:typeface="monospace"/>
+
+ <TextView
+ android:id="@+id/keyDetails"
+ android:text="(RSA, 1024bit)"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+ <LinearLayout
+ android:gravity="right"
+ android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:paddingBottom="2dip"
+ android:paddingTop="2dip"
+ android:layout_gravity="center_vertical">
+
+ <ImageView
+ android:id="@+id/ic_encryptKey"
+ android:src="@drawable/encrypted_small"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+ <ImageView
+ android:id="@+id/ic_signKey"
+ android:src="@drawable/signed_small"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/org_apg/res/layout/key_list_child_item_user_id.xml b/org_apg/res/layout/key_list_child_item_user_id.xml
new file mode 100644
index 000000000..598dcef22
--- /dev/null
+++ b/org_apg/res/layout/key_list_child_item_user_id.xml
@@ -0,0 +1,33 @@
+<?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:paddingLeft="40dip"
+ android:layout_marginRight="?android:attr/scrollbarSize"
+ android:singleLine="true"
+ android:layout_width="fill_parent"
+ android:layout_height="?android:attr/listPreferredItemHeight">
+
+ <TextView
+ android:id="@+id/userId"
+ android:text="User ID"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingRight="3dip"/>
+
+</LinearLayout>
diff --git a/org_apg/res/layout/key_list_group_item.xml b/org_apg/res/layout/key_list_group_item.xml
new file mode 100644
index 000000000..35d0ab367
--- /dev/null
+++ b/org_apg/res/layout/key_list_group_item.xml
@@ -0,0 +1,53 @@
+<?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:singleLine="true"
+ android:layout_marginRight="?android:attr/scrollbarSize"
+ android:layout_width="fill_parent"
+ android:layout_height="?android:attr/listPreferredItemHeight">
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:paddingLeft="36dip">
+
+ <TextView
+ android:id="@+id/mainUserId"
+ android:text="Main User ID"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+ <TextView
+ android:id="@+id/mainUserIdRest"
+ android:text="&lt;user@somewhere.com&gt;"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/org_apg/res/layout/key_server_editor.xml b/org_apg/res/layout/key_server_editor.xml
new file mode 100644
index 000000000..a02540c2c
--- /dev/null
+++ b/org_apg/res/layout/key_server_editor.xml
@@ -0,0 +1,52 @@
+<?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.
+-->
+
+<org.apg.ui.widget.KeyServerEditor
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:layout_height="wrap_content"
+ android:layout_width="fill_parent"
+ android:orientation="horizontal"
+ android:layout_marginLeft="3dip">
+
+ <EditText
+ android:id="@+id/server"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"
+ android:layout_width="fill_parent"
+ android:inputType="textUri"/>
+
+ <ImageButton
+ android:id="@+id/delete"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/MinusButton"
+ android:layout_gravity="center_vertical"
+ android:layout_marginRight="3dip"/>
+
+ </LinearLayout>
+
+ <View
+ android:id="@+id/separator"
+ android:layout_width="fill_parent"
+ android:layout_height="1dip"
+ android:background="?android:attr/listDivider"/>
+
+</org.apg.ui.widget.KeyServerEditor>
diff --git a/org_apg/res/layout/key_server_export_layout.xml b/org_apg/res/layout/key_server_export_layout.xml
new file mode 100644
index 000000000..b2270417d
--- /dev/null
+++ b/org_apg/res/layout/key_server_export_layout.xml
@@ -0,0 +1,42 @@
+<?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="fill_parent"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <Spinner
+ android:id="@+id/keyServer"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"/>
+
+ <Button
+ android:id="@+id/btn_export_to_server"
+ android:text="@string/btn_export_to_server"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/org_apg/res/layout/key_server_preference.xml b/org_apg/res/layout/key_server_preference.xml
new file mode 100644
index 000000000..2f5645f62
--- /dev/null
+++ b/org_apg/res/layout/key_server_preference.xml
@@ -0,0 +1,115 @@
+<?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="fill_parent"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:id="@+android:id/text_layout"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:gravity="center_vertical">
+
+ <RelativeLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="16sp"
+ android:layout_marginRight="6sp"
+ android:layout_marginTop="6sp"
+ android:layout_marginBottom="6sp"
+ android:layout_weight="1"
+ android:focusable="true"
+ android:background="@android:drawable/menuitem_background">
+
+ <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_below="@android:id/title"
+ android:layout_alignLeft="@android:id/title"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:maxLines="2" />
+
+ </RelativeLayout>
+
+ <ImageView
+ android:id="@+id/add"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="4dip"
+ android:layout_marginRight="6dip"
+ android:layout_gravity="center_vertical"
+ android:clickable="true"
+ style="@style/PlusButton"/>
+
+ </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
+ android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ style="@android:style/ButtonBar">
+
+ <Button
+ android:text="@android:string/ok"
+ android:id="@+id/btn_ok"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"/>
+
+ <Button
+ android:text="@android:string/cancel"
+ android:id="@+id/btn_cancel"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"/>
+
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/org_apg/res/layout/key_server_query_layout.xml b/org_apg/res/layout/key_server_query_layout.xml
new file mode 100644
index 000000000..6af4f3644
--- /dev/null
+++ b/org_apg/res/layout/key_server_query_layout.xml
@@ -0,0 +1,53 @@
+<?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="fill_parent"
+ android:orientation="vertical">
+
+ <Spinner
+ android:id="@+id/keyServer"
+ 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/query"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"/>
+
+ <Button
+ android:id="@+id/btn_search"
+ android:text="@string/btn_search"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+ </LinearLayout>
+
+ <ListView
+ android:id="@+id/list"
+ android:layout_width="fill_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1"/>
+
+</LinearLayout>
diff --git a/org_apg/res/layout/key_server_query_result_item.xml b/org_apg/res/layout/key_server_query_result_item.xml
new file mode 100644
index 000000000..29c8b88f4
--- /dev/null
+++ b/org_apg/res/layout/key_server_query_result_item.xml
@@ -0,0 +1,97 @@
+<?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:singleLine="true"
+ android:paddingLeft="3dip"
+ android:paddingRight="?android:attr/scrollbarSize"
+ android:layout_height="wrap_content"
+ android:layout_width="fill_parent"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:paddingLeft="5dip"
+ android:paddingRight="5dip"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1">
+
+ <TextView
+ android:id="@+id/mainUserId"
+ android:text="Main User ID"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+ <TextView
+ android:id="@+id/mainUserIdRest"
+ android:text="&lt;user@somewhere.com&gt;"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:minWidth="90dip"
+ android:paddingLeft="3dip"
+ android:gravity="right"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+
+ <TextView
+ android:id="@+id/keyId"
+ android:text="BBBBBBBB"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:typeface="monospace"
+ android:layout_width="wrap_content"
+ android:layout_height="fill_parent"/>
+
+ <TextView
+ android:id="@+id/algorithm"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+ <TextView
+ android:id="@+id/status"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textColor="#e00"/>
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/list"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="30dip">
+
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/org_apg/res/layout/key_server_query_result_user_id.xml b/org_apg/res/layout/key_server_query_result_user_id.xml
new file mode 100644
index 000000000..9d3a4a1ab
--- /dev/null
+++ b/org_apg/res/layout/key_server_query_result_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/org_apg/res/layout/mailbox_message_item.xml b/org_apg/res/layout/mailbox_message_item.xml
new file mode 100644
index 000000000..05a267fb6
--- /dev/null
+++ b/org_apg/res/layout/mailbox_message_item.xml
@@ -0,0 +1,57 @@
+<?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:singleLine="true"
+ android:paddingLeft="3dip"
+ android:layout_marginRight="?android:attr/scrollbarSize"
+ android:paddingTop="3dip"
+ android:paddingBottom="3dip"
+ android:layout_height="?android:attr/listPreferredItemHeight"
+ android:layout_width="fill_parent">
+
+ <ImageView
+ android:id="@+id/ic_status"
+ android:src="@drawable/encrypted"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"/>
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:paddingLeft="5dip"
+ android:layout_weight="1"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+
+ <TextView
+ android:id="@+id/subject"
+ android:text="Subject"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+ <TextView
+ android:id="@+id/emailAddress"
+ android:text="user@somewhere.com"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/org_apg/res/layout/main.xml b/org_apg/res/layout/main.xml
new file mode 100644
index 000000000..803b2abe9
--- /dev/null
+++ b/org_apg/res/layout/main.xml
@@ -0,0 +1,89 @@
+<?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:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:paddingTop="5dip"
+ android:fillViewport="true">
+
+ <ScrollView
+ android:layout_marginTop="10dip"
+ android:layout_width="fill_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1"
+ android:fillViewport="true">
+
+ <ListView
+ android:id="@+id/accounts"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"/>
+
+ </ScrollView>
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ style="@android:style/ButtonBar">
+
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+
+ <Button
+ android:id="@+id/btn_encryptFile"
+ android:text="@string/btn_encryptFile"
+ android:layout_width="0dip"
+ android:layout_weight="1"
+ android:layout_height="fill_parent"/>
+
+ <Button
+ android:id="@+id/btn_decryptFile"
+ android:text="@string/btn_decryptFile"
+ android:layout_width="0dip"
+ android:layout_weight="1"
+ android:layout_height="fill_parent"/>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+
+ <Button
+ android:id="@+id/btn_encryptMessage"
+ android:text="@string/btn_encryptMessage"
+ android:layout_width="0dip"
+ android:layout_weight="1"
+ android:layout_height="fill_parent"/>
+
+ <Button
+ android:id="@+id/btn_decryptMessage"
+ android:text="@string/btn_decryptMessage"
+ android:layout_width="0dip"
+ android:layout_weight="1"
+ android:layout_height="fill_parent"/>
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/org_apg/res/layout/pass_phrase.xml b/org_apg/res/layout/pass_phrase.xml
new file mode 100644
index 000000000..d66ffca07
--- /dev/null
+++ b/org_apg/res/layout/pass_phrase.xml
@@ -0,0 +1,37 @@
+<?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="5dip"
+ android:paddingRight="5dip">
+
+ <EditText
+ android:id="@+id/passPhrase"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:inputType="textPassword"/>
+
+ <EditText
+ android:id="@+id/passPhraseAgain"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:inputType="textPassword"/>
+
+</LinearLayout>
diff --git a/org_apg/res/layout/select_public_key.xml b/org_apg/res/layout/select_public_key.xml
new file mode 100644
index 000000000..9ce35a7a8
--- /dev/null
+++ b/org_apg/res/layout/select_public_key.xml
@@ -0,0 +1,55 @@
+<?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:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:fillViewport="true">
+
+ <include android:id="@+id/layout_filter" layout="@layout/filter_info"/>
+
+ <ListView
+ android:id="@+id/list"
+ android:choiceMode="multipleChoice"
+ android:layout_width="fill_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1"/>
+
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ style="@android:style/ButtonBar">
+
+ <Button
+ android:text="@android:string/ok"
+ android:id="@+id/btn_ok"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"/>
+
+ <Button
+ android:text="@android:string/cancel"
+ android:id="@+id/btn_cancel"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"/>
+
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/org_apg/res/layout/select_public_key_item.xml b/org_apg/res/layout/select_public_key_item.xml
new file mode 100644
index 000000000..beca23176
--- /dev/null
+++ b/org_apg/res/layout/select_public_key_item.xml
@@ -0,0 +1,82 @@
+<?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:singleLine="true"
+ android:paddingLeft="3dip"
+ android:paddingRight="?android:attr/scrollbarSize"
+ android:layout_height="?android:attr/listPreferredItemHeight"
+ android:layout_width="fill_parent">
+
+ <CheckBox
+ android:id="@+id/selected"
+ android:focusable="false"
+ android:focusableInTouchMode="false"
+ android:clickable="false"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:paddingLeft="5dip"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1">
+
+ <TextView
+ android:id="@+id/mainUserId"
+ android:text="Main User ID"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+ <TextView
+ android:id="@+id/mainUserIdRest"
+ android:text="&lt;user@somewhere.com&gt;"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:minWidth="90dip"
+ android:paddingLeft="3dip"
+ android:gravity="right"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+
+ <TextView
+ android:id="@+id/keyId"
+ android:text="BBBBBBBB"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:typeface="monospace"
+ android:layout_width="wrap_content"
+ android:layout_height="fill_parent"/>
+
+ <TextView
+ android:id="@+id/status"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:text="expired"
+ android:textStyle="italic"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/org_apg/res/layout/select_secret_key.xml b/org_apg/res/layout/select_secret_key.xml
new file mode 100644
index 000000000..feabc6160
--- /dev/null
+++ b/org_apg/res/layout/select_secret_key.xml
@@ -0,0 +1,31 @@
+<?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:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:fillViewport="true">
+
+ <include android:id="@+id/layout_filter" layout="@layout/filter_info"/>
+
+ <ListView
+ android:id="@+id/list"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"/>
+
+</LinearLayout>
diff --git a/org_apg/res/layout/select_secret_key_item.xml b/org_apg/res/layout/select_secret_key_item.xml
new file mode 100644
index 000000000..022545152
--- /dev/null
+++ b/org_apg/res/layout/select_secret_key_item.xml
@@ -0,0 +1,75 @@
+<?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:singleLine="true"
+ android:paddingLeft="3dip"
+ android:paddingRight="?android:attr/scrollbarSize"
+ android:layout_height="?android:attr/listPreferredItemHeight"
+ android:layout_width="fill_parent">
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:paddingLeft="5dip"
+ android:paddingRight="5dip"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1">
+
+ <TextView
+ android:id="@+id/mainUserId"
+ android:text="Main User ID"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+ <TextView
+ android:id="@+id/mainUserIdRest"
+ android:text="&lt;user@somewhere.com&gt;"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:minWidth="90dip"
+ android:paddingLeft="3dip"
+ android:gravity="right"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+
+ <TextView
+ android:id="@+id/keyId"
+ android:text="BBBBBBBB"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:typeface="monospace"
+ android:layout_width="wrap_content"
+ android:layout_height="fill_parent"/>
+
+ <TextView
+ android:id="@+id/status"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:text="expired"
+ android:textStyle="italic"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/org_apg/res/layout/sign_key_layout.xml b/org_apg/res/layout/sign_key_layout.xml
new file mode 100644
index 000000000..17be03b21
--- /dev/null
+++ b/org_apg/res/layout/sign_key_layout.xml
@@ -0,0 +1,40 @@
+<?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="fill_parent"
+ android:orientation="vertical">
+
+ <LinearLayout android:layout_width="fill_parent"
+ android:layout_height="wrap_content" android:orientation="horizontal">
+ </LinearLayout>
+
+ <LinearLayout android:layout_width="fill_parent"
+ android:layout_height="wrap_content" android:orientation="horizontal">
+
+ <TextView android:id="@+id/textView1" android:layout_height="wrap_content"
+ android:text="@string/label_sendKey" android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_width="wrap_content"></TextView>
+ <CheckBox android:text="" android:id="@+id/sendKey"
+ android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:checked="true"></CheckBox>
+ </LinearLayout>
+
+ <LinearLayout android:layout_width="fill_parent"
+ android:layout_height="wrap_content" android:orientation="horizontal">
+
+ <Spinner android:id="@+id/keyServer" android:layout_width="wrap_content"
+ android:layout_height="wrap_content" android:layout_weight="1" />
+ </LinearLayout>
+
+ <Button android:layout_height="wrap_content"
+ android:layout_width="wrap_content" android:text="@string/btn_sign"
+ android:id="@+id/sign"></Button>
+
+</LinearLayout>
diff --git a/org_apg/res/values-da/strings.xml b/org_apg/res/values-da/strings.xml
new file mode 100644
index 000000000..418372fb9
--- /dev/null
+++ b/org_apg/res/values-da/strings.xml
@@ -0,0 +1,261 @@
+<?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.
+-->
+
+<resources>
+
+ <!-- title_lowerCase: capitalized words, no punctuation -->
+ <string name="title_mailInbox">Mail Inbox</string>
+ <string name="title_managePublicKeys">Administrér Offentlige Nøgler</string>
+ <string name="title_manageSecretKeys">Administrér Private Nøgler</string>
+ <string name="title_selectRecipients">Vælg Modtagere</string>
+ <string name="title_selectSignature">Vælg Signatur</string>
+ <string name="title_encrypt">Kryptér</string>
+ <string name="title_decrypt">Afkryptér</string>
+ <string name="title_authentication">Kodeord</string>
+ <string name="title_createKey">Opret Nøgle</string>
+ <string name="title_editKey">Redigér Nøgle</string>
+ <string name="title_preferences">Indstillinger</string>
+ <string name="title_changePassPhrase">Skift Kodeord</string>
+ <string name="title_setPassPhrase">Sæt Kodeord</string>
+ <string name="title_sendEmail">"Send Mail..."</string>
+ <string name="title_encryptToFile">Kryptér Til Fil</string>
+ <string name="title_decryptToFile">Afkryptér Til Fil</string>
+ <string name="title_addAccount">Tilføj Konto</string>
+ <string name="title_importKeys">Importér Nøgle</string>
+ <string name="title_exportKey">Eksportér Nøgle</string>
+ <string name="title_exportKeys">Eksportér Nøgler</string>
+ <string name="title_keyNotFound">Ingen Nøgle Matchede</string>
+
+ <!-- section_lowerCase: capitalized words, no punctuation -->
+ <string name="section_userIds">Bruger ID</string>
+ <string name="section_keys">Nøgler</string>
+ <string name="section_general">Generelt</string>
+ <string name="section_defaults">Standard Indstillinger</string>
+
+ <!-- btn_lowerCase: capitalized words, no punctuation -->
+ <string name="btn_encryptToClipboard">Kryptér Til Clipboard</string>
+ <string name="btn_encryptAndEmail">Kryptér Og Mail</string>
+ <string name="btn_encrypt">Kryptér</string>
+ <string name="btn_decrypt">Afkryptér</string>
+ <string name="btn_verify">Bekræft</string>
+ <string name="btn_selectEncryptKeys">Vælg Modtagere</string>
+ <string name="btn_reply">Svar</string>
+ <string name="btn_encryptMessage">Kryptér Besked</string>
+ <string name="btn_decryptMessage">Afkryptér Besked</string>
+ <string name="btn_encryptFile">Kryptér Fil</string>
+ <string name="btn_decryptFile">Afkryptér Fil</string>
+ <string name="btn_save">Gem</string>
+ <string name="btn_doNotSave">Fortryd</string>
+ <string name="btn_delete">Slet</string>
+ <string name="btn_noDate">Ingen</string>
+ <string name="btn_clearFilter">Ryd Filter</string>
+ <string name="btn_changePassPhrase">Skift Kodeord</string>
+ <string name="btn_setPassPhrase">Sæt Kodeord</string>
+
+ <!-- menu_lowerCase: capitalized words, no punctuation -->
+ <string name="menu_about">Om APG</string>
+ <string name="menu_addAccount">Tilføj GMail Konto</string>
+ <string name="menu_deleteAccount">Slet Konto</string>
+ <string name="menu_managePublicKeys">Administrér Offentlige Nøgler</string>
+ <string name="menu_manageSecretKeys">Administrér Private Nøgler</string>
+ <string name="menu_preferences">Indstillinger</string>
+ <string name="menu_importKeys">Importér Nøgler</string>
+ <string name="menu_exportKeys">Eksportér Nøgler</string>
+ <string name="menu_exportKey">Eksportér Nøgle</string>
+ <string name="menu_deleteKey">Slet Nøgle</string>
+ <string name="menu_createKey">Opret Nøgle</string>
+ <string name="menu_editKey">Redigér Nøgle</string>
+ <string name="menu_search">Søg</string>
+
+ <!-- label_lowerCase: capitalized words, no punctuation -->
+ <string name="label_sign">Signér</string>
+ <string name="label_message">Besked</string>
+ <string name="label_file">Fil</string>
+ <string name="label_passPhrase">Kodeord</string>
+ <string name="label_passPhraseAgain">Igen</string>
+ <string name="label_algorithm">Algoritme</string>
+ <string name="label_asciiArmour">ASCII Rustning</string>
+ <string name="label_selectPublicKeys">Offentlig(e) Nøgle(r)</string>
+ <string name="label_deleteAfterEncryption">Slet Efter Kryptering</string>
+ <string name="label_deleteAfterDecryption">Slet Efter Afkryptering</string>
+ <string name="label_encryptionAlgorithm">Krypteringsalgoritme</string>
+ <string name="label_hashAlgorithm">Hash-algoritme</string>
+ <string name="label_asymmetric">Offentlig Nøgle</string>
+ <string name="label_symmetric">Kodeord</string>
+ <string name="label_passPhraseCacheTtl">Kodeords Cache</string>
+ <string name="label_messageCompression">Besked Komprimering</string>
+ <string name="label_fileCompression">Fil Komprimering</string>
+ <string name="noKeysSelected">Vælg</string>
+ <string name="oneKeySelected">1 Valgt</string>
+ <string name="nKeysSelected">Valgt</string>
+ <string name="unknownUserId">&lt;ukendt></string>
+ <string name="none">&lt;ingen></string>
+ <string name="noKey">&lt;ingen nøgle></string>
+ <string name="noDate">-</string>
+ <string name="noExpiry">&lt;udløber ikke></string>
+ <string name="unknownStatus"></string>
+ <string name="canEncrypt">kan kryptere</string>
+ <string name="canSign">kan signere</string>
+ <string name="expired">udløbet</string>
+ <string name="notValid">ugyldig</string>
+
+ <!-- choice_lowerCase: capitalized firwst word, no punctuation -->
+ <string name="choice_none">Ingen</string>
+ <string name="choice_signOnly">Kun signering</string>
+ <string name="choice_encryptOnly">Kun kryptering</string>
+ <string name="choice_signAndEncrypt">Signér og kryptér</string>
+ <string name="choice_15secs">15 secs</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 timer</string>
+ <string name="choice_2hours">2 timer</string>
+ <string name="choice_4hours">4 timer</string>
+ <string name="choice_8hours">8 timer</string>
+ <string name="choice_untilQuit">Indtil slut</string>
+ <string name="dsa">DSA</string>
+ <string name="elgamal">ElGamal</string>
+ <string name="rsa">RSA</string>
+ <string name="filemanager_titleOpen">Åbn...</string>
+ <string name="filemanager_titleSave">Gem Som...</string>
+ <string name="filemanager_titleEncrypt">Vælg Fil Som Skal Krypteres...</string>
+ <string name="filemanager_titleDecrypt">Vælg Fil Som Skal Afkrypteres...</string>
+ <string name="filemanager_btnOpen">Åbn</string>
+ <string name="filemanager_btnSave">Gem</string>
+ <string name="warning">Advarsel</string>
+ <string name="error">Fejl</string>
+ <string name="warningMessage">Advarsel: %s</string>
+ <string name="errorMessage">Fejl: %s</string>
+
+ <!-- sentences -->
+ <string name="wrongPassPhrase">Forkert kodeord.</string>
+ <string name="usingClipboardContent">Bruger clipboardets indhold.</string>
+ <string name="keySaved">Nøgle gemt.</string>
+ <string name="setAPassPhrase">Sæt et kodeord først.</string>
+ <string name="noFilemanagerInstalled">Der er ikke installeret en kompatibel fil håndtering.</string>
+ <string name="passPhrasesDoNotMatch">Kodeordet matchede ikke.</string>
+ <string name="passPhraseMustNotBeEmpty">Et tomt kodeord er ikke tilladt.</string>
+ <string name="passPhraseForSymmetricEncryption">Symmetrisk kryptering.</string>
+ <string name="passPhraseFor">%s</string>
+ <string name="fileDeleteConfirmation">Er du sikker på at du vil slette \n%s?</string>
+ <string name="fileDeleteSuccessful">Filen er slettet.</string>
+ <string name="noFileSelected">Vælg en fil først.</string>
+ <string name="decryptionSuccessful">Afkryptering lykkedes.</string>
+ <string name="encryptionSuccessful">Kryptering lykkedes.</string>
+ <string name="encryptionToClipboardSuccessful">Kryptering til clipboard lykkedes.</string>
+ <string name="enterPassPhraseTwice">Tast kodeordet to gange.</string>
+ <string name="selectEncryptionKey">Vælg mindst én krypteringsnøgle.</string>
+ <string name="selectEncryptionOrSignatureKey">Vælg mindst én krypteringsnøgle eller signaturnøgle.</string>
+ <string name="specifyFileToEncryptTo">Angiv navnet på filen der skal krypteres til.\nADVARSEL! Filen vil blive overskrevet hvis den allerede eksisterer.</string>
+ <string name="specifyFileToDecryptTo">Angiv navnet på filen der skal afkrypteres til.\nADVARSEL! Filen vil blive overskrevet hvis den allerede eksisterer.</string>
+ <string name="specifyGoogleMailAccount">Angiv den Google Mail konto du ønsker at tilføje.</string>
+ <string name="specifyFileToImportFrom">Angiv hvilken fil du ønsker at importere nøgler fra. (.asc eller .gpg)</string>
+ <string name="specifyFileToExportTo">Angiv hvilken fil der skal eksporteres til.\nADVARSEL! Filen vil blive overskrevet hvis den allerede eksisterer.</string>
+ <string name="specifyFileToExportSecretKeysTo">Angiv hvilken fil der skal eksporteres til.\nADVARSEL! Du er ved at eksportere PRIVATE nøgler.\nADVARSEL! Filen vil blive overskrevet hvis den allerede eksisterer.</string>
+ <string name="keyDeletionConfirmation">Ønsker du at slette \'%s\'-nøglen?\nDu kan ikke fortryde denne handling!</string>
+ <string name="secretKeyDeletionConfirmation">Ønsker du at slette den PRIVATE nøgle, \'%s\'?\nDu kan ikke fortryde denne handling!</string>
+ <string name="keysAddedAndUpdated">Tilføjelse af %1$s nøgle(r) og opdatering af %2$s nøgle(r) lykkedes."</string>
+ <string name="keysAdded">Tilføjelse af %s nøgle(r) lykkedes.</string>
+ <string name="keysUpdated">Opdatring af %s nøgle(r) lykkedes.</string>
+ <string name="noKeysAddedOrUpdated">Ingen nøgler blev tilføjet eller opdateret.</string>
+ <string name="keyExported">Eksport af 1 nøgle lykkedes.</string>
+ <string name="keysExported">Eksport af %s nøgler lykkedes.</string>
+ <string name="noKeysExported">Ingen nøgler blev eksporteret.</string>
+ <string name="keyCreationElGamalInfo">NB: Kun undernøgler understøtter ElGamal, og for ELGamal vil den nærmeste nøglestørrelse - 1536, 2048, 3072, 4096, eller 8192 - blive brugt.</string>
+ <string name="keyNotFound">Kunne ikke finde %08X nøglen.</string>
+
+ <!--
+ error_lowerCase: phrases, no punctuation, all lowercase,
+ they will be put after "errorMessage", e.g. "Error: file not found"
+ -->
+ <string name="error_fileDeleteFailed">\'%s\' kunne ikke slettes</string>
+ <string name="error_fileNotFound">filen findes ikke</string>
+ <string name="error_noSecretKeyFound">ingen egnet privat nøgle fundet</string>
+ <string name="error_noKnownEncryptionFound">ingen kendt krypterings måde fundet</string>
+ <string name="error_externalStorageNotReady">ekstern storage er ikke parat</string>
+ <string name="error_accountNotFound">\'%s\'-kontoen blev ikke fundet</string>
+ <string name="error_addingAccountFailed">\'%s\'-kontoen kunne ikke tilføjes</string>
+ <string name="error_invalidEmail">ugyldig email \'%s\'</string>
+ <string name="error_keySizeMinimum512bit">nøgle størrelsen skal være på mindst 512bit</string>
+ <string name="error_masterKeyMustNotBeElGamal">hoved nøglen kan ikke være en ElGamal nøgle</string>
+ <string name="error_unknownAlgorithmChoice">ukendt algoritme valg</string>
+ <string name="error_userIdNeedsAName">angiv et navn</string>
+ <string name="error_userIdNeedsAnEmailAddress">angiv en email adresse</string>
+ <string name="error_keyNeedsAUserId">der skal bruges mindst ét bruger-id</string>
+ <string name="error_mainUserIdMustNotBeEmpty">hovedbruger-id kan ikke være tomt</string>
+ <string name="error_keyNeedsMasterKey">der skal mindst bruges en hovednøgle</string>
+ <string name="error_expiryMustComeAfterCreation">udløbsdatoen skal være senere end oprettelsesdatoen</string>
+ <string name="error_noEncryptionKeysOrPassPhrase">hverken krypteringsnøgler eller kodeord modtaget</string>
+ <string name="error_signatureFailed">signering mislykkedes</string>
+ <string name="error_noSignaturePassPhrase">intet kodeord modtaget</string>
+ <string name="error_noSignatureKey">ingen signatur nøgle modtaget</string>
+ <string name="error_invalidData">ikke gyldig krypteringsdata</string>
+ <string name="error_corruptData">beskadiget data</string>
+ <string name="error_noSymmetricEncryptionPacket">kunne ikke finde en pakke med symmetrisk kryptering</string>
+ <string name="error_wrongPassPhrase">forkert kodeord</string>
+ <string name="error_savingKeys">der opstod en fejl da nøglen eller nøglerne skulle gemmes</string>
+
+ <!-- progress_lowerCase: lowercase, phrases, usually ending in '...' -->
+ <string name="progress_done">færdig.</string>
+ <string name="progress_initializing">starter op...</string>
+ <string name="progress_saving">gemmer...</string>
+ <string name="progress_importing">importerer...</string>
+ <string name="progress_exporting">eksporterer...</string>
+ <string name="progress_generating">genererer nøgle, dette kan tage et stykke tid...</string>
+ <string name="progress_buildingKey">opbygger nøgle...</string>
+ <string name="progress_preparingMasterKey">forbereder hovednøgle...</string>
+ <string name="progress_certifyingMasterKey">bekræfter hovednøgle...</string>
+ <string name="progress_buildingMasterKeyRing">bygger hovednøglering...</string>
+ <string name="progress_addingSubKeys">tilføjer undernøgler...</string>
+ <string name="progress_savingKeyRing">gemmer nøglering...</string>
+ <string name="progress_importingSecretKeys">importerer private nøgler...</string>
+ <string name="progress_importingPublicKeys">importerer offentlige nøgler...</string>
+ <string name="progress_reloadingKeys">genloader nøgler...</string>
+ <string name="progress_exportingKey">eksporterer nøgle...</string>
+ <string name="progress_exportingKeys">eksporterer nøgler...</string>
+ <string name="progress_extractingSignatureKey">henter signatur nøgle...</string>
+ <string name="progress_extractingKey">henter nøgle...</string>
+ <string name="progress_preparingStreams">forbereder streams...</string>
+ <string name="progress_encrypting">krypterer data...</string>
+ <string name="progress_decrypting">afkrypterer data...</string>
+ <string name="progress_preparingSignature">forbereder signatur...</string>
+ <string name="progress_generatingSignature">skaber signatur...</string>
+ <string name="progress_processingSignature">behandler signatur...</string>
+ <string name="progress_verifyingSignature">bekræfter signatur...</string>
+ <string name="progress_signing">signerer...</string>
+ <string name="progress_readingData">læser data...</string>
+ <string name="progress_findingKey">søger nøgle...</string>
+ <string name="progress_decompressingData">afkomprimerer data...</string>
+ <string name="progress_verifyingIntegrity">bekræfter integritet...</string>
+
+ <!-- permission strings -->
+ <string name="permission_read_key_details_label">Læs nøgle detaljer fra APG.</string>
+ <string name="permission_read_key_details_description">Læs om offentlige og private nøgler gemt i APG, f. eks. nøgle ID og bruger ID. Selve nøglerne kan IKKE læses.</string>
+
+ <!-- action strings -->
+ <string name="action_encrypt">Kryptér</string>
+ <string name="action_decrypt">Afkryptér</string>
+ <string name="action_importPublic">Importér Offentlige Nøgler</string>
+ <string name="action_importSecret">Importér Private Nøgler</string>
+ <string name="hint_publicKeys">Søg I Offentlige Nøgler</string>
+ <string name="hint_secretKeys">Søg I Private Nøgler</string>
+ <string name="filterInfo">Filter: \"%s\"</string>
+
+</resources> \ No newline at end of file
diff --git a/org_apg/res/values-de/strings.xml b/org_apg/res/values-de/strings.xml
new file mode 100644
index 000000000..732d58b0a
--- /dev/null
+++ b/org_apg/res/values-de/strings.xml
@@ -0,0 +1,269 @@
+<?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.
+
+ Author: Christian Woehrl <chw@wort-und-satz.de>
+-->
+
+<resources>
+
+ <!-- title_lowerCase: capitalized words, no punctuation -->
+ <string name="title_mailInbox">Posteingang</string>
+ <string name="title_managePublicKeys">Öffentliche Schlüssel verwalten</string>
+ <string name="title_manageSecretKeys">Private Schlüssel verwalten</string>
+ <string name="title_selectRecipients">Empfänger auswählen</string>
+ <string name="title_selectSignature">Signatur 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_createKey">Schlüssel erzeugen</string>
+ <string name="title_editKey">Schlüssel bearbeiten</string>
+ <string name="title_preferences">Einstellungen</string>
+ <string name="title_changePassPhrase">Passwort ändern</string>
+ <string name="title_setPassPhrase">Passwort wählen</string>
+ <string name="title_sendEmail">"Mail senden ..."</string>
+ <string name="title_encryptToFile">In eine Datei verschlüsseln</string>
+ <string name="title_decryptToFile">In eine Datei entschlüsseln</string>
+ <string name="title_addAccount">Konto hinzufügen</string>
+ <string name="title_importKeys">Schlüssel importieren</string>
+ <string name="title_exportKey">Einzelnen Schlüssel exportieren</string>
+ <string name="title_exportKeys">Mehrere Schlüssel exportieren</string>
+ <string name="title_keyNotFound">Kein Schlüssel gefunden</string>
+
+ <!-- section_lowerCase: capitalized words, no punctuation -->
+ <string name="section_userIds">Benutzer-IDs</string>
+ <string name="section_keys">Schlüssel</string>
+ <string name="section_general">Allgemein</string>
+ <string name="section_defaults">Voreinstellungen</string>
+
+ <!-- btn_lowerCase: capitalized words, no punctuation -->
+
+ <string name="btn_encryptToClipboard">In die Zwischenablage verschlüsseln</string>
+ <string name="btn_encryptAndEmail">Verschlüsseln und per Mail senden</string>
+ <string name="btn_encrypt">Verschlüsseln</string>
+ <string name="btn_decrypt">Entschlüsseln</string>
+ <string name="btn_verify">Verifizieren</string>
+ <string name="btn_selectEncryptKeys">Empfänger auswählen</string>
+ <string name="btn_reply">Antworten</string>
+ <string name="btn_encryptMessage">Nachricht verschlüsseln</string>
+ <string name="btn_decryptMessage">Nachricht entschlüsseln</string>
+ <string name="btn_encryptFile">Datei verschlüsseln</string>
+ <string name="btn_decryptFile">Datei entschlüsseln</string>
+ <string name="btn_save">Speichern</string>
+ <string name="btn_doNotSave">Abbrechen</string>
+ <string name="btn_delete">Löschen</string>
+ <string name="btn_noDate">Niemals</string>
+ <string name="btn_clearFilter">Filter zurücksetzen</string>
+ <string name="btn_changePassPhrase">Passwort ändern</string>
+ <string name="btn_setPassPhrase">Passwort wählen</string>
+
+ <!-- menu_lowerCase: capitalized words, no punctuation -->
+ <string name="menu_about">Über</string>
+ <string name="menu_addAccount">GMail-Konto hinzufügen</string>
+ <string name="menu_deleteAccount">Konto entfernen</string>
+ <string name="menu_managePublicKeys">Öffentliche Schlüssel verwalten</string>
+ <string name="menu_manageSecretKeys">Private Schlüssel verwalten</string>
+ <string name="menu_preferences">Einstellungen</string>
+ <string name="menu_importKeys">Schlüssel importieren</string>
+ <string name="menu_exportKeys">Mehrere Schlüssel exportieren</string>
+ <string name="menu_exportKey">Einzelnen Schlüssel exportieren</string>
+ <string name="menu_deleteKey">Einzelnen Schlüssel löschen</string>
+ <string name="menu_createKey">Schlüssel erzeugen</string>
+ <string name="menu_editKey">Schlüssel bearbeiten</string>
+ <string name="menu_search">Suche</string>
+
+ <!-- label_lowerCase: capitalized words, no punctuation -->
+ <string name="label_sign">Signieren</string>
+ <string name="label_message">Nachricht</string>
+ <string name="label_file">Datei</string>
+ <string name="label_passPhrase">Passwort</string>
+ <string name="label_passPhraseAgain">Wiederholen</string>
+ <string name="label_algorithm">Algorithmus</string>
+ <string name="label_asciiArmour">ASCII Armor</string>
+ <string name="label_selectPublicKeys">Öffentliche(r) Schlüssel</string>
+ <string name="label_deleteAfterEncryption">Nach Verschlüsselung löschen</string>
+ <string name="label_deleteAfterDecryption">Nach Entschlüsselung löschen</string>
+ <string name="label_encryptionAlgorithm">Verschlüsselungs-Algorithmus</string>
+ <string name="label_hashAlgorithm">Hash-Algorithmus</string>
+ <string name="label_asymmetric">Öffentlicher Schlüssel</string>
+ <string name="label_symmetric">Passwort</string>
+ <string name="label_passPhraseCacheTtl">Passwort-Cache</string>
+ <string name="label_messageCompression">Nachrichten-Kompression</string>
+ <string name="label_fileCompression">Datei-Kompression</string>
+ <string name="noKeysSelected">Auswählen</string>
+ <string name="oneKeySelected">1 ausgewählt</string>
+ <string name="nKeysSelected">Ausgewählt</string>
+ <string name="unknownUserId">&lt;Unbekannt></string>
+ <string name="none">&lt;Kein></string>
+ <string name="noKey">&lt;Kein Schlüssel></string>
+ <string name="noDate">-</string>
+ <string name="noExpiry">&lt;Kein Verfallsdatum></string>
+ <string name="unknownStatus"></string>
+ <string name="canEncrypt">Kann verschlüsseln</string>
+ <string name="canSign">Kann signieren</string>
+ <string name="expired">Verfallen</string>
+ <string name="notValid">Ungültig</string>
+
+ <!-- choice_lowerCase: capitalized first word, no punctuation -->
+ <string name="choice_none">Keine Auswahl</string>
+ <string name="choice_signOnly">Nur signieren</string>
+ <string name="choice_encryptOnly">Nur verschlüsseln</string>
+ <string name="choice_signAndEncrypt">Signieren und verschlüsseln</string>
+ <string name="choice_15secs">15 Sek.</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 Std.</string>
+ <string name="choice_2hours">2 Std.</string>
+ <string name="choice_4hours">4 Std.</string>
+ <string name="choice_8hours">8 Std.</string>
+ <string name="choice_untilQuit">Bis Programmende</string>
+ <string name="dsa">DSA</string>
+ <string name="elgamal">ElGamal</string>
+ <string name="rsa">RSA</string>
+ <string name="filemanager_titleOpen">Öffnen ...</string>
+ <string name="filemanager_titleSave">Speichern unter ...</string>
+ <string name="filemanager_titleEncrypt">Zu entschlüsselnde Datei wählen ...</string>
+ <string name="filemanager_titleDecrypt">Zu verschlüsselnde Datei wählen ...</string>
+ <string name="filemanager_btnOpen">Öffnen</string>
+ <string name="filemanager_btnSave">Speichern</string>
+ <string name="warning">Achtung</string>
+ <string name="error">Fehler</string>
+ <string name="warningMessage">Achtung: %s</string>
+ <string name="errorMessage">Fehler: %s</string>
+
+ <!-- sentences -->
+ <string name="wrongPassPhrase">Falsches Passwort.</string>
+ <string name="usingClipboardContent">Zwischenablage wird verwendet.</string>
+ <string name="keySaved">Schlüssel gespeichert.</string>
+ <string name="setAPassPhrase">Zuerst Passwort festlegen.</string>
+ <string name="noFilemanagerInstalled">Kein kompatibler Dateimanager installiert.</string>
+ <string name="passPhrasesDoNotMatch">Passwörter stimmen nicht überein.</string>
+ <string name="passPhraseMustNotBeEmpty">Leere Passwörter sind unzulässig.</string>
+ <string name="passPhraseForSymmetricEncryption">Symmetrische Verschlüsselung.</string>
+ <string name="passPhraseFor">%s</string>
+ <string name="fileDeleteConfirmation">Soll\n%s wirklich gelöscht werden?</string>
+ <string name="fileDeleteSuccessful">Erfolgreich gelöscht.</string>
+ <string name="noFileSelected">Zuerst eine Datei auswählen.</string>
+ <string name="decryptionSuccessful">Entschlüsselung erfolgreich.</string>
+ <string name="encryptionSuccessful">Verschlüsselung erfolgreich.</string>
+ <string name="encryptionToClipboardSuccessful">Verschlüsselung in die Zwischenablage erfolgreich.</string>
+ <string name="enterPassPhraseTwice">Passwort bitte zwei Mal eingeben.</string>
+ <string name="selectEncryptionKey">Bitte mindestens einen Schlüssel zur Verschlüsselung auswählen.</string>
+ <string name="selectEncryptionOrSignatureKey">Bitte mindestens einen Schlüssel zur Verschlüsselung oder zum Signieren auswählen.</string>
+ <string name="specifyFileToEncryptTo">Bitte Zieldatei für Verschlüsselung angeben.\nACHTUNG! Vorhandene Datei gleichen Namens wird überschrieben.</string>
+ <string name="specifyFileToDecryptTo">Bitte Zieldatei für Entschlüsselung angeben.\nACHTUNG! Vorhandene Datei gleichen Namens wird überschrieben.</string>
+ <string name="specifyGoogleMailAccount">Bitte Google-Mail-Konto angeben, das hinzugefügt werden soll.</string>
+ <string name="specifyFileToImportFrom">Bitte Datei angeben, aus der Schlüssel importiert werden sollen. (.asc oder .gpg)</string>
+ <string name="specifyFileToExportTo">Bitte Zieldatei zum Export angeben.\nACHTUNG! Vorhandene Datei gleichen Namens wird überschrieben.</string>
+ <string name="specifyFileToExportSecretKeysTo">Bitte Zieldatei zum Export angeben.\nACHTUNG! Es sollen private Schlüssel exportiert werden.\nACHTUNG! Vorhandene Datei gleichen Namens wird überschrieben.</string>
+ <string name="keyDeletionConfirmation">Soll der Schlüssel \'%s\' wirklich gelöscht werden?\nDiese Aktion lässt sich nicht rückgängig machen!</string>
+ <string name="secretKeyDeletionConfirmation">Soll der PRIVATE Schlüssel \'%s\' wirklich gelöscht werden?\nDiese Aktion lässt sich nicht rückgängig machen!</string>
+ <string name="keysAddedAndUpdated">Erfolgreich %1$s Schlüssel hinzugefügt und %2$s Schlüssel aktualisiert.</string>
+ <string name="keysAdded">Erfolgreich %s Schlüssel hinzugefügt.</string>
+ <string name="keysUpdated">Erfolgreich %s Schlüssel aktualisiert.</string>
+ <string name="noKeysAddedOrUpdated">Keine Schlüssel hinzugefügt oder aktualisiert.</string>
+ <string name="keyExported">Erfolgreich 1 Schlüssel exportiert.</string>
+ <string name="keysExported"> Erfolgreich %s Schlüssel exportiert.</string>
+ <string name="noKeysExported">Kein Schlüssel exportiert.</string>
+ <string name="keyCreationElGamalInfo">Hinweis: Nur Subschlüssel unterstützen ElGamal, und für ElGamal wird die nächstmögliche Schlüssellänge 1536, 2048, 3072, 4096 oder 8192 genutzt.</string>
+ <string name="keyNotFound">Es konnte kein Schlüssel %08X gefunden werden.</string>
+
+ <!--
+ error_lowerCase: phrases, no punctuation, all lowercase,
+ they will be put after "errorMessage", e.g. "Error: file not found"
+ -->
+ <string name="error_fileDeleteFailed">Löschen von \'%s\' fehlgeschlagen</string>
+ <string name="error_fileNotFound">Datei nicht gefunden</string>
+ <string name="error_noSecretKeyFound">Kein geeigneter privater Schlüssel gefunden</string>
+ <string name="error_noKnownEncryptionFound">Keine bekannte Verschlüsselungsmethode gefunden</string>
+ <string name="error_externalStorageNotReady">Externer Speicher nicht bereit</string>
+ <string name="error_accountNotFound">Konto \'%s\' nicht gefunden</string>
+ <string name="error_addingAccountFailed">Hinzufügen von Konto \'%s\' fehlgeschlagen</string>
+ <string name="error_invalidEmail">Ungültige E-Mail \'%s\'</string>
+ <string name="error_keySizeMinimum512bit">Schlüssellänge von mindestens 512bit erforderlich</string>
+ <string name="error_masterKeyMustNotBeElGamal">Primärschlüssel darf kein ElGamal-Schlüssel sein</string>
+ <string name="error_unknownAlgorithmChoice">Unbekannter Algorithmus ausgewählt</string>
+ <string name="error_userIdNeedsAName">Angabe eines Namens erforderlich</string>
+ <string name="error_userIdNeedsAnEmailAddress">Angabe einer E-Mail-Adresse erforderlich</string>
+ <string name="error_keyNeedsAUserId">Mindestens eine Benutzer-ID erforderlich</string>
+ <string name="error_mainUserIdMustNotBeEmpty">Haupt-Benutzer-ID darf nicht leer bleiben</string>
+ <string name="error_keyNeedsMasterKey">Mindestens ein Primärschlüssel erforderlich</string>
+ <string name="error_expiryMustComeAfterCreation">Verfallsdatum muss später als Erstellungsdatum liegen</string>
+ <string name="error_noEncryptionKeysOrPassPhrase">Kein(e) Schlüssel oder Passwort zur Verschlüsselung angegeben</string>
+ <string name="error_signatureFailed">Signieren fehlgeschlagen</string>
+ <string name="error_noSignaturePassPhrase">Kein Passwort angegeben</string>
+ <string name="error_noSignatureKey">Kein Schlüssel zum Signieren angegeben</string>
+ <string name="error_invalidData">Keine gültigen Verschlüsselungsdaten</string>
+ <string name="error_corruptData">Daten nicht lesbar</string>
+ <string name="error_noSymmetricEncryptionPacket">Kein Paket mit symmetrischer Verschlüsselung gefunden</string>
+ <string name="error_wrongPassPhrase">Falsches Passwort</string>
+ <string name="error_savingKeys">Fehler beim Abspeichern eines oder mehrerer Schlüssel</string>
+
+ <!-- progress_lowerCase: lowercase, phrases, usually ending in '...' -->
+ <string name="progress_done">Abgeschlossen.</string>
+ <string name="progress_initializing">Initialisierung ...</string>
+ <string name="progress_saving">Speicherung ...</string>
+ <string name="progress_importing">Import ...</string>
+ <string name="progress_exporting">Export ...</string>
+ <string name="progress_generating">Schlüssel wird erzeugt, dies kann eine Weile dauern ...</string>
+ <string name="progress_buildingKey">Schlüssel wird erstellt ...</string>
+ <string name="progress_preparingMasterKey">Primärschlüssel wird vorbereitet ...</string>
+ <string name="progress_certifyingMasterKey">Primärschlüssel wird zertifiziert ...</string>
+ <string name="progress_buildingMasterKeyRing">Primärschlüsselbund wird erstellt ...</string>
+ <string name="progress_addingSubKeys">Unterschlüssel werden hinzugefügt ...</string>
+ <string name="progress_savingKeyRing">Schlüsselbund wird gespeichert ...</string>
+ <string name="progress_importingSecretKeys">Private Schlüssel werden importiert ...</string>
+ <string name="progress_importingPublicKeys">Öffentliche Schlüssel werden importiert ...</string>
+ <string name="progress_reloadingKeys">Schlüssel werden neu geladen ...</string>
+ <string name="progress_exportingKey">Schlüssel wird exportiert ...</string>
+ <string name="progress_exportingKeys">Schlüssel werden exportiert ...</string>
+ <string name="progress_extractingSignatureKey">Signaturschlüssel wird extrahiert ...</string>
+ <string name="progress_extractingKey">Schlüssel wird extrahiert ...</string>
+ <string name="progress_preparingStreams">Daten werden vorbereitet ...</string>
+ <string name="progress_encrypting">Daten werden verschlüsselt ...</string>
+ <string name="progress_decrypting">Daten werden entschlüsselt ...</string>
+ <string name="progress_preparingSignature">Signatur wird vorbereitet ...</string>
+ <string name="progress_generatingSignature">Signatur wird erzeugt ...</string>
+ <string name="progress_processingSignature">Signatur wird verarbeitet...</string>
+ <string name="progress_verifyingSignature">Signatur wird verifiziert ...</string>
+ <string name="progress_signing">Signieren ...</string>
+ <string name="progress_readingData">Daten werden gelesen ...</string>
+ <string name="progress_findingKey">Schlüssel wird gesucht ...</string>
+ <string name="progress_decompressingData">Daten werden dekomprimiert ...</string>
+ <string name="progress_verifyingIntegrity">Integrität wird verifiziert ...</string>
+
+ <!-- permission strings -->
+ <string name="permission_read_key_details_label">Schlüsseldetails von APG auslesen.</string>
+ <string name="permission_read_key_details_description">Details der öffentlichen und privaten Schlüssel können von APG ausgelesen werden, etwa Schlüssel-ID und Benutzer-IDs. Die Schlüssel selbst können NICHT gelesen werden.</string>
+
+ <!-- action strings -->
+ <string name="action_encrypt">Verschlüsseln</string>
+ <string name="action_decrypt">Entschlüsseln</string>
+ <string name="action_importPublic">Öffentliche Schlüssel importieren</string>
+ <string name="action_importSecret">Private Schlüssel importieren</string>
+ <string name="hint_publicKeys">Öffentliche Schlüssel suchen</string>
+ <string name="hint_secretKeys">Private Schlüssel suchen</string>
+ <string name="filterInfo">Filter: \"%s\"</string>
+
+ <!-- misc -->
+ <string name="fast">schnell</string>
+ <string name="slow">langsam</string>
+ <string name="very_slow">sehr langsam</string>
+
+</resources> \ No newline at end of file
diff --git a/org_apg/res/values-es/strings.xml b/org_apg/res/values-es/strings.xml
new file mode 100644
index 000000000..a2faf56d9
--- /dev/null
+++ b/org_apg/res/values-es/strings.xml
@@ -0,0 +1,303 @@
+<?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.
+-->
+
+<resources>
+
+ <!-- title_lowerCase: capitalized words, no punctuation -->
+ <string name="title_mailInbox">Bandeja de Entrada</string>
+ <string name="title_managePublicKeys">Administrar Llaves Publicas</string>
+ <string name="title_manageSecretKeys">Administrar Llaves Privadas</string>
+ <string name="title_selectRecipients">Seleccionar Destinatarios</string>
+ <string name="title_selectSignature">Seleccionar Firma</string>
+ <string name="title_encrypt">Cifrar</string>
+ <string name="title_decrypt">Descifrar</string>
+ <string name="title_authentication">Frase de Paso</string>
+ <string name="title_createKey">Crear Llave</string>
+ <string name="title_editKey">Editar Llave</string>
+ <string name="title_preferences">Preferencias</string>
+ <string name="title_keyServerPreference">Preferencias del Servidor de Llaves</string>
+ <string name="title_changePassPhrase">Cambiar Frase de Paso</string>
+ <string name="title_setPassPhrase">Establecer Frase de Paso</string>
+ <string name="title_sendEmail">"Enviar Correo..."</string>
+ <string name="title_encryptToFile">Cifrar en Fichero</string>
+ <string name="title_decryptToFile">Descifrar en Fichero</string>
+ <string name="title_addAccount">Añadir Cuenta</string>
+ <string name="title_importKeys">Importar Llaves</string>
+ <string name="title_exportKey">Exportar Llave</string>
+ <string name="title_exportKeys">Exportar Llaves</string>
+ <string name="title_keyNotFound">Llave no Encontrada</string>
+ <string name="title_help">Empezando</string>
+ <string name="title_keyServerQuery">Petición al Servidor de Llaves</string>
+
+ <!-- section_lowerCase: capitalized words, no punctuation -->
+ <string name="section_userIds">Identidades de Usuario</string>
+ <string name="section_keys">Llaves</string>
+ <string name="section_general">General</string>
+ <string name="section_defaults">Valores por Defecto</string>
+ <string name="section_advanced">Avanzado</string>
+
+ <!-- btn_lowerCase: capitalized words, no punctuation -->
+ <string name="btn_signToClipboard">Firmar En Portapapeles</string>
+ <string name="btn_encryptToClipboard">Cifrar En Portapapeles</string>
+ <string name="btn_encryptAndEmail">Cifrar y Enviar</string>
+ <string name="btn_signAndEmail">Firmar y Enviar</string>
+ <string name="btn_encrypt">Cifrar</string>
+ <string name="btn_sign">Firmar</string>
+ <string name="btn_decrypt">Descifrar</string>
+ <string name="btn_verify">Verificar</string>
+ <string name="btn_selectEncryptKeys">Seleccionar Destinatarios</string>
+ <string name="btn_reply">Contestar</string>
+ <string name="btn_encryptMessage">Cifrar Mensaje</string>
+ <string name="btn_decryptMessage">Descifrar Mensaje</string>
+ <string name="btn_encryptFile">Cifrar Fichero</string>
+ <string name="btn_decryptFile">Descifrar Mensaje</string>
+ <string name="btn_save">Guardar</string>
+ <string name="btn_doNotSave">Cancelar</string>
+ <string name="btn_delete">Borrar</string>
+ <string name="btn_noDate">Ninguno</string>
+ <string name="btn_clearFilter">Limpiar Filtro</string>
+ <string name="btn_changePassPhrase">Cambiar Frase de Paso</string>
+ <string name="btn_setPassPhrase">Establecer Frase de Paso</string>
+ <string name="btn_search">Buscar</string>
+
+ <!-- menu_lowerCase: capitalized words, no punctuation -->
+ <string name="menu_about">Acerca De</string>
+ <string name="menu_addAccount">Añadir Cuenta de Gmail</string>
+ <string name="menu_deleteAccount">Borrar Cuenta</string>
+ <string name="menu_managePublicKeys">Administrar Llaves Publicas</string>
+ <string name="menu_manageSecretKeys">Administrar Llaves Privadas</string>
+ <string name="menu_preferences">Preferencias</string>
+ <string name="menu_importKeys">Importar Llaves</string>
+ <string name="menu_exportKeys">Exportar Llaves</string>
+ <string name="menu_exportKey">Exportar Llave</string>
+ <string name="menu_deleteKey">Borrar Llave</string>
+ <string name="menu_createKey">Crear Llave</string>
+ <string name="menu_editKey">Editar Llave</string>
+ <string name="menu_search">Buscar</string>
+ <string name="menu_help">Ayuda</string>
+ <string name="menu_keyServer">Servidor de Llaves</string>
+ <string name="menu_updateKey">Actualizar</string>
+
+ <!-- label_lowerCase: capitalized words, no punctuation -->
+ <string name="label_sign">Firmar</string>
+ <string name="label_message">Mensaje</string>
+ <string name="label_file">Fichero</string>
+ <string name="label_passPhrase">Frase de Paso</string>
+ <string name="label_passPhraseAgain">Otra Vez</string>
+ <string name="label_algorithm">Algoritmo</string>
+ <string name="label_asciiArmour">Armadura ASCII</string>
+ <string name="label_selectPublicKeys">Llave(s) Publica(s)</string>
+ <string name="label_deleteAfterEncryption">Borrar Una Vez Cifrado</string>
+ <string name="label_deleteAfterDecryption">Borrar Una Vez Descifrado</string>
+ <string name="label_deleteAfterImport">Borrar Tras Importar</string>
+ <string name="label_encryptionAlgorithm">Algoritmo De Cifrado</string>
+ <string name="label_hashAlgorithm">Algoritmo Hash</string>
+ <string name="label_asymmetric">Llave Pública</string>
+ <string name="label_symmetric">Frase de Paso</string>
+ <string name="label_passPhraseCacheTtl">Caché De La Frase de Paso</string>
+ <string name="label_messageCompression">Compresión de Mensaje</string>
+ <string name="label_fileCompression">Compresión de Fichero</string>
+ <string name="label_language">Idioma</string>
+ <string name="label_forceV3Signature">Forzar Firmas V3</string>
+ <string name="label_keyServers">Servidores De Llaves</string>
+ <string name="label_keyId">Identidad de Llave</string>
+ <string name="label_creation">Creación</string>
+ <string name="label_expiry">Expira</string>
+ <string name="label_usage">Uso</string>
+ <string name="label_keySize">Tamaño De La Llave</string>
+ <string name="label_mainUserId">Identidad Principal Del Usuario</string>
+ <string name="label_name">Nombre</string>
+ <string name="label_comment">Comentario</string>
+ <string name="label_email">Email</string>
+ <string name="noKeysSelected">Seleccionar</string>
+ <string name="oneKeySelected">1 Seleccionada</string>
+ <string name="nKeysSelected">Seleccionada</string>
+ <string name="unknownUserId">&lt;desconocido></string>
+ <string name="none">&lt;ninguno></string>
+ <string name="noKey">&lt;sin llave></string>
+ <string name="noDate">-</string>
+ <string name="noExpiry">&lt;no expira></string>
+ <string name="unknownStatus"></string>
+ <string name="canEncrypt">puede cifrar</string>
+ <string name="canSign">puede firmar</string>
+ <string name="expired">expirada</string>
+ <string name="notValid">no valida</string>
+ <string name="nKeyServers">%s servidor de llave(s)</string>
+
+ <!-- choice_lowerCase: capitalized first word, no punctuation -->
+ <string name="choice_none">Ninguno</string>
+ <string name="choice_signOnly">Sólo firmar</string>
+ <string name="choice_encryptOnly">Sólo cifrar</string>
+ <string name="choice_signAndEncrypt">Firmar y Cifrar</string>
+ <string name="choice_15secs">15 segs</string>
+ <string name="choice_1min">1 min</string>
+ <string name="choice_3mins">3 mins</string>
+ <string name="choice_5mins">5 mins</string>
+ <string name="choice_10mins">10 mins</string>
+ <string name="choice_20mins">20 mins</string>
+ <string name="choice_40mins">40 mins</string>
+ <string name="choice_untilQuit">hasta salir</string>
+ <string name="choice_language_system">Sistema por defecto</string>
+ <string name="dsa">DSA</string>
+ <string name="elgamal">ElGamal</string>
+ <string name="rsa">RSA</string>
+ <string name="filemanager_titleOpen">Abrir...</string>
+ <string name="filemanager_titleSave">Guardar Como...</string>
+ <string name="filemanager_titleEncrypt">Seleccionar Fichero Para Cifrar...</string>
+ <string name="filemanager_titleDecrypt">Seleccionar Fichero Para Descifrar...</string>
+ <string name="filemanager_btnOpen">Abrir</string>
+ <string name="filemanager_btnSave">Guardar</string>
+ <string name="warning">Advertencia</string>
+ <string name="error">Error</string>
+ <string name="warningMessage">Advertencia: %s</string>
+ <string name="errorMessage">Error: %s</string>
+
+ <!-- sentences -->
+ <string name="wrongPassPhrase">Frase de paso incorrecta.</string>
+ <string name="usingClipboardContent">Usando contenido del portapapeles.</string>
+ <string name="keySaved">Llave guardada.</string>
+ <string name="setAPassPhrase">Establece primero una frase de paso.</string>
+ <string name="noFilemanagerInstalled">Administrador de ficheros instalado no compatible.</string>
+ <string name="passPhrasesDoNotMatch">La frase de paso no concuerda.</string>
+ <string name="passPhraseMustNotBeEmpty">No se permiten palabras de paso vacías.</string>
+ <string name="passPhraseForSymmetricEncryption">Cifrado simetrico.</string>
+ <string name="passPhraseFor">%s</string>
+ <string name="fileDeleteConfirmation">¿Seguro que quieres borrar\n%s?</string>
+ <string name="fileDeleteSuccessful">Borrado completo.</string>
+ <string name="noFileSelected">Selecciona primero un fichero.</string>
+ <string name="decryptionSuccessful">Descifrado correcto.</string>
+ <string name="encryptionSuccessful">Cifrado correcto.</string>
+ <string name="encryptionToClipboardSuccessful">Cifrado en portapapeles correcto.</string>
+ <string name="enterPassPhraseTwice">Introduce la frase de paso dos veces.</string>
+ <string name="selectEncryptionKey">Selecciona al menos una llave de cifrado.</string>
+ <string name="selectEncryptionOrSignatureKey">Selecciona al menos una llave de cifrado o de firma.</string>
+ <string name="specifyFileToEncryptTo">Por favor, indica el nombre del fichero a cifrar.\nADVERTENCIA! El fichero se sobre-escribirá si ya existe.</string>
+ <string name="specifyFileToDecryptTo">Por favor, indica el nombre del fichero a descifrar.\nADVERTENCIA! El fichero se sobre-escribirá si ya existe.</string>
+ <string name="specifyGoogleMailAccount">Indica la cuenta de Google Mail que quieres añadir.</string>
+ <string name="specifyFileToImportFrom">Por favor, indica el fichero desde el cual importar las llaves. (.asc o .gpg)</string>
+ <string name="specifyFileToExportTo">Por favor, indica el fichero al que exportar.\nADVERTENCIA! El fichero se sobre-escribirá si ya existe.</string>
+ <string name="specifyFileToExportSecretKeysTo">Por favor, indica el fichero al que exportar.\nADVERTENCIA! Estás a punto de exportar las llaves PRIVADAS.\nADVERTENCIA! El fichero se sobre-escribirá si ya existe.</string>
+ <string name="keyDeletionConfirmation">¿Realmente quieres borrar la llave \'%s\'?\nNo puede deshacerse!</string>
+ <string name="secretKeyDeletionConfirmation">¿Realmente quieres borrar la llave SECRETA \'%s\'?\nNo puede deshacerse!</string>
+ <string name="keysAddedAndUpdated">Se han añadido %1$s llave(s) y actualizado %2$s llave(s) de forma correcta."</string>
+ <string name="keysAdded">Se ha(n) añadido %s llave(s) de forma correcta.</string>
+ <string name="keysUpdated">Se ha(n) actualizado %s llave(s) de forma correcta.</string>
+ <string name="noKeysAddedOrUpdated">No se ha añadido ni actualizado ninguna llave.</string>
+ <string name="keyExported">Se ha exportado correctamente 1 llave.</string>
+ <string name="keysExported">Se han exportado %s llaves de forma correcta.</string>
+ <string name="noKeysExported">No se han exportado llaves.</string>
+ <string name="keyCreationElGamalInfo">Nota: solamente las sub-llaves admiten ElGamal, y para ElGamal se usará el tamaño más cercano a 1536, 2048, 3072, 4096, o 8192.</string>
+ <string name="keyNotFound">No se pudo encontrar la llave %08X.</string>
+ <string name="keysFound">Se ha(n) encontrado %s llave(s).</string>
+ <string name="unknownSignatureKeyTouchToLookUp">Firma desconocida, pulsa para buscarla.</string>
+ <string name="keyEditingIsBeta">Edición de llaves aún en fase beta.</string>
+
+ <!--
+ error_lowerCase: phrases, no punctuation, all lowercase,
+ they will be put after "errorMessage", e.g. "Error: file not found"
+ -->
+ <string name="error_fileDeleteFailed">borrado de \'%s\' fallido</string>
+ <string name="error_fileNotFound">no se encontró el fichero</string>
+ <string name="error_noSecretKeyFound">no se encontró la llave secreta adecuada</string>
+ <string name="error_noKnownEncryptionFound">no se encontró un medio de cifrado</string>
+ <string name="error_externalStorageNotReady">el sistema de almacenamiento externo no está preparado</string>
+ <string name="error_accountNotFound">la cuenta \'%s\' no se encontró</string>
+ <string name="error_accountReadingNotAllowed">no tienes permisos para leer esta cuenta</string>
+ <string name="error_addingAccountFailed">la creación de la cuenta \'%s\' ha fallado</string>
+ <string name="error_invalidEmail">dirección de correo inválida \'%s\'</string>
+ <string name="error_keySizeMinimum512bit">el tamaño mínimo debe ser de 512bits</string>
+ <string name="error_masterKeyMustNotBeElGamal">la llave maestra no puede ser ElGamal</string>
+ <string name="error_unknownAlgorithmChoice">algoritmo desconocido</string>
+ <string name="error_userIdNeedsAName">necesitas especificar un nombre</string>
+ <string name="error_userIdNeedsAnEmailAddress">necesitas indicat una dirección de correo</string>
+ <string name="error_keyNeedsAUserId">necesitas al menos una identidad de usuario</string>
+ <string name="error_mainUserIdMustNotBeEmpty">el usuario principal no puede estar vacío</string>
+ <string name="error_keyNeedsMasterKey">necesitas al menos una llae maestra</string>
+ <string name="error_expiryMustComeAfterCreation">la fecha de expiración debe ser posterior a la fecha de creación</string>
+ <string name="error_noEncryptionKeysOrPassPhrase">no se ha(n) indicado llave(s) de cifrado o frase de paso</string>
+ <string name="error_signatureFailed">el proceso de firma ha fallado</string>
+ <string name="error_noSignaturePassPhrase">no se ha indicado frase de paso</string>
+ <string name="error_noSignatureKey">no se ha indicado llave de firma</string>
+ <string name="error_invalidData">datos de cifrado no válidos</string>
+ <string name="error_corruptData">datos corruptos</string>
+ <string name="error_noSymmetricEncryptionPacket">no se ha podido encontrar un paquete con cifrado simétrico</string>
+ <string name="error_wrongPassPhrase">frase de paso incorrecta</string>
+ <string name="error_savingKeys">hubo un error al guardar alguna(s) llave(s)</string>
+
+ <!-- progress_lowerCase: lowercase, phrases, usually ending in '...' -->
+ <string name="progress_done">hecho.</string>
+ <string name="progress_initializing">iniciando...</string>
+ <string name="progress_saving">guardando...</string>
+ <string name="progress_importing">importando...</string>
+ <string name="progress_exporting">exportando...</string>
+ <string name="progress_generating">generando la llave, esto puede tardar un rato...</string>
+ <string name="progress_buildingKey">creando la llave...</string>
+ <string name="progress_preparingMasterKey">preparando la llave maestra...</string>
+ <string name="progress_certifyingMasterKey">certificando la llave maestra...</string>
+ <string name="progress_buildingMasterKeyRing">creando el anillo maestro de llaves...</string>
+ <string name="progress_addingSubKeys">añadiendo sub-llaves...</string>
+ <string name="progress_savingKeyRing">guardando anillo de llaves...</string>
+ <string name="progress_importingSecretKeys">importando llaves privadas...</string>
+ <string name="progress_importingPublicKeys">importando llaves públicas...</string>
+ <string name="progress_reloadingKeys">re-cargando llaves...</string>
+ <string name="progress_exportingKey">exportando llave...</string>
+ <string name="progress_exportingKeys">exportando llaves...</string>
+ <string name="progress_extractingSignatureKey">extrayendo llave de firma...</string>
+ <string name="progress_extractingKey">extrayendo llave...</string>
+ <string name="progress_preparingStreams">preparando flujos...</string>
+ <string name="progress_encrypting">cifrando datos...</string>
+ <string name="progress_decrypting">descifrando datos...</string>
+ <string name="progress_preparingSignature">preparando firma...</string>
+ <string name="progress_generatingSignature">generando firma...</string>
+ <string name="progress_processingSignature">procesanto firma...</string>
+ <string name="progress_verifyingSignature">verificando firma...</string>
+ <string name="progress_signing">firmando...</string>
+ <string name="progress_readingData">leyendo datos...</string>
+ <string name="progress_findingKey">buscando la llave...</string>
+ <string name="progress_decompressingData">descomprimiendo datos...</string>
+ <string name="progress_verifyingIntegrity">verificando integridad...</string>
+ <string name="progress_deletingSecurely">borrando \'%s\' de forma segura...</string>
+ <string name="progress_querying">realizando petición...</string>
+ <string name="progress_queryingServer">realizando %s petición(es)...</string>
+
+ <!-- permission strings -->
+ <string name="permission_read_key_details_label">Leer detalles de llave desde APG.</string>
+ <string name="permission_read_key_details_description">Leer detalles de llaves pública y privada almacenados en APG, como identidades de llave y usuario. Las llaves en si NO se pueden leer.</string>
+
+ <!-- action strings -->
+ <string name="action_encrypt">Cifrar</string>
+ <string name="action_decrypt">Descifrar</string>
+ <string name="action_importPublic">Importar Llaves Públicas</string>
+ <string name="action_importSecret">Importar Llaves Privadas</string>
+ <string name="hint_publicKeys">Buscar Llaves Públicas</string>
+ <string name="hint_secretKeys">Buscar Llaves Privadas</string>
+ <string name="filterInfo">Filtro: \"%s\"</string>
+
+ <!-- misc -->
+ <string name="fast">rápido</string>
+ <string name="slow">lento</string>
+ <string name="very_slow">muy lento</string>
+
+ <!-- texts -->
+ <!-- "OI File Manager", "ASTRO", and "K-9 Mail" must not be translated in order for the links to the market to work. -->
+ <string name="text_help">uedes instalar K-9 Mail para una mejor integración, soporta APG para PGP de forma nativa y permite difrar/descifrar mensajes directamente.
+\n\nSe recomienda instalar OI File Manager o ASTRO para poder usar el botón de navegación para seleccioar ficheros en APG.
+\n\nPrimero necesitarás algunas llaves. Puedes importarlas mediante los menús de opciones en \"Administrar Llaves Públicas\" y \"Administrar Llaves Privadas\" o crearlas mediante \"Administrar Llaves Privadas\".
+\n\nTambién puedes añadir una cuenta GMail como actividad principal mediante \"Añadir Cuenta\", lo que simplifica el descifrado de mensajes recibidos en esa cuenta.
+\n\nDale un vistazo a los menús de opciones para encontrar más funciones.</string>
+
+</resources> \ No newline at end of file
diff --git a/org_apg/res/values-it/strings.xml b/org_apg/res/values-it/strings.xml
new file mode 100644
index 000000000..21a6d3486
--- /dev/null
+++ b/org_apg/res/values-it/strings.xml
@@ -0,0 +1,314 @@
+<?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.
+
+ Author: Fabrizio "Rick Deckard" Buzzi <deckardb76@gmail.com>
+-->
+
+<resources>
+
+ <!-- title_lowerCase: capitalized words, no punctuation -->
+ <string name="title_mailInbox">Posta in arrivo</string>
+ <string name="title_managePublicKeys">Gestione Chiave Pubblica</string>
+ <string name="title_manageSecretKeys">Gestione Chiave Privata</string>
+ <string name="title_selectRecipients">Seleziona i Destinatari</string>
+ <string name="title_selectSignature">Seleziona Firma</string>
+ <string name="title_encrypt">Critta</string>
+ <string name="title_decrypt">Decritta</string>
+ <string name="title_authentication">Pass Phrase</string>
+ <string name="title_createKey">Crea Chiave</string>
+ <string name="title_editKey">Modifica Chiave</string>
+ <string name="title_preferences">Preferenze</string>
+ <string name="title_keyServerPreference">Preferenze Key Server</string>
+ <string name="title_changePassPhrase">Cambia Pass Phrase</string>
+ <string name="title_setPassPhrase">Imposta Pass Phrase</string>
+ <string name="title_sendEmail">"Invia mail..."</string>
+ <string name="title_encryptToFile">Critta su File</string>
+ <string name="title_decryptToFile">Decritta su File</string>
+ <string name="title_addAccount">Aggiungi Acconto</string>
+ <string name="title_importKeys">Importa Chiavi</string>
+ <string name="title_exportKey">Esporta Chiave</string>
+ <string name="title_exportKeys">Esporta Chiavi</string>
+ <string name="title_keyNotFound">Chiave non trovata</string>
+ <string name="title_help">Guida</string>
+ <string name="title_keyServerQuery">Query Key Server</string>
+ <string name="title_unknownSignatureKey">Firma chiave sconosciuta</string>
+
+ <!-- section_lowerCase: capitalized words, no punctuation -->
+ <string name="section_userIds">User IDs</string>
+ <string name="section_keys">Chiavi</string>
+ <string name="section_general">Generale</string>
+ <string name="section_defaults">Defaults</string>
+ <string name="section_advanced">Avanzate</string>
+
+ <!-- btn_lowerCase: capitalized words, no punctuation -->
+ <string name="btn_signToClipboard">Firma su Clipboard</string>
+ <string name="btn_encryptToClipboard">Critta su Clipboard</string>
+ <string name="btn_encryptAndEmail">Critta ed invia per mail</string>
+ <string name="btn_signAndEmail">Firma ed invia per mail</string>
+ <string name="btn_encrypt">Critta</string>
+ <string name="btn_sign">Firma</string>
+ <string name="btn_decrypt">Decritta</string>
+ <string name="btn_verify">Verifica</string>
+ <string name="btn_selectEncryptKeys">Seleziona i Destinatari</string>
+ <string name="btn_reply">Rispondi</string>
+ <string name="btn_encryptMessage">Critta Messaggio</string>
+ <string name="btn_decryptMessage">Decritta Messaggio</string>
+ <string name="btn_encryptFile">Critta File</string>
+ <string name="btn_decryptFile">Decritta File</string>
+ <string name="btn_save">Salva</string>
+ <string name="btn_doNotSave">Annulla</string>
+ <string name="btn_delete">Cancella</string>
+ <string name="btn_noDate">Nessuno</string>
+ <string name="btn_clearFilter">Cancella Filtro</string>
+ <string name="btn_changePassPhrase">Cambia Pass Phrase</string>
+ <string name="btn_setPassPhrase">Imposta Pass Phrase</string>
+ <string name="btn_search">Cerca</string>
+
+ <!-- menu_lowerCase: capitalized words, no punctuation -->
+ <string name="menu_about">Informazioni</string>
+ <string name="menu_addAccount">Aggiungi Acconto GMail</string>
+ <string name="menu_deleteAccount">Rimuovi Acconto</string>
+ <string name="menu_managePublicKeys">Gestisci Chiavi Pubbliche</string>
+ <string name="menu_manageSecretKeys">Gestisci Chiavi Private</string>
+ <string name="menu_preferences">Impostazioni</string>
+ <string name="menu_importKeys">Importa Chiavi</string>
+ <string name="menu_exportKeys">Esporta Chiavi</string>
+ <string name="menu_exportKey">Esporta Chiave</string>
+ <string name="menu_deleteKey">Rimuovi Chiave</string>
+ <string name="menu_createKey">Crea Chiave</string>
+ <string name="menu_editKey">Modifica Chiave</string>
+ <string name="menu_search">Cerca</string>
+ <string name="menu_help">Aiuto</string>
+ <string name="menu_keyServer">Key Server</string>
+ <string name="menu_updateKey">Aggiorna</string>
+
+ <!-- label_lowerCase: capitalized words, no punctuation -->
+ <string name="label_sign">Firma</string>
+ <string name="label_message">Messaggio</string>
+ <string name="label_file">File</string>
+ <string name="label_passPhrase">Pass Phrase</string>
+ <string name="label_passPhraseAgain">Conferma Pass Phrase</string>
+ <string name="label_algorithm">Algoritmo</string>
+ <string name="label_asciiArmour">ASCII Armour</string>
+ <string name="label_selectPublicKeys">Chiavi Pubbliche</string>
+ <string name="label_deleteAfterEncryption">Cancella dopo la crittazione</string>
+ <string name="label_deleteAfterDecryption">Cancella dopo la decrittazione</string>
+ <string name="label_deleteAfterImport">Cancella dopo l\'importazione</string>
+ <string name="label_encryptionAlgorithm">Algoritmo di Crittazione</string>
+ <string name="label_hashAlgorithm">Algoritmo di Hash</string>
+ <string name="label_asymmetric">Chiave Pubblica</string>
+ <string name="label_symmetric">Pass Phrase</string>
+ <string name="label_passPhraseCacheTtl">Pass Phrase Cache</string>
+ <string name="label_messageCompression">Compressione Messaggio</string>
+ <string name="label_fileCompression">Compressione File</string>
+ <string name="label_language">Lingua</string>
+ <string name="label_forceV3Signature">Forza l\'uso di firme V3</string>
+ <string name="label_keyServers">Key Servers</string>
+ <string name="label_keyId">ID chiave</string>
+ <string name="label_creation">Creazione</string>
+ <string name="label_expiry">Scadenza</string>
+ <string name="label_usage">Utilizzo</string>
+ <string name="label_keySize">Dimensione chiave</string>
+ <string name="label_mainUserId">User ID principale</string>
+ <string name="label_name">Nome</string>
+ <string name="label_comment">Commento</string>
+ <string name="label_email">Email</string>
+ <string name="noKeysSelected">Seleziona</string>
+ <string name="oneKeySelected">1 Selezionato</string>
+ <string name="nKeysSelected">Selezionato</string>
+ <string name="unknownUserId">&lt;sconosciuto></string>
+ <string name="none">&lt;nessuno></string>
+ <string name="noKey">&lt;nessuna chiave></string>
+ <string name="noDate">-</string>
+ <string name="noExpiry">&lt;nessuna scadenza></string>
+ <string name="unknownStatus"></string>
+ <string name="canEncrypt">crittazione consentita</string>
+ <string name="canSign">firma consentita</string>
+ <string name="expired">scaduto</string>
+ <string name="notValid">non valido</string>
+ <string name="nKeyServers">%s key server(s)</string>
+ <string name="fingerprint">fingerprint</string>
+
+ <!-- choice_lowerCase: capitalized firwst word, no punctuation -->
+ <string name="choice_none">Nessuno</string>
+ <string name="choice_signOnly">Solo Firma</string>
+ <string name="choice_encryptOnly">Critta solamente</string>
+ <string name="choice_signAndEncrypt">Firma e critta</string>
+ <string name="choice_15secs">15 sec</string>
+ <string name="choice_1min">1 min</string>
+ <string name="choice_3mins">3 min</string>
+ <string name="choice_5mins">5 min</string>
+ <string name="choice_10mins">10 min</string>
+ <string name="choice_20mins">20 min</string>
+ <string name="choice_40mins">40 min</string>
+ <string name="choice_1hour">1 ora</string>
+ <string name="choice_2hours">2 ore</string>
+ <string name="choice_4hours">4 ore</string>
+ <string name="choice_8hours">8 ore</string>
+ <string name="choice_untilQuit">tutta la sessione</string>
+ <string name="choice_language_system">Default di sistema</string>
+ <string name="dsa">DSA</string>
+ <string name="elgamal">ElGamal</string>
+ <string name="rsa">RSA</string>
+ <string name="filemanager_titleOpen">Apri...</string>
+ <string name="filemanager_titleSave">Salva con nome...</string>
+ <string name="filemanager_titleEncrypt">Seleziona il file da crittare...</string>
+ <string name="filemanager_titleDecrypt">Seleziona il file da decrittare...</string>
+ <string name="filemanager_btnOpen">Apri</string>
+ <string name="filemanager_btnSave">Salva</string>
+ <string name="warning">Attenzione</string>
+ <string name="error">Errore</string>
+ <string name="warningMessage">Attenzione: %s</string>
+ <string name="errorMessage">Errore: %s</string>
+
+ <!-- sentences -->
+ <string name="wrongPassPhrase">Pass Phrase errata.</string>
+ <string name="usingClipboardContent">Utilizza il contenuto della clipboard.</string>
+ <string name="keySaved">Chiave salvata..</string>
+ <string name="setAPassPhrase">Imposta prima una Pass Phrase.</string>
+ <string name="noFilemanagerInstalled">Nessun file manager compatibile installato.</string>
+ <string name="passPhrasesDoNotMatch">La Pass Phrase non è corretta</string>
+ <string name="passPhraseMustNotBeEmpty">La Pass Phrases non può essere vuota.</string>
+ <string name="passPhraseForSymmetricEncryption">Crittazione simmetrica.</string>
+ <string name="passPhraseFor">%s</string>
+ <string name="fileDeleteConfirmation">Sei sicuro di procedere con la cancellazione di\n%s?</string>
+ <string name="fileDeleteSuccessful">Cancellazione eseguita..</string>
+ <string name="noFileSelected">Selezionare un file.</string>
+ <string name="decryptionSuccessful">Decrittazione eseguita.</string>
+ <string name="encryptionSuccessful">Crittazione eseguita.</string>
+ <string name="encryptionToClipboardSuccessful">Crittazione vero la clipboard eseguita.</string>
+ <string name="enterPassPhraseTwice">Scrivere la Pass Phrase due volte.</string>
+ <string name="selectEncryptionKey">Selezionare almeno una chiave.</string>
+ <string name="selectEncryptionOrSignatureKey">Selezionare almeno una chiave oppure una firma.</string>
+ <string name="specifyFileToEncryptTo">Specificare il file che si desidera crittare.\nATTENZIONE! Se il file esiste, sarà sovrascritto.</string>
+ <string name="specifyFileToDecryptTo">Specificare il file che si desidera decrittare.\nATTENZIONE! Se il file esiste, sarà sovrascritto.</string>
+ <string name="specifyGoogleMailAccount">Specificare l\'acconto Google Mail che si vuole aggiungere.</string>
+ <string name="specifyFileToImportFrom">Specificare il file da cui importare le chiavi (.asc or .gpg).</string>
+ <string name="specifyFileToExportTo">Specificare il file per l\'esportazione.\nATTENZIONE! Se il file esiste, sarà sovrascritto.</string>
+ <string name="specifyFileToExportSecretKeysTo">Specificare il file per l\'esportazione.\nATTENZIONE! Stai esportando la chiave privata.\nATTENZIONE! Se il file esiste, sarà sovrascritto.</string>
+ <string name="keyDeletionConfirmation">Confermi la cancellazione della chiave \'%s\'?\nL\'operazione è irreversibile!</string>
+ <string name="secretKeyDeletionConfirmation">Confermi la cancellazione della chiave PRIVATA \'%s\'?\nL\'operazione è irreversibile!</string>
+ <string name="keysAddedAndUpdated">Sono state correttamente inserite %1$s chiavi ed aggiornate %2$s chiavi."</string>
+ <string name="keysAdded">Sono state correttamente inserite %s chiavi.</string>
+ <string name="keysUpdated">Sono state correttamente aggiornate %s chiavi.</string>
+ <string name="noKeysAddedOrUpdated">Nessuna chiave è stata aggiunta o modificata.</string>
+ <string name="keyExported">La chiave è stata correttamente esportata.</string>
+ <string name="keysExported">Sono state correttamente esportate %s chiavi.</string>
+ <string name="noKeysExported">Nessuna chiave è stata esportata.</string>
+ <string name="keyCreationElGamalInfo">Nota: solo le sotto-chiavi supportano ElGamal, e per ElGamal sarà utilizzata la dimensione più vicina a 1536, 2048, 3072, 4096, oppure 8192.</string>
+ <string name="keyNotFound">Non è stato possibile trovare la chiave %08X.</string>
+ <string name="keysFound">Trovate %s chiavi.</string>
+ <string name="unknownSignatureKeyTouchToLookUp">Firma sconosciuta, premi per controllare la chiave.</string>
+ <string name="keyEditingIsBeta">L\'editing delle chiavi è una funzionalità ancora in fase beta.</string>
+ <string name="badKeysEncountered">%s chiavi segrete ignorate. Se hai esportato le chiavi con l\'opzione\n --export-secret-subkeys\nprovare ad utilizzare il parametro\n --export-secret-keys\n.</string>
+ <string name="lookupUnknownKey">La chiave %s è sconosciuta; si desidera provare a cercarla su un keyserver?</string>
+
+ <!--
+ error_lowerCase: phrases, no punctuation, all lowercase,
+ they will be put after "errorMessage", e.g. "Error: file not found"
+ -->
+ <string name="error_fileDeleteFailed">cancellazione di \'%s\' fallita</string>
+ <string name="error_fileNotFound">file non trovato</string>
+ <string name="error_noSecretKeyFound">non è stata trovata nessuna chiave privata utilizzabile</string>
+ <string name="error_noKnownEncryptionFound">non è stato trovato nessun tipo di crittazione valido</string>
+ <string name="error_externalStorageNotReady">la scheda di memoria non è pronta o non è utilizzabile</string>
+ <string name="error_accountNotFound">acconto \'%s\' non trovato</string>
+ <string name="error_accountReadingNotAllowed">accesso in lettura all\'account negato</string>
+ <string name="error_addingAccountFailed">non è stato possibile aggiungere l\'acconto \'%s\'</string>
+ <string name="error_invalidEmail">mail non valida \'%s\'</string>
+ <string name="error_keySizeMinimum512bit">la dimensione della chiave deve essere almeno di 512bit</string>
+ <string name="error_masterKeyMustNotBeElGamal">la chiave master non può essere una chiave ElGamal</string>
+ <string name="error_unknownAlgorithmChoice">algoritmo selezionato sconosciuto</string>
+ <string name="error_userIdNeedsAName">è necessario specificare un nome</string>
+ <string name="error_userIdNeedsAnEmailAddress">è necessario specificare una email</string>
+ <string name="error_keyNeedsAUserId">è necessario almeno uno user id</string>
+ <string name="error_mainUserIdMustNotBeEmpty">lo user id principale non può essere vuoto</string>
+ <string name="error_keyNeedsMasterKey">è necessaria almeno una chiave master</string>
+ <string name="error_expiryMustComeAfterCreation">la data di scadenza deve essere successiva alla data di creazione</string>
+ <string name="error_noEncryptionKeysOrPassPhrase">non è stata specifica una chiave oppure una Pass Phrase</string>
+ <string name="error_signatureFailed">firma fallita</string>
+ <string name="error_noSignaturePassPhrase">Pass Phrase non specificata</string>
+ <string name="error_noSignatureKey">firma non specificata</string>
+ <string name="error_invalidData">dati di crittazione non validi</string>
+ <string name="error_corruptData">dati corrotti</string>
+ <string name="error_noSymmetricEncryptionPacket">non è stato possibile trovare pacchetto con crittazione simmetrica</string>
+ <string name="error_wrongPassPhrase">Pass Phrase errata</string>
+ <string name="error_savingKeys">errore durante il salvataggio di alcune chiavi</string>
+ <string name="error_couldNotExtractPrivateKey">non è stato possibile estrarre la chiave privata</string>
+
+ <!-- progress_lowerCase: lowercase, phrases, usually ending in '...' -->
+ <string name="progress_done">eseguito.</string>
+ <string name="progress_initializing">inizializzazione in corso...</string>
+ <string name="progress_saving">salvataggio in corso...</string>
+ <string name="progress_importing">importazione in corso...</string>
+ <string name="progress_exporting">esportazione in corso...</string>
+ <string name="progress_generating">generazione della chiave in corso; questo processo potrebbe richiedere alcuni minuti...</string>
+ <string name="progress_buildingKey">costruzione della chiave in corso...</string>
+ <string name="progress_preparingMasterKey">impostazione della chiave master in corso...</string>
+ <string name="progress_certifyingMasterKey">certificazione della chiave master in corso...</string>
+ <string name="progress_buildingMasterKeyRing">costruzione del master key ring in corso...</string>
+ <string name="progress_addingSubKeys">aggiunta delle sotto-chiavi in corso...</string>
+ <string name="progress_savingKeyRing">salvataggio del key ring in corso...</string>
+ <string name="progress_importingSecretKeys">importazione delle chiavi private in corso...</string>
+ <string name="progress_importingPublicKeys">importazione delle chiavi pubbliche in corso...</string>
+ <string name="progress_reloadingKeys">ricaricamento chiavi in corso...</string>
+ <string name="progress_exportingKey">esportazione chiave in corso...</string>
+ <string name="progress_exportingKeys">esportazione chiavi in corso...</string>
+ <string name="progress_extractingSignatureKey">estrazione firma in corso...</string>
+ <string name="progress_extractingKey">estrazione chiave in corso...</string>
+ <string name="progress_preparingStreams">preparazione del flusso dati in corso...</string>
+ <string name="progress_encrypting">crittazione in corso...</string>
+ <string name="progress_decrypting">decrittazione in corso...</string>
+ <string name="progress_preparingSignature">impostazione della firma in corso...</string>
+ <string name="progress_generatingSignature">generazione della firma in corso...</string>
+ <string name="progress_processingSignature">elaborazione della firma in corso...</string>
+ <string name="progress_verifyingSignature">verifica della firma in corso...</string>
+ <string name="progress_signing">applicazione della firma in corso...</string>
+ <string name="progress_readingData">lettura in corso...</string>
+ <string name="progress_findingKey">ricerca chiave in corso...</string>
+ <string name="progress_decompressingData">decompressione dei dati in corso...</string>
+ <string name="progress_verifyingIntegrity">verifica integrità dati in corso...</string>
+ <string name="progress_deletingSecurely">rimozione sicura di \'%s\'...</string>
+ <string name="progress_querying">interrogazione in corso...</string>
+ <string name="progress_queryingServer">interrogazione di %s in corso...</string>
+
+ <!-- permission strings -->
+ <string name="permission_read_key_details_label">Accesso in lettura ai dettagli della chiave da APG.</string>
+ <string name="permission_read_key_details_description">Accesso in lettura ai dettagli delle chiavi pubbliche e private memorizzate in APG, compresi ID della chiave e ID dell\'utente. Non è possibile accedere in lettura alle chiavi in sè.</string>
+
+ <!-- action strings -->
+ <string name="action_encrypt">Critta</string>
+ <string name="action_decrypt">Decritta</string>
+ <string name="action_importPublic">Importa Chiavi Pubbliche</string>
+ <string name="action_importSecret">Importa Chiavi Private</string>
+ <string name="hint_publicKeys">Cerca Chiavi Pubbliche</string>
+ <string name="hint_secretKeys">Cerca Chiavi Private</string>
+ <string name="filterInfo">Filtra: \"%s\"</string>
+
+ <!-- misc -->
+ <string name="fast">veloce</string>
+ <string name="slow">lenta</string>
+ <string name="very_slow">molto lenta</string>
+
+ <!-- texts -->
+ <!-- "OI File Manager", "ASTRO", and "K-9 Mail" must not be translated in order for the links to the market to work. -->
+ <string name="text_help">er una migliore integrazione, si consiglia di installare K-9 Mail; questo applicativo supporta infatti APG e consente di crittare/decrittare direttamente le mail.
+\n\nPer poter utilizzare il bottone \"sfoglia\" per la selezione dei file su APG, si consiglia di installare OI File Manager oppure ASTRO.
+\n\nCome prima cosa, è necessario disporre di una o più chiavi. E\' possibile importare chiavi esistenti tramite l\'opzione \"Manage Public Keys\" e \"Manage Secret Keys\", oppure crearne una nuova tramite l\'opzione \"Manage Secret Keys\".
+\n\nE\' possibile aggiungere un account GMail tramite l\'opzione \"Aggiungi Acconto GMail\"; questo semplifica il processo di decrittaggio delle mail ricevute sulla propria casella di posta su GMail.
+\n\nProva e controlla le opzioni dei vari menù per esplorare e conoscere tutte le funzionalità di APG.</string>
+
+</resources> \ No newline at end of file
diff --git a/org_apg/res/values-no/strings.xml b/org_apg/res/values-no/strings.xml
new file mode 100644
index 000000000..825701f38
--- /dev/null
+++ b/org_apg/res/values-no/strings.xml
@@ -0,0 +1,313 @@
+<?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.
+
+ Author: Sander Danielsen <thelugal@gmail.com>
+ Norsk, Bokmål
+
+ Send gjerne en e-post med spørsmål eller kommentarer. Kom gjerne med forslag til bedre
+ oversettelser!
+-->
+
+<resources>
+
+ <!-- title_lowerCase: capitalized words, no punctuation -->
+ <string name="title_mailInbox">Epost innboks</string>
+ <string name="title_managePublicKeys">Håndter Offentlige Nøkler</string>
+ <string name="title_manageSecretKeys">Håndter Private Nøkler</string>
+ <string name="title_selectRecipients">Velg Mottakere</string>
+ <string name="title_selectSignature">SVelg Signatur</string>
+ <string name="title_encrypt">Krypter</string>
+ <string name="title_decrypt">Dekrypter</string>
+ <string name="title_authentication">Passord</string>
+ <string name="title_createKey">Lag Nøkkel</string>
+ <string name="title_editKey">Rediger Nøkkel</string>
+ <string name="title_preferences">Egenskaper</string>
+ <string name="title_keyServerPreference">Nøkkelserver Egenskaper</string>
+ <string name="title_changePassPhrase">Endre Passord</string>
+ <string name="title_setPassPhrase">Sett Passord</string>
+ <string name="title_sendEmail">"Send Epost..."</string>
+ <string name="title_encryptToFile">Krypter Til Fil</string>
+ <string name="title_decryptToFile">Dekrypter Til Fil</string>
+ <string name="title_addAccount">Legg Til Konto</string>
+ <string name="title_importKeys">Importer Nøkler</string>
+ <string name="title_exportKey">Eksporter Nøkkel</string>
+ <string name="title_exportKeys">Eksporter Nøkler</string>
+ <string name="title_keyNotFound">Finner Ikke Nøkkel</string>
+ <string name="title_help">Kom I Gang</string>
+ <string name="title_keyServerQuery">Søk Nøkkelserver</string>
+
+ <!-- section_lowerCase: capitalized words, no punctuation -->
+ <string name="section_userIds">Bruker IDer</string>
+ <string name="section_keys">Nøkler</string>
+ <string name="section_general">Generelt</string>
+ <string name="section_defaults">Standard Innstillinger</string>
+ <string name="section_advanced">Avansert</string>
+
+ <!-- btn_lowerCase: capitalized words, no punctuation -->
+ <string name="btn_signToClipboard">Signer Til Utklippstavle</string>
+ <string name="btn_encryptToClipboard">Krypter Til Utklippstavle</string>
+ <string name="btn_encryptAndEmail">Krypter Og Send Epost</string>
+ <string name="btn_signAndEmail">Signer Og Send Epost</string>
+ <string name="btn_encrypt">Krypter</string>
+ <string name="btn_sign">Signer</string>
+ <string name="btn_decrypt">Dekrypter</string>
+ <string name="btn_verify">Verifiser</string>
+ <string name="btn_selectEncryptKeys">Velg Mottakere</string>
+ <string name="btn_reply">Svar</string>
+ <string name="btn_encryptMessage">Krypter Melding</string>
+ <string name="btn_decryptMessage">Dekrypter Melding</string>
+ <string name="btn_encryptFile">Krypter Fil</string>
+ <string name="btn_decryptFile">Dekrypter Fil</string>
+ <string name="btn_save">Lagre</string>
+ <string name="btn_doNotSave">Avbryt</string>
+ <string name="btn_delete">Slett</string>
+ <string name="btn_noDate">Ingen</string>
+ <string name="btn_clearFilter">Tøm Filter</string>
+ <string name="btn_changePassPhrase">Endre Passord</string>
+ <string name="btn_setPassPhrase">Sett Passord</string>
+ <string name="btn_search">Søk</string>
+
+ <!-- menu_lowerCase: capitalized words, no punctuation -->
+ <string name="menu_about">Om APG</string>
+ <string name="menu_addAccount">Legg til GMail Konto</string>
+ <string name="menu_deleteAccount">Slett Konto</string>
+ <string name="menu_managePublicKeys">Håndter Offentlige Nøkler</string>
+ <string name="menu_manageSecretKeys">Håndter Private Nøkler</string>
+ <string name="menu_preferences">Instillinger</string>
+ <string name="menu_importKeys">Importer Nøkler</string>
+ <string name="menu_exportKeys">Eksporter Nøkler</string>
+ <string name="menu_exportKey">Eksporter Nøkkel</string>
+ <string name="menu_deleteKey">Slett Nøkkel</string>
+ <string name="menu_createKey">Opprett Nøkkel</string>
+ <string name="menu_editKey">Rediger Nøkkel</string>
+ <string name="menu_search">Søk</string>
+ <string name="menu_help">Hjelp</string>
+ <string name="menu_keyServer">Nøkkelserver</string>
+ <string name="menu_updateKey">Oppdater</string>
+
+ <!-- label_lowerCase: capitalized words, no punctuation -->
+ <string name="label_sign">Signer</string>
+ <string name="label_message">Melding</string>
+ <string name="label_file">Fil</string>
+ <string name="label_passPhrase">Passord</string>
+ <string name="label_passPhraseAgain">Igjen</string>
+ <string name="label_algorithm">Algoritme</string>
+ <string name="label_asciiArmour">ASCII Armour</string>
+ <string name="label_selectPublicKeys">Offentlig(e) Nøkkel(/Nøkler) </string>
+ <string name="label_deleteAfterEncryption">Slett Etter Kryptering</string>
+ <string name="label_deleteAfterDecryption">Slett Etter Dekryptering</string>
+ <string name="label_deleteAfterImport">Slett Etter Import</string>
+ <string name="label_encryptionAlgorithm">Krypteringsalgoritme</string>
+ <string name="label_hashAlgorithm">Hash Algoritme</string>
+ <string name="label_asymmetric">Offentlig Nøkkel</string>
+ <string name="label_symmetric">Passord</string>
+ <string name="label_passPhraseCacheTtl">Passord Mellomlager</string>
+ <string name="label_messageCompression">Melding komprimering</string>
+ <string name="label_fileCompression">Fil Komprimering</string>
+ <string name="label_language">Språk</string>
+ <string name="label_forceV3Signature">Tving V3 Signaturer</string>
+ <string name="label_keyServers">Nøkkelserver</string>
+ <string name="label_keyId">Nøkkel ID</string>
+ <string name="label_creation">Laget</string>
+ <string name="label_expiry">Utløper</string>
+ <string name="label_usage">Bruk</string>
+ <string name="label_keySize">Nøkkelstørrelse</string>
+ <string name="label_mainUserId">Hoved Bruker ID</string>
+ <string name="label_name">Navn</string>
+ <string name="label_comment">Kommentar</string>
+ <string name="label_email">Epost</string>
+ <string name="noKeysSelected">Velg</string>
+ <string name="oneKeySelected">1 Valgt</string>
+ <string name="nKeysSelected">Valgt</string>
+ <string name="unknownUserId">&lt;ukjent></string>
+ <string name="none">&lt;ingen></string>
+ <string name="noKey">&lt;ingen nøkkel></string>
+ <string name="noDate">-</string>
+ <string name="noExpiry">&lt;utløper ikke></string>
+ <string name="unknownStatus"></string>
+ <string name="canEncrypt">kan kryptere</string>
+ <string name="canSign">kan signere</string>
+ <string name="expired">utløpt</string>
+ <string name="notValid">ugyldig</string>
+ <string name="nKeyServers">%s nøkkelserver(e)</string>
+
+ <!-- choice_lowerCase: capitalized first word, no punctuation -->
+ <string name="choice_none">Ingen</string>
+ <string name="choice_signOnly">Kun Signer</string>
+ <string name="choice_encryptOnly">Kun Krypter</string>
+ <string name="choice_signAndEncrypt">Signer Og Krypter</string>
+ <string name="choice_15secs">15 sek</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 time</string>
+ <string name="choice_2hours">2 timer</string>
+ <string name="choice_4hours">4 timer</string>
+ <string name="choice_8hours">8 timer</string>
+ <string name="choice_untilQuit">til avsluttning</string>
+ <string name="choice_language_system">System </string>
+ <string name="dsa">DSA</string>
+ <string name="elgamal">ElGamal</string>
+ <string name="rsa">RSA</string>
+ <string name="filemanager_titleOpen">Åpne...</string>
+ <string name="filemanager_titleSave">Lagre Som...</string>
+ <string name="filemanager_titleEncrypt">Velg Fil Som Skal Krypteres...</string>
+ <string name="filemanager_titleDecrypt">Velg Fil Som Skal Dekrypteres...</string>
+ <string name="filemanager_btnOpen">Åpne</string>
+ <string name="filemanager_btnSave">Lagre</string>
+ <string name="warning">Advarsel</string>
+ <string name="error">Feil</string>
+ <string name="warningMessage">Advarsel: %s</string>
+ <string name="errorMessage">Feil: %s</string>
+
+ <!-- sentences -->
+ <string name="wrongPassPhrase">Feil passord.</string>
+ <string name="usingClipboardContent">Bruker innhold i utklippstavle.</string>
+ <string name="keySaved">Nøkkel lagret.</string>
+ <string name="setAPassPhrase">Sett en passord først.</string>
+ <string name="noFilemanagerInstalled">Det er ikke installert en kompitabel filhåndtering.</string>
+ <string name="passPhrasesDoNotMatch">Passordet matchet ikke.</string>
+ <string name="passPhraseMustNotBeEmpty">Tomme passord er ikke lov.</string>
+ <string name="passPhraseForSymmetricEncryption">Symmetrisk kryptering.</string>
+ <string name="passPhraseFor">%s</string>
+ <string name="fileDeleteConfirmation">Er du sikker på at du vil slette\n%s?</string>
+ <string name="fileDeleteSuccessful">Filen er slettet.</string>
+ <string name="noFileSelected">Velg en fil først.</string>
+ <string name="decryptionSuccessful">Dekryptering lykktes.</string>
+ <string name="encryptionSuccessful">Krytering lyktes.</string>
+ <string name="encryptionToClipboardSuccessful">Kryptering til utklippstavle lykktes.</string>
+ <string name="enterPassPhraseTwice">Tast passordet to ganger.</string>
+ <string name="selectEncryptionKey">Velg minst én krypteringsnøkkel.</string>
+ <string name="selectEncryptionOrSignatureKey">Velg minst én krypteringsnøkkel eller signaturnøkkel.</string>
+ <string name="specifyFileToEncryptTo">Vennligst angi hvilken fil det skal krypteres til.\nADVARSEL! Filen vil bli overskrevet, hvis den eksisterer.</string>
+ <string name="specifyFileToDecryptTo">Vennligst angi hvilken fil det skal dekrypteres til.\nADVARSEL! Filen vil bli overskrevet, hvis den eksisterer.</string>
+ <string name="specifyGoogleMailAccount">Angi hvilken Google Mail konto du vil legge til</string>
+ <string name="specifyFileToImportFrom">Vennligst angi hvilken fil du vil importere fra. (.asc eller .gpg)</string>
+ <string name="specifyFileToExportTo">Vennligst angi hvilken fil du vil eksportere til.\nADVARSEL! Filen vil bli overskrevet, hvis den eksisterer.</string>
+ <string name="specifyFileToExportSecretKeysTo">Vennligst angi hvilken fil du vil eksportere til.\nADVARSEL! Du er på vei til p eksportere HEMMELIGE nøkkler.\nADVARSEL! Filen vil bli overskrevet, hvis den eksisterer.</string>
+ <string name="keyDeletionConfirmation">Vil du virkelig slette nøkkelen? \'%s\'?\nDu kan ikke gjøre om dette!</string>
+ <string name="secretKeyDeletionConfirmation">Vil du virkelig slette den HEMMELIGE nøkkelen? \'%s\'?\nDu kan ikke gjøre om dette!</string>
+ <string name="keysAddedAndUpdated">Lykktes å legge til %1$s nøkkel(er) og oppdatere %2$s nøkkel(er)."</string>
+ <string name="keysAdded">Lykktes å legge til %s nøkkel(er).</string>
+ <string name="keysUpdated">Lykktes å oppdatere %s nøkkel(er).</string>
+ <string name="noKeysAddedOrUpdated">Ingen nøkler lagt til eller oppdatert.</string>
+ <string name="keyExported">Lykktes å eksportere 1 nøkkel.</string>
+ <string name="keysExported">Lykktes å eksportere %s nøkler.</string>
+ <string name="noKeysExported">Ingen nøkler eksportert.</string>
+ <string name="keyCreationElGamalInfo">Noter: kun undernøkler støtter ElGamal, og for ElGamal vil den nærmeste nøkkelstørrelsen 1536, 2048, 3072, 4096, eller 8192 bli brukt.</string>
+ <string name="keyNotFound">Kunne ikke finne nøkkel %08X.</string>
+ <string name="keysFound">Fang %s nøkkel(er).</string>
+ <string name="unknownSignatureKeyTouchToLookUp">Ukjent signatur, trykk for å sjekke nøkkel.</string>
+ <string name="keyEditingIsBeta">Nøkkelredigering er enda beta.</string>
+
+ <!--
+ error_lowerCase: phrases, no punctuation, all lowercase,
+ they will be put after "errorMessage", e.g. "Error: file not found"
+ -->
+ <string name="error_fileDeleteFailed">sletting \'%s\' mislyktes</string>
+ <string name="error_fileNotFound">finner ikke fil</string>
+ <string name="error_noSecretKeyFound">finner ingen passende hemmelig nøkkel</string>
+ <string name="error_noKnownEncryptionFound">ingen kjente krypteringsmåter funnet</string>
+ <string name="error_externalStorageNotReady">ekstern lagringsplass ikke klar</string>
+ <string name="error_accountNotFound">finner ikke kontoen - \'%s\'</string>
+ <string name="error_accountReadingNotAllowed">ikke tillatelse til lese konto</string>
+ <string name="error_addingAccountFailed">kunne ikke legge til konto - \'%s\'</string>
+ <string name="error_invalidEmail">ugyldig epost \'%s\'</string>
+ <string name="error_keySizeMinimum512bit">størrelsen på nøkkel må være minst 512bit</string>
+ <string name="error_masterKeyMustNotBeElGamal">hovednøkkelen kan ikke være en ElGamal nøkkel</string>
+ <string name="error_unknownAlgorithmChoice">ukjent algoritme valgt</string>
+ <string name="error_userIdNeedsAName">Du må spesifisere et navn</string>
+ <string name="error_userIdNeedsAnEmailAddress">du må spesifisere en epost adresse</string>
+ <string name="error_keyNeedsAUserId">må ha minst en bruker id</string>
+ <string name="error_mainUserIdMustNotBeEmpty">hovedbruker id kan ikke være tom</string>
+ <string name="error_keyNeedsMasterKey">trenger minst én hovednøkkel</string>
+ <string name="error_expiryMustComeAfterCreation">utløpsdatoen må komme etter opprettelsesdatoen</string>
+ <string name="error_noEncryptionKeysOrPassPhrase">ingen krypteringsnøkkel(er) eller passord gitt</string>
+ <string name="error_signatureFailed">signering feilet</string>
+ <string name="error_noSignaturePassPhrase">ingen passord gitt</string>
+ <string name="error_noSignatureKey">ingen signaturnøkkel gitt</string>
+ <string name="error_invalidData">ikke gyldig krypteringsdata</string>
+ <string name="error_corruptData">korrupt data</string>
+ <string name="error_noSymmetricEncryptionPacket">kunne ikke finne en pakke med symmetrisk kryptering</string>
+ <string name="error_wrongPassPhrase">Feil passord</string>
+ <string name="error_savingKeys">Feil under lagring av noen nøkkel(er)</string>
+
+ <!-- progress_lowerCase: lowercase, phrases, usually ending in '...' -->
+ <string name="progress_done">ferdig.</string>
+ <string name="progress_initializing">initialiserer...</string>
+ <string name="progress_saving">lagrer...</string>
+ <string name="progress_importing">importerer...</string>
+ <string name="progress_exporting">eksporterer...</string>
+ <string name="progress_generating">genererer nøkkel, dette kan ta en stund...</string>
+ <string name="progress_buildingKey">bygger nøkkel...</string>
+ <string name="progress_preparingMasterKey">forbereder hovednøkkel...</string>
+ <string name="progress_certifyingMasterKey">berefter hovednøkkel...</string>
+ <string name="progress_buildingMasterKeyRing">bygger hovednøkkelring...</string>
+ <string name="progress_addingSubKeys">legger til undernøkler...</string>
+ <string name="progress_savingKeyRing">lagrer nøkkelring...</string>
+ <string name="progress_importingSecretKeys">importerer hemmelige nøkler...</string>
+ <string name="progress_importingPublicKeys">importerer offentlige nøkler...</string>
+ <string name="progress_reloadingKeys">gjennlaster nøkler...</string>
+ <string name="progress_exportingKey">eksporterer nøkkel...</string>
+ <string name="progress_exportingKeys">eksporterer nøkler...</string>
+ <string name="progress_extractingSignatureKey">henter signaturnøkkel...</string>
+ <string name="progress_extractingKey">henter nøkkel...</string>
+ <string name="progress_preparingStreams">forereder strømmer...</string>
+ <string name="progress_encrypting">krypterer data...</string>
+ <string name="progress_decrypting">dekrypterer data...</string>
+ <string name="progress_preparingSignature">forbereder signatur...</string>
+ <string name="progress_generatingSignature">genererer signatur...</string>
+ <string name="progress_processingSignature">behandler signatur...</string>
+ <string name="progress_verifyingSignature">bekrefter signatur...</string>
+ <string name="progress_signing">signerer...</string>
+ <string name="progress_readingData">leser data...</string>
+ <string name="progress_findingKey">finner nøkkel...</string>
+ <string name="progress_decompressingData">dekomprimerer data...</string>
+ <string name="progress_verifyingIntegrity">bekrefter integritet...</string>
+ <string name="progress_deletingSecurely">sletter \'%s\' sikkert...</string>
+ <string name="progress_querying">søker...</string>
+ <string name="progress_queryingServer">søker %s...</string>
+
+ <!-- permission strings -->
+ <string name="permission_read_key_details_label">Les nøkkel-detaljer fra APG.</string>
+ <string name="permission_read_key_details_description">Les nøkkel-detaljer fra APG om offentlige og private nøkler lagret i APG, som f.eks. nøkkel-ID og bruker-IDer. Nøklene i seg selv kan IKKE bli lest.</string>
+
+ <!-- action strings -->
+ <string name="action_encrypt">Krypter</string>
+ <string name="action_decrypt">Dekrypter</string>
+ <string name="action_importPublic">Importer Offentlige Nøkler</string>
+ <string name="action_importSecret">Importer Private Nøkler</string>
+ <string name="hint_publicKeys">Søk I Offentlige Nøkler</string>
+ <string name="hint_secretKeys">Søk I Private Nøkler</string>
+ <string name="filterInfo">Filter: \"%s\"</string>
+
+ <!-- misc -->
+ <string name="fast">rask</string>
+ <string name="slow">treg</string>
+ <string name="very_slow">veldig treg</string>
+
+ <!-- texts -->
+ <!-- "OI File Manager", "ASTRO", and "K-9 Mail" must not be translated in order for the links to the market to work. -->
+ <string name="text_help">Installer K-9 Mail for den beste integrasjonen, det støtter APG for PGP/INLINE og lar deg kryptere/dekryptere epost direkte.
+\n\nDet er anbefalt at du installerer OI File Manager eller ASTRO for å kunne velge filer igjennom APG, med manuelt søk. <!-- to be able to use the browse button for file selection in APG. -->
+\n\nFørst trenger du noen nøkler. Du kan enten importere dem via menyene i \"Håndter Offentlige Nøkler\" og \"Håndter Private Nøkler\" eller lage dem selv i \"Håndter Private Nøkler\".
+\n\nDu kan også legge til en GMail-konto i hovedmenyen via \"Legg Til Konto\", noe som forenkler dekryptering av epost mottatt der.
+\n\nSjekk menyene under de forskjellige aktivitetene for å finne flere funksjoner.</string>
+
+</resources> \ No newline at end of file
diff --git a/org_apg/res/values-pt/strings.xml b/org_apg/res/values-pt/strings.xml
new file mode 100644
index 000000000..76e0cb244
--- /dev/null
+++ b/org_apg/res/values-pt/strings.xml
@@ -0,0 +1,312 @@
+<?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.
+-->
+
+<resources>
+
+ <!-- title_lowerCase: capitalized words, no punctuation -->
+ <string name="title_mailInbox">Caixa de Entrada</string>
+ <string name="title_managePublicKeys">Gerir Chaves Públicas</string>
+ <string name="title_manageSecretKeys">Gerir Chaves Privadas</string>
+ <string name="title_selectRecipients">Escolher Destinatários</string>
+ <string name="title_selectSignature">Escolher Assinatura</string>
+ <string name="title_encrypt">Cifrar</string>
+ <string name="title_decrypt">Decifrar</string>
+ <string name="title_authentication">Senha</string>
+ <string name="title_createKey">Criar Chave</string>
+ <string name="title_editKey">Editar Chave</string>
+ <string name="title_preferences">Preferências</string>
+ <string name="title_keyServerPreference">Preferências de Servidor de Chaves</string>
+ <string name="title_changePassPhrase">Alterar Senha</string>
+ <string name="title_setPassPhrase">Definir Senha</string>
+ <string name="title_sendEmail">"Enviar Email..."</string>
+ <string name="title_encryptToFile">Cifrar Para Arquivo</string>
+ <string name="title_decryptToFile">Descifrar Para Arquivo</string>
+ <string name="title_addAccount">Adicionar Conta</string>
+ <string name="title_importKeys">Importar Chaves</string>
+ <string name="title_exportKey">Exportar Chave</string>
+ <string name="title_exportKeys">Exportar Chaves</string>
+ <string name="title_keyNotFound">Chave Não Encontrada</string>
+ <string name="title_help">Conhecendo</string>
+ <string name="title_keyServerQuery">Busca no Servidor de Chaves</string>
+ <string name="title_unknownSignatureKey">Assinatura Desconhecida</string>
+
+ <!-- section_lowerCase: capitalized words, no punctuation -->
+ <string name="section_userIds">IDs de Usuários</string>
+ <string name="section_keys">Chaves</string>
+ <string name="section_general">Geral</string>
+ <string name="section_defaults">Padrões</string>
+ <string name="section_advanced">Avançado</string>
+
+ <!-- btn_lowerCase: capitalized words, no punctuation -->
+ <string name="btn_signToClipboard">Assinar Para Clipboard</string>
+ <string name="btn_encryptToClipboard">Cifrar Para Clipboard</string>
+ <string name="btn_encryptAndEmail">Cifrar Para Email</string>
+ <string name="btn_signAndEmail">Assinar Para Email</string>
+ <string name="btn_encrypt">Cifrar</string>
+ <string name="btn_sign">Assinar</string>
+ <string name="btn_decrypt">Decifrar</string>
+ <string name="btn_verify">Verificar</string>
+ <string name="btn_selectEncryptKeys">Escolher Destinatários</string>
+ <string name="btn_reply">Responder</string>
+ <string name="btn_encryptMessage">Cifrar Mensagem</string>
+ <string name="btn_decryptMessage">Decifrar Mesagem</string>
+ <string name="btn_encryptFile">Cifrar Arquivo</string>
+ <string name="btn_decryptFile">Decifrar Arquivo</string>
+ <string name="btn_save">Salvar</string>
+ <string name="btn_doNotSave">Cancelar</string>
+ <string name="btn_delete">Apagar</string>
+ <string name="btn_noDate">Nenhuma</string>
+ <string name="btn_clearFilter">Limpar Filtro</string>
+ <string name="btn_changePassPhrase">Alterar Senha</string>
+ <string name="btn_setPassPhrase">Definir Senha</string>
+ <string name="btn_search">Buscar</string>
+
+ <!-- menu_lowerCase: capitalized words, no punctuation -->
+ <string name="menu_about">Sobre</string>
+ <string name="menu_addAccount">Adicionar Conta GMail</string>
+ <string name="menu_deleteAccount">Apagar Conta</string>
+ <string name="menu_managePublicKeys">Gerir Chaves Públicas</string>
+ <string name="menu_manageSecretKeys">Gerir Chaves Privadas</string>
+ <string name="menu_preferences">Configurações</string>
+ <string name="menu_importKeys">Importar Chaves</string>
+ <string name="menu_exportKeys">Exportar Chaves</string>
+ <string name="menu_exportKey">Exportar Chave</string>
+ <string name="menu_deleteKey">Apagar Chave</string>
+ <string name="menu_createKey">Criar Chave</string>
+ <string name="menu_editKey">Editar Chave</string>
+ <string name="menu_search">Buscar</string>
+ <string name="menu_help">Ajuda</string>
+ <string name="menu_keyServer">Servidor de Chaves</string>
+ <string name="menu_updateKey">Atualizar</string>
+
+ <!-- label_lowerCase: capitalized words, no punctuation -->
+ <string name="label_sign">Assinar</string>
+ <string name="label_message">Mensagem</string>
+ <string name="label_file">Arquivo</string>
+ <string name="label_passPhrase">Senha</string>
+ <string name="label_passPhraseAgain">Novamente</string>
+ <string name="label_algorithm">Algoritmo</string>
+ <string name="label_asciiArmour">Armadura ASCII</string>
+ <string name="label_selectPublicKeys">Chave(s) Pública(s)</string>
+ <string name="label_deleteAfterEncryption">Apagar Após Cifrar</string>
+ <string name="label_deleteAfterDecryption">Apagar Após Decifrar</string>
+ <string name="label_deleteAfterImport">Apagar Após Importar</string>
+ <string name="label_encryptionAlgorithm">Algoritmo de Cifragem</string>
+ <string name="label_hashAlgorithm">Algoritmo de Hash</string>
+ <string name="label_asymmetric">Chave Pública</string>
+ <string name="label_symmetric">Senha</string>
+ <string name="label_passPhraseCacheTtl">Cache de Senhas</string>
+ <string name="label_messageCompression">Compressão de Mensagem</string>
+ <string name="label_fileCompression">Compressão de Arquivo</string>
+ <string name="label_language">Língua</string>
+ <string name="label_forceV3Signature">Forçar Assinaturas V3</string>
+ <string name="label_keyServers">Servidores de Chave</string>
+ <string name="label_keyId">ID da Chave</string>
+ <string name="label_creation">Criação</string>
+ <string name="label_expiry">Expiração</string>
+ <string name="label_usage">Uso</string>
+ <string name="label_keySize">Tamanho da Chave</string>
+ <string name="label_mainUserId">ID do Usuário Principal</string>
+ <string name="label_name">Nome</string>
+ <string name="label_comment">Comentário</string>
+ <string name="label_email">Email</string>
+ <string name="noKeysSelected">Selecionar</string>
+ <string name="oneKeySelected">1 Selecionada</string>
+ <string name="nKeysSelected">Selecionada</string>
+ <string name="unknownUserId">&lt;desconhecido></string>
+ <string name="none">&lt;ninguém></string>
+ <string name="noKey">&lt;sem chave></string>
+ <string name="noDate">-</string>
+ <string name="noExpiry">&lt;não expira></string>
+ <string name="unknownStatus"></string>
+ <string name="canEncrypt">pode cifrar</string>
+ <string name="canSign">pode assinar</string>
+ <string name="expired">expirada</string>
+ <string name="notValid">não válida</string>
+ <string name="nKeyServers">%s servidor(es) de chave(s)</string>
+ <string name="fingerprint">fingerprint</string>
+
+ <!-- choice_lowerCase: capitalized first word, no punctuation -->
+ <string name="choice_none">Nenhum</string>
+ <string name="choice_signOnly">Assinar apenas</string>
+ <string name="choice_encryptOnly">Cifrar apenas</string>
+ <string name="choice_signAndEncrypt">Assinar e Cifrar</string>
+ <string name="choice_15secs">15 segs</string>
+ <string name="choice_1min">1 min</string>
+ <string name="choice_3mins">3 mins</string>
+ <string name="choice_5mins">5 mins</string>
+ <string name="choice_10mins">10 mins</string>
+ <string name="choice_20mins">20 mins</string>
+ <string name="choice_40mins">40 mins</string>
+ <string name="choice_1hour">1 hora</string>
+ <string name="choice_2hours">2 horas</string>
+ <string name="choice_4hours">4 horas</string>
+ <string name="choice_8hours">8 horas</string>
+ <string name="choice_untilQuit">até sair</string>
+ <string name="choice_language_system">Padrões do sistema</string>
+ <string name="dsa">DSA</string>
+ <string name="elgamal">ElGamal</string>
+ <string name="rsa">RSA</string>
+ <string name="filemanager_titleOpen">Abrir...</string>
+ <string name="filemanager_titleSave">Salvar Como...</string>
+ <string name="filemanager_titleEncrypt">Selecionar Arquivo para Cifrar...</string>
+ <string name="filemanager_titleDecrypt">Selecionar Arquivo para Decifrar...</string>
+ <string name="filemanager_btnOpen">Abrir</string>
+ <string name="filemanager_btnSave">Salvar</string>
+ <string name="warning">Advertência</string>
+ <string name="error">Erro</string>
+ <string name="warningMessage">Advertência: %s</string>
+ <string name="errorMessage">Erro: %s</string>
+
+ <!-- sentences -->
+ <string name="wrongPassPhrase">Chave errada.</string>
+ <string name="usingClipboardContent">Usando conteúdo do clipboard.</string>
+ <string name="keySaved">Chave salva.</string>
+ <string name="setAPassPhrase">Defina uma senha antes.</string>
+ <string name="noFilemanagerInstalled">Não há gerenciador de arquivos compatível instalado.</string>
+ <string name="passPhrasesDoNotMatch">A senha não confere.</string>
+ <string name="passPhraseMustNotBeEmpty">Não são permitidas senhas vazias.</string>
+ <string name="passPhraseForSymmetricEncryption">Cifragem simétrica.</string>
+ <string name="passPhraseFor">%s</string>
+ <string name="fileDeleteConfirmation">Tem certeza que deseja apagar\n%s?</string>
+ <string name="fileDeleteSuccessful">Apagado com sucesso.</string>
+ <string name="noFileSelected">Selecione um arquivo primeiro.</string>
+ <string name="decryptionSuccessful">Decifrado com sucesso.</string>
+ <string name="encryptionSuccessful">Cifrado com sucesso.</string>
+ <string name="encryptionToClipboardSuccessful">Cifrado para clipboard com sucesso.</string>
+ <string name="enterPassPhraseTwice">Insira a senha duas vezes.</string>
+ <string name="selectEncryptionKey">Selecione pelo menos uma chave de cifragem.</string>
+ <string name="selectEncryptionOrSignatureKey">Selecione pelo menos uma chave de cifragem ou uma chave de assinatura.</string>
+ <string name="specifyFileToEncryptTo">Especifique o arquivo a cifrar sobre.\nATENÇÃO! O arquivo, se existir, será sobrescrito!</string>
+ <string name="specifyFileToDecryptTo">Especifique o arquivo a decifrar sobre.\nATENÇÃO! O arquivo, se existir, será sobrescrito!</string>
+ <string name="specifyGoogleMailAccount">Especifique a conta de Email do Google que deseja adicionar.</string>
+ <string name="specifyFileToImportFrom">Especifique o arquivo para importação de chaves. (.asc or .gpg)</string>
+ <string name="specifyFileToExportTo">Especifique o arquivo de exportação.\nATENÇÃO! O arquivo, se existir, será sobrescrito!</string>
+ <string name="specifyFileToExportSecretKeysTo">Especifique o arquivo de exportação.\nATENÇÃO! Você está exportando suas chaves PRIVADAS (SECRETAS).\nATENÇÃO! O arquivo, se existir, será sobrescrito.</string>
+ <string name="keyDeletionConfirmation">Você realmente deseja apagar a chave \'%s\'?\nEssa ação não pode ser desfeita!</string>
+ <string name="secretKeyDeletionConfirmation">Você realmente deseja apagar a chave PRIVADA \'%s\'?\nEssa ação não pode ser desfeita!</string>
+ <string name="keysAddedAndUpdated">Adicionada(s) com sucesso %1$s chave(s) e atualizada(s) %2$s chave(s).</string>
+ <string name="keysAdded">Adicionada(s) %s chave(s) com sucesso.</string>
+ <string name="keysUpdated">Atualizada(s) %s chaves(s) com sucesso.</string>
+ <string name="noKeysAddedOrUpdated">Nenhuma chave adicionada ou atualizada.</string>
+ <string name="keyExported">1 chave exportada com sucesso.</string>
+ <string name="keysExported">%s chaves exportadas com sucesso.</string>
+ <string name="noKeysExported">Nenhuma chave exportada.</string>
+ <string name="keyCreationElGamalInfo">Nota: apenas as sub-chaves suportam ElGamal, e para ElGamal se usuára o tamanho mais próximo a 1536, 2048, 3072, 4096, ou 8192.</string>
+ <string name="keyNotFound">Não se pode encontrar a chave %08X.</string>
+ <string name="keysFound">%s chave(s) encontrada(s).</string>
+ <string name="unknownSignatureKeyTouchToLookUp">Assinatura desconhecida, toque para buscar a chave.</string>
+ <string name="keyEditingIsBeta">A edição de chaves ainda está na versão beta.</string>
+ <string name="badKeysEncountered">%s chave(s) secreta(s) ignoradas. Provavelmente foram exportadas com a opção\n --export-secret-subkeys\nCertifique-se de exporta com a opção\n --export-secret-keys</string>
+ <string name="lookupUnknownKey">Chave %s desconhecida, você gostaria de procurá-la em um servidor de chaves?</string>
+
+ <!--
+ error_lowerCase: phrases, no punctuation, all lowercase,
+ they will be put after "errorMessage", e.g. "Error: file not found"
+ -->
+ <string name="error_fileDeleteFailed">erro ao apagar \'%s\'</string>
+ <string name="error_fileNotFound">arquivo não encontrado</string>
+ <string name="error_noSecretKeyFound">não foi encontrado uma chave secreta adequada</string>
+ <string name="error_noKnownEncryptionFound">não foi encontrado tipo de cifragem conhecido</string>
+ <string name="error_externalStorageNotReady">armazenamento externo não disponível</string>
+ <string name="error_accountNotFound">conta \'%s\' não encontrada</string>
+ <string name="error_accountReadingNotAllowed">sem permissões para ler a conta</string>
+ <string name="error_addingAccountFailed">falha ao adicionar a conta \'%s\'</string>
+ <string name="error_invalidEmail">email inválido \'%s\'</string>
+ <string name="error_keySizeMinimum512bit">o tamanho da chave deve ser pelo menos 512bit</string>
+ <string name="error_masterKeyMustNotBeElGamal">a chave primária não pode ser do tipo ElGamal</string>
+ <string name="error_unknownAlgorithmChoice">algoritmo selecionado desconhecido</string>
+ <string name="error_userIdNeedsAName">é necessário informar um nome</string>
+ <string name="error_userIdNeedsAnEmailAddress">é necessário informar um endereço de email</string>
+ <string name="error_keyNeedsAUserId">é necessário pelo menos um id de usuário</string>
+ <string name="error_mainUserIdMustNotBeEmpty">id de usuário principal não pode ser vazio</string>
+ <string name="error_keyNeedsMasterKey">é necessário pelo menos uma chave primária</string>
+ <string name="error_expiryMustComeAfterCreation">data de expiração deve ser após a data de criação</string>
+ <string name="error_noEncryptionKeysOrPassPhrase">não foram inseridos chave(s) de cifragem ou senha</string>
+ <string name="error_signatureFailed">falha na assinatura</string>
+ <string name="error_noSignaturePassPhrase">senha não inserida</string>
+ <string name="error_noSignatureKey">chave para assinatura não inserida</string>
+ <string name="error_invalidData">dados a serem cifrados inválidos</string>
+ <string name="error_corruptData">dados corrompidos</string>
+ <string name="error_noSymmetricEncryptionPacket">não foi encontrado um pacote com criptografia simétrica</string>
+ <string name="error_wrongPassPhrase">senha inválida</string>
+ <string name="error_savingKeys">erro ao salvar chave(s)</string>
+ <string name="error_couldNotExtractPrivateKey">não foi possível extrair chave privada</string>
+
+ <!-- progress_lowerCase: lowercase, phrases, usually ending in '...' -->
+ <string name="progress_done">pronto.</string>
+ <string name="progress_initializing">inicializando...</string>
+ <string name="progress_saving">salvando...</string>
+ <string name="progress_importing">importando...</string>
+ <string name="progress_exporting">exportando...</string>
+ <string name="progress_generating">criando chave, isso pode demorar um pouco...</string>
+ <string name="progress_buildingKey">criando chave...</string>
+ <string name="progress_preparingMasterKey">preparando chave primária...</string>
+ <string name="progress_certifyingMasterKey">certificando chave primária...</string>
+ <string name="progress_buildingMasterKeyRing">criando anel de chave primária...</string>
+ <string name="progress_addingSubKeys">adicionando sub-chaves...</string>
+ <string name="progress_savingKeyRing">salvando anel de chaves...</string>
+ <string name="progress_importingSecretKeys">importando chaves secretas...</string>
+ <string name="progress_importingPublicKeys">importando chaves públicas...</string>
+ <string name="progress_reloadingKeys">recarregando chaves...</string>
+ <string name="progress_exportingKey">exportando chave...</string>
+ <string name="progress_exportingKeys">exportando chaves...</string>
+ <string name="progress_extractingSignatureKey">extraindo chave de assinatura...</string>
+ <string name="progress_extractingKey">extraindo chave...</string>
+ <string name="progress_preparingStreams">preparando fluxos...</string>
+ <string name="progress_encrypting">cifrando dados...</string>
+ <string name="progress_decrypting">decifrando dados...</string>
+ <string name="progress_preparingSignature">preparando assinatura...</string>
+ <string name="progress_generatingSignature">gerando assinatura...</string>
+ <string name="progress_processingSignature">processando assinatura...</string>
+ <string name="progress_verifyingSignature">verificando assinatura...</string>
+ <string name="progress_signing">assinando...</string>
+ <string name="progress_readingData">carregando dados...</string>
+ <string name="progress_findingKey">buscando chave...</string>
+ <string name="progress_decompressingData">descompactando dados...</string>
+ <string name="progress_verifyingIntegrity">verificando integridade...</string>
+ <string name="progress_deletingSecurely">apagando \'%s\' de forma segura...</string>
+ <string name="progress_querying">buscando...</string>
+ <string name="progress_queryingServer">buscando %s...</string>
+
+ <!-- permission strings -->
+ <string name="permission_read_key_details_label">Ler detalhes de chaves do APG.</string>
+ <string name="permission_read_key_details_description">Ler detalhes de chaves públicas e privadas salvas no APG, tais como ID da chave e ID do usuário. As chaves NÃO podem ser lidas.</string>
+
+ <!-- action strings -->
+ <string name="action_encrypt">Cifrar</string>
+ <string name="action_decrypt">Decifrar</string>
+ <string name="action_importPublic">Importar Chaves Públicas</string>
+ <string name="action_importSecret">Importar Chaves Privadas</string>
+ <string name="hint_publicKeys">Buscar Chaves Públicas</string>
+ <string name="hint_secretKeys">Buscar Chaves Privadas</string>
+ <string name="filterInfo">Filter: \"%s\"</string>
+
+ <!-- misc -->
+ <string name="fast">rápido</string>
+ <string name="slow">lento</string>
+ <string name="very_slow">muito lento</string>
+
+ <!-- texts -->
+ <!-- "OI File Manager", "ASTRO", and "K-9 Mail" must not be translated in order for the links to the market to work. -->
+ <string name="text_help"> Instale o leitor de emails K-9 para uma melhor integração. Ele suporta o APG para PGP/INLINE e permite cifragem e decifragem de emails diretamente.
+\n\nÉ recomendado o uso dos Gerenciadores de Arquivos OI ou ASTRO para ser possível a utilização do botão de buscar arquivos no APG.
+\n\nSão necessárias chaves para o APG. Importe-as pela opção do menu \"Gerir Chaves Públicas\" ou \"Gerir Chaves Privadas\" ou crie-as no menu \"Gerir Chaves Privadas\".
+\n\nVocê também pode adicionar uma conta GMail pela opção \"Adicionar Conta\", isso simplifica a decifragem de emails.
+\n\nVerifique as opções de menus para conhecer as funcionalides disponíveis.</string>
+
+</resources> \ No newline at end of file
diff --git a/org_apg/res/values-sl/strings.xml b/org_apg/res/values-sl/strings.xml
new file mode 100644
index 000000000..576d6e56b
--- /dev/null
+++ b/org_apg/res/values-sl/strings.xml
@@ -0,0 +1,304 @@
+<?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.
+
+ Author: 359 <359@siol.com>
+-->
+
+<resources>
+
+ <!-- title_lowerCase: capitalized words, no punctuation -->
+ <string name="title_mailInbox">Poštni nabiralnik</string>
+ <string name="title_managePublicKeys">Upravljanje javnih ključev</string>
+ <string name="title_manageSecretKeys">Upravljanje zasebnih ključev</string>
+ <string name="title_selectRecipients">Izberi prejemnike</string>
+ <string name="title_selectSignature">Izberi podpis</string>
+ <string name="title_encrypt">Šifriraj</string>
+ <string name="title_decrypt">Dešifriraj</string>
+ <string name="title_authentication">Geslo</string>
+ <string name="title_createKey">Ustvari ključ</string>
+ <string name="title_editKey">Uredi ključ</string>
+ <string name="title_preferences">Nastavitve</string>
+ <string name="title_keyServerPreference">Nastavitve strežnikov s ključi</string>
+ <string name="title_changePassPhrase">Spremeni geslo</string>
+ <string name="title_setPassPhrase">Določi geslo</string>
+ <string name="title_sendEmail">"Pošlji e-pošto..."</string>
+ <string name="title_encryptToFile">Šifriraj v datoteko</string>
+ <string name="title_decryptToFile">Dešifriraj v datoteko</string>
+ <string name="title_addAccount">Dodaj račun</string>
+ <string name="title_importKeys">Uvozi ključe</string>
+ <string name="title_exportKey">Izvozi ključ</string>
+ <string name="title_exportKeys">Izvozi ključe</string>
+ <string name="title_keyNotFound">Ključ ni bil najden</string>
+ <string name="title_help">Kako začeti</string>
+ <string name="title_keyServerQuery">Iskanje po strežnikih s ključi</string>
+
+ <!-- section_lowerCase: capitalized words, no punctuation -->
+ <string name="section_userIds">Uporabniške identitete</string>
+ <string name="section_keys">Ključi</string>
+ <string name="section_general">Splošno</string>
+ <string name="section_defaults">Privzete nastavitve</string>
+ <string name="section_advanced">Napredno</string>
+
+ <!-- btn_lowerCase: capitalized words, no punctuation -->
+ <string name="btn_signToClipboard">Podpiši in kopiraj v odložišče</string>
+ <string name="btn_encryptToClipboard">Šifriraj in kopiraj v odložišče</string>
+ <string name="btn_encryptAndEmail">Šifriraj in pošlji</string>
+ <string name="btn_signAndEmail">Podpiši in pošlji</string>
+ <string name="btn_encrypt">Šifriraj</string>
+ <string name="btn_sign">Podpiši</string>
+ <string name="btn_decrypt">Dešifriraj</string>
+ <string name="btn_verify">Overi</string>
+ <string name="btn_selectEncryptKeys">Izberi prejemnike</string>
+ <string name="btn_reply">Odgovori</string>
+ <string name="btn_encryptMessage">Šifriraj sporočilo</string>
+ <string name="btn_decryptMessage">Dešifriraj sporočilo</string>
+ <string name="btn_encryptFile">Šifriraj datoteko</string>
+ <string name="btn_decryptFile">Dešifriraj datoteko</string>
+ <string name="btn_save">Shrani</string>
+ <string name="btn_doNotSave">Prekliči</string>
+ <string name="btn_delete">Izbriši</string>
+ <string name="btn_noDate">Brez</string>
+ <string name="btn_clearFilter">Umakni filtriranje</string>
+ <string name="btn_changePassPhrase">Spremeni geslo</string>
+ <string name="btn_setPassPhrase">Določi geslo</string>
+ <string name="btn_search">Išči</string>
+
+ <!-- menu_lowerCase: capitalized words, no punctuation -->
+ <string name="menu_about">O programu</string>
+ <string name="menu_addAccount">Dodaj GMail račun</string>
+ <string name="menu_deleteAccount">Izbriši račun</string>
+ <string name="menu_managePublicKeys">Upravljanje javnih ključev</string>
+ <string name="menu_manageSecretKeys">Upravljanje zasebnih ključev</string>
+ <string name="menu_preferences">Nastavitve</string>
+ <string name="menu_importKeys">Uvozi ključe</string>
+ <string name="menu_exportKeys">Izvozi ključe</string>
+ <string name="menu_exportKey">Izvozi ključ</string>
+ <string name="menu_deleteKey">Izbriši ključ</string>
+ <string name="menu_createKey">Ustvari ključ</string>
+ <string name="menu_editKey">Uredi ključ</string>
+ <string name="menu_search">Išči</string>
+ <string name="menu_help">Pomoč</string>
+ <string name="menu_keyServer">Strežnik s ključi</string>
+ <string name="menu_updateKey">Posodobi</string>
+
+ <!-- label_lowerCase: capitalized words, no punctuation -->
+ <string name="label_sign">Podpiši</string>
+ <string name="label_message">Sporočilo</string>
+ <string name="label_file">Datoteka</string>
+ <string name="label_passPhrase">Geslo</string>
+ <string name="label_passPhraseAgain">Ponovi</string>
+ <string name="label_algorithm">Algoritem</string>
+ <string name="label_asciiArmour">ASCII Armour</string>
+ <string name="label_selectPublicKeys">Javni ključ(i)</string>
+ <string name="label_deleteAfterEncryption">Po šifriranju izbriši</string>
+ <string name="label_deleteAfterDecryption">Po dešifriranju izbriši</string>
+ <string name="label_deleteAfterImport">Po uvozu izbriši</string>
+ <string name="label_encryptionAlgorithm">Šifrirni algoritem</string>
+ <string name="label_hashAlgorithm">Hash algoritem</string>
+ <string name="label_asymmetric">Javni ključ</string>
+ <string name="label_symmetric">Geslo</string>
+ <string name="label_passPhraseCacheTtl">Predpomnilnik gesel</string>
+ <string name="label_messageCompression">Zgoščevanje sporočil</string>
+ <string name="label_fileCompression">Zgoščevanje datotek</string>
+ <string name="label_language">Jezik</string>
+ <string name="label_forceV3Signature">Vsili podpis V3 </string>
+ <string name="label_keyServers">Strežniki s ključi</string>
+ <string name="label_keyId">Identiteta ključa</string>
+ <string name="label_creation">Izdelava</string>
+ <string name="label_expiry">Pretek</string>
+ <string name="label_usage">Uporaba</string>
+ <string name="label_keySize">Velikost ključa</string>
+ <string name="label_mainUserId">Glavna uporabniška identiteta</string>
+ <string name="label_name">Ime</string>
+ <string name="label_comment">Komentar</string>
+ <string name="label_email">E-pošta</string>
+ <string name="noKeysSelected">Izberi</string>
+ <string name="oneKeySelected">1 izbran</string>
+ <string name="nKeysSelected">Izbrani</string>
+ <string name="unknownUserId">&lt;nepoznan></string>
+ <string name="none">&lt;brez></string>
+ <string name="noKey">&lt;brez ključa></string>
+ <string name="noDate">-</string>
+ <string name="noExpiry">&lt;nikoli></string>
+ <string name="unknownStatus"></string>
+ <string name="canEncrypt">lahko šifrira</string>
+ <string name="canSign">lahko podpiše</string>
+ <string name="expired">potečeno</string>
+ <string name="notValid">neveljavno</string>
+ <string name="nKeyServers">%s strežnik(i) s ključi</string>
+
+ <!-- choice_lowerCase: capitalized first word, no punctuation -->
+ <string name="choice_none">Brez</string>
+ <string name="choice_signOnly">Samo podpis</string>
+ <string name="choice_encryptOnly">Samo šifriranje</string>
+ <string name="choice_signAndEncrypt">Podpis in šifriranje</string>
+ <string name="choice_15secs">15 sek</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_untilQuit">do izhoda</string>
+ <string name="choice_language_system">Sistemsko nastavljeno</string>
+ <string name="dsa">DSA</string>
+ <string name="elgamal">ElGamal</string>
+ <string name="rsa">RSA</string>
+ <string name="filemanager_titleOpen">Odpri...</string>
+ <string name="filemanager_titleSave">Shrani kot...</string>
+ <string name="filemanager_titleEncrypt">Izberi datoteko za šifriranje...</string>
+ <string name="filemanager_titleDecrypt">Izberi datoteko za dešifriranje...</string>
+ <string name="filemanager_btnOpen">Odpri</string>
+ <string name="filemanager_btnSave">Shrani</string>
+ <string name="warning">Opozorilo</string>
+ <string name="error">Napaka</string>
+ <string name="warningMessage">Opozorilo: %s</string>
+ <string name="errorMessage">Napaka: %s</string>
+
+ <!-- sentences -->
+ <string name="wrongPassPhrase">Napačno geslo.</string>
+ <string name="usingClipboardContent">Uporabljam vsebino odložišča.</string>
+ <string name="keySaved">Ključ shranjen.</string>
+ <string name="setAPassPhrase">Najprej določite geslo.</string>
+ <string name="noFilemanagerInstalled">Nameščen ni noben združljiv upravitelj datotek.</string>
+ <string name="passPhrasesDoNotMatch">Gesli se ne ujemata.</string>
+ <string name="passPhraseMustNotBeEmpty">Prazna gesla niso dovoljena.</string>
+ <string name="passPhraseForSymmetricEncryption">Simetrična enkripcija.</string>
+ <string name="passPhraseFor">%s</string>
+ <string name="fileDeleteConfirmation">Ali ste prepričani, da želite izbrisati\n%s?</string>
+ <string name="fileDeleteSuccessful">Uspešno izbrisano.</string>
+ <string name="noFileSelected">Najprej izberite datoteko.</string>
+ <string name="decryptionSuccessful">Uspešno dešifrirano.</string>
+ <string name="encryptionSuccessful">Uspešno šifrirano.</string>
+ <string name="encryptionToClipboardSuccessful">Uspešno šifrirano v odložišče.</string>
+ <string name="enterPassPhraseTwice">Vstavite geslo dvakrat.</string>
+ <string name="selectEncryptionKey">Izberite vsaj en šifrirni ključ.</string>
+ <string name="selectEncryptionOrSignatureKey">Izberite vsaj en šifrirni ključ ali ključ za podpis.</string>
+ <string name="specifyFileToEncryptTo">Določite datoteko v katero želite šifrirati.\nPOZOR! Če ta datoteka že obstaja, bo prepisana.</string>
+ <string name="specifyFileToDecryptTo">Določite datoteko v katero želite dešifrirati.\nPOZOR! Če ta datoteka že obstaja, bo prepisana.</string>
+ <string name="specifyGoogleMailAccount">Določite Google Mail račun, ki ga želite dodati.</string>
+ <string name="specifyFileToImportFrom">Določite iz katere datoteke želite uvoziti ključe. (.asc ali .gpg)</string>
+ <string name="specifyFileToExportTo">Določite v katero datoteko želite izvoziti.\nPOZOR! Če ta datoteka že obstaja, bo prepisana.</string>
+ <string name="specifyFileToExportSecretKeysTo">Določite v katero datoteko želite izvoziti.\nPOZOR! Izvozili boste ZASEBNI ključ.\nPOZOR! Če ta datoteka že obstaja, bo prepisana.</string>
+ <string name="keyDeletionConfirmation">Ali zares želite izbrisati ključ \'%s\'?\nTega ne boste mogli popraviti!</string>
+ <string name="secretKeyDeletionConfirmation">Ali zares želite izbrisati ZASEBNI ključ \'%s\'?\nTega ne boste mogli popraviti!</string>
+ <string name="keysAddedAndUpdated">Uspešno dodani ključi: %1$s. Uspešno posodobljeni ključi: %2$s."</string>
+ <string name="keysAdded">Uspešno dodani ključi: %s.</string>
+ <string name="keysUpdated">Uspešno posodobljeni ključi: %s.</string>
+ <string name="noKeysAddedOrUpdated">Noben ključ ni bil dodan ali posodobljen.</string>
+ <string name="keyExported">Uspešno izvožen 1 ključ.</string>
+ <string name="keysExported">Uspešno izvoženi ključi: %s</string>
+ <string name="noKeysExported">Noben ključ ni bil izvožen.</string>
+ <string name="keyCreationElGamalInfo">Opomba: le podključi podpirajo ElGamal. Za ElGamal bo uporabljena velikost najbližja 1536, 2048, 3072, 4096, ali 8192.</string>
+ <string name="keyNotFound">Ne najdem ključa %08X.</string>
+ <string name="keysFound">Najdeni ključi: %s</string>
+ <string name="unknownSignatureKeyTouchToLookUp">Neznan podpis, za ogled pritisni.</string>
+ <string name="keyEditingIsBeta">Urejanje ključev je še vedno v precej testni fazi.</string>
+
+ <!--
+ error_lowerCase: phrases, no punctuation, all lowercase,
+ they will be put after "errorMessage", e.g. "Error: file not found"
+ -->
+ <string name="error_fileDeleteFailed">izbris \'%s\' ni uspel</string>
+ <string name="error_fileNotFound">ne najdem datoteke</string>
+ <string name="error_noSecretKeyFound">najden ni bil noben ustrezen zasebni kluč</string>
+ <string name="error_noKnownEncryptionFound">najdena ni bila nobena poznana vrsta enkripcije</string>
+ <string name="error_externalStorageNotReady">zunanji pomnilnik ni pripravljen</string>
+ <string name="error_accountNotFound">račun \'%s\' ni najden</string>
+ <string name="error_accountReadingNotAllowed">branje računa ni dovoljeno</string>
+ <string name="error_addingAccountFailed">dodajanje računa \'%s\' ni uspelo</string>
+ <string name="error_invalidEmail">neveljaven e-naslov \'%s\'</string>
+ <string name="error_keySizeMinimum512bit">velikost ključa mora biti vsaj 512bit</string>
+ <string name="error_masterKeyMustNotBeElGamal">statični ključ ne more biti ključ ElGamal</string>
+ <string name="error_unknownAlgorithmChoice">neznana izbira algoritma</string>
+ <string name="error_userIdNeedsAName">določiti morate ime</string>
+ <string name="error_userIdNeedsAnEmailAddress">določiti morate naslov e-pošte</string>
+ <string name="error_keyNeedsAUserId">potrebujem vsaj eno uporabniško identiteto</string>
+ <string name="error_mainUserIdMustNotBeEmpty">glavna uporabniška identiteta ne more biti prazna</string>
+ <string name="error_keyNeedsMasterKey">potrebujem vsaj statični ključ</string>
+ <string name="error_expiryMustComeAfterCreation">datum poteka mora biti kasnejši od datuma nastanka</string>
+ <string name="error_noEncryptionKeysOrPassPhrase">dan ni bil noben šifrirni ključ ali geslo</string>
+ <string name="error_signatureFailed">podpis ni bil uspešen</string>
+ <string name="error_noSignaturePassPhrase">dano ni bilo nobeno geslo</string>
+ <string name="error_noSignatureKey">dan ni bil noben podpisni ključ</string>
+ <string name="error_invalidData">neveljavni šifrirni podatki</string>
+ <string name="error_corruptData">pokvarjeni podatki</string>
+ <string name="error_noSymmetricEncryptionPacket">ne najdem podatkov s simetrično enkripcijo</string>
+ <string name="error_wrongPassPhrase">napačno geslo</string>
+ <string name="error_savingKeys">napaka pri shranjevanju nakaterih ključev</string>
+
+ <!-- progress_lowerCase: lowercase, phrases, usually ending in '...' -->
+ <string name="progress_done">končano.</string>
+ <string name="progress_initializing">inicializiram...</string>
+ <string name="progress_saving">shranjujem...</string>
+ <string name="progress_importing">uvažam...</string>
+ <string name="progress_exporting">izvažam...</string>
+ <string name="progress_generating">generiram ključ, to lahko traja nekaj časa...</string>
+ <string name="progress_buildingKey">gradim ključ...</string>
+ <string name="progress_preparingMasterKey">pripravljam statični ključ...</string>
+ <string name="progress_certifyingMasterKey">potrjujem statični ključ...</string>
+ <string name="progress_buildingMasterKeyRing">gradim datoteko s statičnimi ključi...</string>
+ <string name="progress_addingSubKeys">dodajam podključe...</string>
+ <string name="progress_savingKeyRing">shranjujem datoteko s ključi...</string>
+ <string name="progress_importingSecretKeys">uvažam zasebne ključe...</string>
+ <string name="progress_importingPublicKeys">uvažam javne ključe...</string>
+ <string name="progress_reloadingKeys">reloading keys...</string>
+ <string name="progress_exportingKey">izvažam ključ...</string>
+ <string name="progress_exportingKeys">izvažam ključe...</string>
+ <string name="progress_extractingSignatureKey">izvlačim podpisni kluč...</string>
+ <string name="progress_extractingKey">izvlačim ključ...</string>
+ <string name="progress_preparingStreams">pripravljam tok...</string>
+ <string name="progress_encrypting">šifriram podatke...</string>
+ <string name="progress_decrypting">dešifriram podatke...</string>
+ <string name="progress_preparingSignature">pripravljam podpis...</string>
+ <string name="progress_generatingSignature">generiram podpis...</string>
+ <string name="progress_processingSignature">obdelujem podpis...</string>
+ <string name="progress_verifyingSignature">overovljam podpis...</string>
+ <string name="progress_signing">podpisujem...</string>
+ <string name="progress_readingData">berem podatke...</string>
+ <string name="progress_findingKey">iščem ključ...</string>
+ <string name="progress_decompressingData">raztezam podatke...</string>
+ <string name="progress_verifyingIntegrity">overovljam integriteto...</string>
+ <string name="progress_deletingSecurely">varno brišem \'%s\'...</string>
+ <string name="progress_queryingServer">poizvedujem na %s...</string>
+
+ <!-- permission strings -->
+ <string name="permission_read_key_details_label">Preverite podrobnosti ključev v APG.</string>
+ <string name="permission_read_key_details_description">Preverite podrobnosti javnih in zasebnih ključev shranjenih v APG, kot so identiteta ključev in identiteta uporabnikov. Samih ključev NI MOGOČE preveriti.</string>
+
+ <!-- action strings -->
+ <string name="action_encrypt">Šifriraj</string>
+ <string name="action_decrypt">Dešifriraj</string>
+ <string name="action_importPublic">Uvozi javne ključe</string>
+ <string name="action_importSecret">Uvozi zasebne ključe</string>
+ <string name="hint_publicKeys">Poišči javne ključe</string>
+ <string name="hint_secretKeys">Poišči zasebne ključe</string>
+ <string name="filterInfo">Filter: \"%s\"</string>
+
+ <!-- misc -->
+ <string name="fast">hitro</string>
+ <string name="slow">počasi</string>
+ <string name="very_slow">zelo počasi</string>
+
+ <!-- texts -->
+ <!-- "OI File Manager", "ASTRO", and "K-9 Mail" must not be translated in order for the links to the market to work. -->
+ <string name="text_help">Za boljšo integracijo namestite program K-9 Mail, ki omogoča način PGP/INLINE in neposredno šifriranje/dešifriranje e-pošte.
+ \n\nZaželjeno je, da namestite programa OI File Manager ali ASTRO, ki omogočata iskanje, izbiro in vnos datotek v APG.
+ \n\nZa začetek potrebujete nekaj ključev. Lahko jih uvozite s klikom na menija \"Upravljanje javnih ključev\" in \"Upravljanje zasebnih ključev\" ali jih ustvarite v meniju \"Upravljanje zasebnih ključev\".
+ \n\nPreko menija \"Dodaj račun\" lahko dodate vaše GMail račune in tako poenostavite dešifriranje e-pošte prejete nanje.
+ \n\nDa bi odkrili dodatne funkcije in zmožnosti programa APG, se sprehodite skozi njegove menije.</string>
+
+</resources> \ No newline at end of file
diff --git a/org_apg/res/values-zh/strings.xml b/org_apg/res/values-zh/strings.xml
new file mode 100644
index 000000000..8fa3d23fc
--- /dev/null
+++ b/org_apg/res/values-zh/strings.xml
@@ -0,0 +1,309 @@
+<?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.
+
+ Author: Fredrick Zhang <fredrick.zhang@nagra.com>
+-->
+
+<resources>
+
+ <!-- title_lowerCase: capitalized words, no punctuation -->
+ <string name="title_mailInbox">收件箱</string>
+ <string name="title_managePublicKeys">管理公钥</string>
+ <string name="title_manageSecretKeys">管理私钥</string>
+ <string name="title_selectRecipients">选择接收人</string>
+ <string name="title_selectSignature">选择签名</string>
+ <string name="title_encrypt">加密</string>
+ <string name="title_decrypt">解密</string>
+ <string name="title_authentication">密码口令</string>
+ <string name="title_createKey">创建密钥</string>
+ <string name="title_editKey">编辑密钥</string>
+ <string name="title_preferences">设置</string>
+ <string name="title_keyServerPreference">密钥服务器设置</string>
+ <string name="title_changePassPhrase">更改密码口令</string>
+ <string name="title_setPassPhrase">设置密码口令</string>
+ <string name="title_sendEmail">\"发送邮件...\"</string>
+ <string name="title_encryptToFile">加密至文件</string>
+ <string name="title_decryptToFile">解密至文件</string>
+ <string name="title_addAccount">添加账户</string>
+ <string name="title_importKeys">导入密钥</string>
+ <string name="title_exportKey">导出密钥</string>
+ <string name="title_exportKeys">导出密钥</string>
+ <string name="title_keyNotFound">密钥未发现</string>
+ <string name="title_help">准备开始</string>
+ <string name="title_keyServerQuery">查询密钥服务器</string>
+ <string name="title_unknownSignatureKey">未知签名密钥</string>
+
+ <!-- section_lowerCase: capitalized words, no punctuation -->
+ <string name="section_userIds">用户标识</string>
+ <string name="section_keys">密钥</string>
+ <string name="section_general">常规</string>
+ <string name="section_defaults">默认</string>
+ <string name="section_advanced">高级</string>
+
+ <!-- btn_lowerCase: capitalized words, no punctuation -->
+ <string name="btn_signToClipboard">签名至剪贴板</string>
+ <string name="btn_encryptToClipboard">加密至剪贴板</string>
+ <string name="btn_encryptAndEmail">加密并发送邮件</string>
+ <string name="btn_signAndEmail">签名并发送邮件</string>
+ <string name="btn_encrypt">加密</string>
+ <string name="btn_sign">签名</string>
+ <string name="btn_decrypt">解密</string>
+ <string name="btn_verify">验证</string>
+ <string name="btn_selectEncryptKeys">选择接收人</string>
+ <string name="btn_reply">回复</string>
+ <string name="btn_encryptMessage">加密信息</string>
+ <string name="btn_decryptMessage">解密信息</string>
+ <string name="btn_encryptFile">加密文件</string>
+ <string name="btn_decryptFile">解密文件</string>
+ <string name="btn_save">保存</string>
+ <string name="btn_doNotSave">取消</string>
+ <string name="btn_delete">删除</string>
+ <string name="btn_noDate">无</string>
+ <string name="btn_clearFilter">清空过滤器</string>
+ <string name="btn_changePassPhrase">更改密码口令</string>
+ <string name="btn_setPassPhrase">设置密码口令</string>
+ <string name="btn_search">搜索</string>
+
+ <!-- menu_lowerCase: capitalized words, no punctuation -->
+ <string name="menu_about">关于</string>
+ <string name="menu_addAccount">添加GMail账户</string>
+ <string name="menu_deleteAccount">删除账户</string>
+ <string name="menu_managePublicKeys">管理公钥</string>
+ <string name="menu_manageSecretKeys">管理私钥</string>
+ <string name="menu_preferences">设置</string>
+ <string name="menu_importKeys">导入密钥</string>
+ <string name="menu_exportKeys">导出密钥</string>
+ <string name="menu_exportKey">导出密钥</string>
+ <string name="menu_deleteKey">删除密钥</string>
+ <string name="menu_createKey">创建密钥</string>
+ <string name="menu_editKey">编辑密钥</string>
+ <string name="menu_search">搜索</string>
+ <string name="menu_help">帮助</string>
+ <string name="menu_keyServer">密钥服务器</string>
+ <string name="menu_updateKey">更新密钥</string>
+
+ <!-- label_lowerCase: capitalized words, no punctuation -->
+ <string name="label_sign">签名</string>
+ <string name="label_message">信息</string>
+ <string name="label_file">文件</string>
+ <string name="label_passPhrase">密码口令</string>
+ <string name="label_passPhraseAgain">确认口令</string>
+ <string name="label_algorithm">算法</string>
+ <string name="label_asciiArmour">二进制转文本编码</string>
+ <string name="label_selectPublicKeys">选择公钥</string>
+ <string name="label_deleteAfterEncryption">加密后删除</string>
+ <string name="label_deleteAfterDecryption">解密后删除</string>
+ <string name="label_deleteAfterImport">导入后删除</string>
+ <string name="label_encryptionAlgorithm">加密算法</string>
+ <string name="label_hashAlgorithm">哈希算法</string>
+ <string name="label_asymmetric">非对称算法</string>
+ <string name="label_symmetric">对称算法</string>
+ <string name="label_passPhraseCacheTtl">口令缓存</string>
+ <string name="label_messageCompression">信息压缩</string>
+ <string name="label_fileCompression">文件压缩</string>
+ <string name="label_language">语言</string>
+ <string name="label_forceV3Signature">强制V3签名</string>
+ <string name="label_keyServers">密钥服务器</string>
+ <string name="label_keyId">密钥标识</string>
+ <string name="label_creation">创建日期</string>
+ <string name="label_expiry">期限</string>
+ <string name="label_usage">用途</string>
+ <string name="label_keySize">密钥尺寸</string>
+ <string name="label_mainUserId">主用户标识</string>
+ <string name="label_name">姓名</string>
+ <string name="label_comment">注释</string>
+ <string name="label_email">电子邮件</string>
+ <string name="noKeysSelected">选择</string>
+ <string name="oneKeySelected">1个选定</string>
+ <string name="nKeysSelected">个选定</string>
+ <string name="unknownUserId">&lt;未知></string>
+ <string name="none">&lt;无></string>
+ <string name="noKey">&lt;无密钥></string>
+ <string name="noDate">-</string>
+ <string name="noExpiry">&lt;不过期></string>
+ <string name="unknownStatus"></string>
+ <string name="canEncrypt">可以加密</string>
+ <string name="canSign">可以签名</string>
+ <string name="expired">已过期</string>
+ <string name="notValid">不合法</string>
+ <string name="nKeyServers">%s 个密钥服务器</string>
+
+ <!-- choice_lowerCase: capitalized first word, no punctuation -->
+ <string name="choice_none">无</string>
+ <string name="choice_signOnly">仅签名</string>
+ <string name="choice_encryptOnly">仅加密</string>
+ <string name="choice_signAndEncrypt">签名且加密</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_untilQuit">直到退出</string>
+ <string name="choice_language_system">系统默认</string>
+ <string name="dsa">DSA</string>
+ <string name="elgamal">ElGamal</string>
+ <string name="rsa">RSA</string>
+ <string name="filemanager_titleOpen">打开...</string>
+ <string name="filemanager_titleSave">另存为...</string>
+ <string name="filemanager_titleEncrypt">选择要加密的文件...</string>
+ <string name="filemanager_titleDecrypt">选择要解密的文件...</string>
+ <string name="filemanager_btnOpen">打开</string>
+ <string name="filemanager_btnSave">保存</string>
+ <string name="warning">警告</string>
+ <string name="error">错误</string>
+ <string name="warningMessage">警告: %s</string>
+ <string name="errorMessage">错误: %s</string>
+
+ <!-- sentences -->
+ <string name="wrongPassPhrase">密码口令错误.</string>
+ <string name="usingClipboardContent">使用剪贴板内容.</string>
+ <string name="keySaved">密钥已保存.</string>
+ <string name="setAPassPhrase">请先设置密码口令.</string>
+ <string name="noFilemanagerInstalled">没有兼容的文件管理器被安装.</string>
+ <string name="passPhrasesDoNotMatch">密码口令不匹配.</string>
+ <string name="passPhraseMustNotBeEmpty">不允许空的密码口令.</string>
+ <string name="passPhraseForSymmetricEncryption">对称加密.</string>
+ <string name="passPhraseFor">%s</string>
+ <string name="fileDeleteConfirmation">确定要删除\n%s?</string>
+ <string name="fileDeleteSuccessful">删除成功.</string>
+ <string name="noFileSelected">请先选择文件.</string>
+ <string name="decryptionSuccessful">解密成功.</string>
+ <string name="encryptionSuccessful">加密成功.</string>
+ <string name="encryptionToClipboardSuccessful">成功加密至剪贴板.</string>
+ <string name="enterPassPhraseTwice">输入密码口令两次.</string>
+ <string name="selectEncryptionKey">至少选择一个加密密钥.</string>
+ <string name="selectEncryptionOrSignatureKey">至少选择一个加密密钥或一个签名密钥.</string>
+ <string name="specifyFileToEncryptTo">请指定加密生成的文件.\n警告! 文件如存在会被覆盖.</string>
+ <string name="specifyFileToDecryptTo">请指定解密生成的文件.\n警告! 文件如存在会被覆盖.</string>
+ <string name="specifyGoogleMailAccount">指定你想添加的Google Mail帐号.</string>
+ <string name="specifyFileToImportFrom">请指定导入密钥的文件. (.asc or .gpg)</string>
+ <string name="specifyFileToExportTo">请指定导出文件.\n警告! 文件如存在会被覆盖.</string>
+ <string name="specifyFileToExportSecretKeysTo">请指定导出文件.\n警告! 你正准备导出私钥.\n警告! 文件如存在会被覆盖.</string>
+ <string name="keyDeletionConfirmation">确认需要删除 \'%s\'?\n操作无法被恢复!</string>
+ <string name="secretKeyDeletionConfirmation">确认需要删除私钥 \'%s\'?\n操作无法被恢复!</string>
+ <string name="keysAddedAndUpdated">成功添加 %1$s 个密钥和更新 %2$s 个密钥.</string>
+ <string name="keysAdded">成功添加 %s 个密钥.</string>
+ <string name="keysUpdated">成功更新 %s 个密钥.</string>
+ <string name="noKeysAddedOrUpdated">无密钥被添加或更新.</string>
+ <string name="keyExported">成功导出 1 个密钥.</string>
+ <string name="keysExported">成功导出 %s 个密钥.</string>
+ <string name="noKeysExported">无密钥导出.</string>
+ <string name="keyCreationElGamalInfo">注意: 只有子密钥支持ElGamal, 最接近的密钥大小如1536, 2048, 3072, 4096, 8192会被其使用.</string>
+ <string name="keyNotFound">无法找到密钥 %08X.</string>
+ <string name="keysFound">发现 %s 个密钥.</string>
+ <string name="unknownSignatureKeyTouchToLookUp">未知签名, 请查找密钥.</string>
+ <string name="keyEditingIsBeta">密钥编辑仍处于测试阶段.</string>
+ <string name="badKeysEncountered">忽略 %s 个错误的密钥. 或许导出时你使用了\n --export-secret-subkeys\n请确保使用\n --export-secret-keys\n代替前者.</string>
+ <string name="lookupUnknownKey">未知密钥 %s, 是否需要在密钥服务器上查找?</string>
+
+ <!--
+ error_lowerCase: phrases, no punctuation, all lowercase,
+ they will be put after "errorMessage", e.g. "Error: file not found"
+ -->
+ <string name="error_fileDeleteFailed">删除 \'%s\' 失败</string>
+ <string name="error_fileNotFound">文件没有找到</string>
+ <string name="error_noSecretKeyFound">未发现合适的私钥</string>
+ <string name="error_noKnownEncryptionFound">未知加密方式</string>
+ <string name="error_externalStorageNotReady">外部存储设备未准备就绪</string>
+ <string name="error_accountNotFound">账户 \'%s\' 未发现</string>
+ <string name="error_accountReadingNotAllowed">无读取账户权限</string>
+ <string name="error_addingAccountFailed">添加账户 \'%s\' 失败</string>
+ <string name="error_invalidEmail">无效电子邮件 \'%s\'</string>
+ <string name="error_keySizeMinimum512bit">文件尺寸至少需512比特</string>
+ <string name="error_masterKeyMustNotBeElGamal">主密钥不可以是ElGamal</string>
+ <string name="error_unknownAlgorithmChoice">选择了未知算法</string>
+ <string name="error_userIdNeedsAName">需指定姓名</string>
+ <string name="error_userIdNeedsAnEmailAddress">需指定电子邮件</string>
+ <string name="error_keyNeedsAUserId">至少需要一个用户标识</string>
+ <string name="error_mainUserIdMustNotBeEmpty">主用户标识不能为空</string>
+ <string name="error_keyNeedsMasterKey">至少需要选择一个主密钥</string>
+ <string name="error_expiryMustComeAfterCreation">期限日期必需在创建日期之后</string>
+ <string name="error_noEncryptionKeysOrPassPhrase">无加密密钥或密码口令被提供</string>
+ <string name="error_signatureFailed">签名失败</string>
+ <string name="error_noSignaturePassPhrase">无密码口令被提供</string>
+ <string name="error_noSignatureKey">无签名密钥被提供</string>
+ <string name="error_invalidData">加密数据不合法</string>
+ <string name="error_corruptData">数据损坏</string>
+ <string name="error_noSymmetricEncryptionPacket">未发现对称加密包</string>
+ <string name="error_wrongPassPhrase">密码口令错误</string>
+ <string name="error_savingKeys">保存密钥出错</string>
+ <string name="error_couldNotExtractPrivateKey">无法提取私钥</string>
+
+ <!-- progress_lowerCase: lowercase, phrases, usually ending in '...' -->
+ <string name="progress_done">完成.</string>
+ <string name="progress_initializing">初始化中...</string>
+ <string name="progress_saving">保存中...</string>
+ <string name="progress_importing">导入中...</string>
+ <string name="progress_exporting">导出中...</string>
+ <string name="progress_generating">正在创建密钥, 请等待...</string>
+ <string name="progress_buildingKey">生成密钥中...</string>
+ <string name="progress_preparingMasterKey">正在准备主密钥...</string>
+ <string name="progress_certifyingMasterKey">正在认证主密钥...</string>
+ <string name="progress_buildingMasterKeyRing">正在生成主密钥圈...</string>
+ <string name="progress_addingSubKeys">正在添加子密钥...</string>
+ <string name="progress_savingKeyRing">正在保存密钥圈...</string>
+ <string name="progress_importingSecretKeys">导入私钥中...</string>
+ <string name="progress_importingPublicKeys">导入公钥中...</string>
+ <string name="progress_reloadingKeys">重新加载密钥...</string>
+ <string name="progress_exportingKey">导出密钥中...</string>
+ <string name="progress_exportingKeys">导出密钥中...</string>
+ <string name="progress_extractingSignatureKey">提取签名密钥中...</string>
+ <string name="progress_extractingKey">提取密钥中...</string>
+ <string name="progress_preparingStreams">准备数据流...</string>
+ <string name="progress_encrypting">加密数据中...</string>
+ <string name="progress_decrypting">解密数据中...</string>
+ <string name="progress_preparingSignature">正在准备签名...</string>
+ <string name="progress_generatingSignature">正在生成签名...</string>
+ <string name="progress_processingSignature">处理签名中...</string>
+ <string name="progress_verifyingSignature">验证签名中...</string>
+ <string name="progress_signing">正在签名...</string>
+ <string name="progress_readingData">读取数据中...</string>
+ <string name="progress_findingKey">正在搜索密钥...</string>
+ <string name="progress_decompressingData">解压数据中...</string>
+ <string name="progress_verifyingIntegrity">正在验证集成...</string>
+ <string name="progress_deletingSecurely">正在安全删除 \'%s\' ...</string>
+ <string name="progress_querying">正在查询...</string>
+ <string name="progress_queryingServer">正在查询 %s...</string>
+
+ <!-- permission strings -->
+ <string name="permission_read_key_details_label">从APG读取密钥信息.</string>
+ <string name="permission_read_key_details_description">读取保存在APG中的公钥和私钥信息, 比如密钥标识和用户标识. 这些密钥本身无法被读取.</string>
+
+ <!-- action strings -->
+ <string name="action_encrypt">加密</string>
+ <string name="action_decrypt">解密</string>
+ <string name="action_importPublic">导入公钥</string>
+ <string name="action_importSecret">导入私钥</string>
+ <string name="hint_publicKeys">搜索公钥</string>
+ <string name="hint_secretKeys">搜索私钥</string>
+ <string name="filterInfo">过滤信息: \"%s\"</string>
+
+ <!-- misc -->
+ <string name="fast">快</string>
+ <string name="slow">慢</string>
+ <string name="very_slow">非常慢</string>
+
+ <!-- texts -->
+ <!-- "OI File Manager", "ASTRO", and "K-9 Mail" must not be translated in order for the links to the market to work. -->
+ <string name="text_help">装 K-9 Mail 以便达到最好的集成, K9支持APG处理PGP/INLINE并让你直接加密或解密邮件.
+\n\n建议安装 OI File Manager 或 ASTRO 以便在APG中浏览文件夹.
+\n\n首先你需要一些密钥. 可以通过菜单 \"管理公钥\" 和 \"管理私钥\" 来导入, 或者在菜单 \"管理私钥\" 中创建.
+\n\n你也可以通过菜单 \"添加账户\" 来添加GMail账户, 这样可以简化解密那些账户中邮件的过程.
+\n\n可以通过查看选项菜单寻找更多的功能.</string>
+
+</resources> \ No newline at end of file
diff --git a/org_apg/res/values/arrays.xml b/org_apg/res/values/arrays.xml
new file mode 100644
index 000000000..25a4e66df
--- /dev/null
+++ b/org_apg/res/values/arrays.xml
@@ -0,0 +1,214 @@
+<?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.
+-->
+
+<resources>
+
+ <string-array name="pass_phrase_cache_ttl_entries">
+ <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>
+ </string-array>
+
+ <string-array name="pass_phrase_cache_ttl_values">
+ <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>
+ </string-array>
+
+ <string-array name="language_entries">
+ <item>@string/choice_language_system</item>
+ <item>Afrikaans</item>
+ <item>Bahasa indonesia</item>
+ <item>Bahasa melayu</item>
+ <item>Bamanankan</item>
+ <item>Català</item>
+ <item>Cymraeg</item>
+ <item>Dansk</item>
+ <item>Deutsch</item>
+ <item>Dholuo</item>
+ <item>Eesti</item>
+ <item>English</item>
+ <item>Español</item>
+ <item>Fulfulde, Pulaar, Pular</item>
+ <item>Gaeilge</item>
+ <item>Galego</item>
+ <item>Hausa</item>
+ <item>Hrvatski</item>
+ <item>Kinyarwanda</item>
+ <item>Kirundi</item>
+ <item>Kiswahili</item>
+ <item>Latviešu</item>
+ <item>Lietuviškai</item>
+ <item>Magyar</item>
+ <item>Malti</item>
+ <item>Nederlands</item>
+ <item>Norsk bokmål</item>
+ <item>Pyccĸий</item>
+ <item>Română</item>
+ <item>Slovenčina</item>
+ <item>Slovenščina</item>
+ <item>Somali</item>
+ <item>Српски</item>
+ <item>Tiếng Việt</item>
+ <item>Tϋrkçe</item>
+ <item>Wolof</item>
+ <item>Yorùbá</item>
+ <item>Azərbaycan</item>
+ <item>Euskera</item>
+ <item>Français</item>
+ <item>isiXhosa</item>
+ <item>isiZulu</item>
+ <item>Italiano</item>
+ <item>O\'zbek</item>
+ <item>Polski</item>
+ <item>Português</item>
+ <item>Shqip</item>
+ <item>Suomi</item>
+ <item>Svenska</item>
+ <item>Íslenska</item>
+ <item>Čeština</item>
+ <item>Ɛʋɛ</item>
+ <item>Ελληνικά</item>
+ <item>Беларуская</item>
+ <item>Български</item>
+ <item>Кыргыз</item>
+ <item>Македонски</item>
+ <item>Українська</item>
+ <item>аҧсуа бызшәа</item>
+ <item>Қазақ</item>
+ <item>Հայերեն</item>
+ <item>עברית</item>
+ <item>اردو</item>
+ <item>العربية</item>
+ <item>فارسی</item>
+ <item>پښتو</item>
+ <item>हिंदी</item>
+ <item>తెలుగు</item>
+ <item>ಕನ್ನಡ</item>
+ <item>ภาษาไทย</item>
+ <item>አማርኛ</item>
+ <item>中文</item>
+ <item>日本語</item>
+ <item>한국어</item>
+ </string-array>
+
+ <string-array name="language_values">
+ <item></item>
+ <item>af</item>
+ <item>id</item>
+ <item>ms</item>
+ <item>bm</item>
+ <item>ca</item>
+ <item>cy</item>
+ <item>da</item>
+ <item>de</item>
+ <item>luo</item>
+ <item>et</item>
+ <item>en</item>
+ <item>es</item>
+ <item>ff</item>
+ <item>ga</item>
+ <item>gl</item>
+ <item>ha</item>
+ <item>hr</item>
+ <item>rw</item>
+ <item>rn</item>
+ <item>sw</item>
+ <item>lv</item>
+ <item>lt</item>
+ <item>hu</item>
+ <item>mt</item>
+ <item>nl</item>
+ <item>no</item>
+ <item>ru</item>
+ <item>ro</item>
+ <item>sk</item>
+ <item>sl</item>
+ <item>so</item>
+ <item>sr</item>
+ <item>vi</item>
+ <item>tr</item>
+ <item>wo</item>
+ <item>yo</item>
+ <item>az</item>
+ <item>eu</item>
+ <item>fr</item>
+ <item>xs</item>
+ <item>zu</item>
+ <item>it</item>
+ <item>uz</item>
+ <item>pl</item>
+ <item>pt</item>
+ <item>sq</item>
+ <item>fi</item>
+ <item>sv</item>
+ <item>is</item>
+ <item>cs</item>
+ <item>ee</item>
+ <item>el</item>
+ <item>be</item>
+ <item>bg</item>
+ <item>ky</item>
+ <item>mk</item>
+ <item>uk</item>
+ <item>ab</item>
+ <item>kk</item>
+ <item>hy</item>
+ <item>he</item>
+ <item>ur</item>
+ <item>ar</item>
+ <item>fa</item>
+ <item>ps</item>
+ <item>hi</item>
+ <item>te</item>
+ <item>kn</item>
+ <item>th</item>
+ <item>am</item>
+ <item>zh</item>
+ <item>ja</item>
+ <item>ko</item>
+ </string-array>
+
+ <string-array name="supported_languages">
+ <item></item>
+ <item>da</item>
+ <item>de</item>
+ <item>en</item>
+ <item>es</item>
+ <item>it</item>
+ <item>no</item>
+ <item>pt</item>
+ <item>sl</item>
+ <item>zh</item>
+ </string-array>
+
+</resources>
diff --git a/org_apg/res/values/static_strings.xml b/org_apg/res/values/static_strings.xml
new file mode 100644
index 000000000..c8a6a5026
--- /dev/null
+++ b/org_apg/res/values/static_strings.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <string name="app_name" translate="false">APG+</string>
+ <string name="about_url" translate="false">https://github.com/dschuermann/apg</string>
+
+</resources> \ No newline at end of file
diff --git a/org_apg/res/values/strings.xml b/org_apg/res/values/strings.xml
new file mode 100644
index 000000000..4cfc99df8
--- /dev/null
+++ b/org_apg/res/values/strings.xml
@@ -0,0 +1,333 @@
+<?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.
+-->
+
+<resources>
+
+ <!-- title_lowerCase: capitalized words, no punctuation -->
+ <string name="title_mailInbox">Mail Inbox</string>
+ <string name="title_managePublicKeys">Manage Public Keys</string>
+ <string name="title_manageSecretKeys">Manage Secret Keys</string>
+ <string name="title_selectRecipients">Select Recipients</string>
+ <string name="title_selectSignature">Select Signature</string>
+ <string name="title_encrypt">Encrypt</string>
+ <string name="title_decrypt">Decrypt</string>
+ <string name="title_authentication">Pass Phrase</string>
+ <string name="title_createKey">Create Key</string>
+ <string name="title_editKey">Edit Key</string>
+ <string name="title_preferences">Preferences</string>
+ <string name="title_keyServerPreference">Key Server Preference</string>
+ <string name="title_changePassPhrase">Change Pass Phrase</string>
+ <string name="title_setPassPhrase">Set Pass Phrase</string>
+ <string name="title_sendEmail">"Send Mail..."</string>
+ <string name="title_encryptToFile">Encrypt To File</string>
+ <string name="title_decryptToFile">Decrypt To File</string>
+ <string name="title_addAccount">Add Account</string>
+ <string name="title_importKeys">Import Keys</string>
+ <string name="title_exportKey">Export Key</string>
+ <string name="title_exportKeys">Export Keys</string>
+ <string name="title_keyNotFound">Key Not Found</string>
+ <string name="title_help">Getting Started</string>
+ <string name="title_keyServerQuery">Query Key Server</string>
+ <string name="title_sendKey">Export to Key Server</string>
+ <string name="title_unknownSignatureKey">Unknown Signature Key</string>
+ <string name="title_importFromQRCode">Import from QR Code</string>
+ <string name="title_signKey">Sign Key</string>
+ <string name="title_about">About</string>
+
+ <!-- section_lowerCase: capitalized words, no punctuation -->
+ <string name="section_userIds">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>
+
+ <!-- btn_lowerCase: capitalized words, no punctuation -->
+ <string name="btn_signToClipboard">Sign To Clipboard</string>
+ <string name="btn_encryptToClipboard">Encrypt To Clipboard</string>
+ <string name="btn_encryptAndEmail">Encrypt And Email</string>
+ <string name="btn_signAndEmail">Sign And Email</string>
+ <string name="btn_encrypt">Encrypt</string>
+ <string name="btn_sign">Sign</string>
+ <string name="btn_decrypt">Decrypt</string>
+ <string name="btn_verify">Verify</string>
+ <string name="btn_selectEncryptKeys">Select Recipients</string>
+ <string name="btn_reply">Reply</string>
+ <string name="btn_encryptMessage">Encrypt Message</string>
+ <string name="btn_decryptMessage">Decrypt Message</string>
+ <string name="btn_encryptFile">Encrypt File</string>
+ <string name="btn_decryptFile">Decrypt File</string>
+ <string name="btn_save">Save</string>
+ <string name="btn_doNotSave">Cancel</string>
+ <string name="btn_delete">Delete</string>
+ <string name="btn_noDate">None</string>
+ <string name="btn_clearFilter">Clear Filter</string>
+ <string name="btn_changePassPhrase">Change Pass Phrase</string>
+ <string name="btn_setPassPhrase">Set Pass Phrase</string>
+ <string name="btn_search">Search</string>
+ <string name="btn_export_to_server">Export To Server</string>
+
+ <!-- menu_lowerCase: capitalized words, no punctuation -->
+ <string name="menu_about">About</string>
+ <string name="menu_addAccount">Add GMail Account</string>
+ <string name="menu_deleteAccount">Delete Account</string>
+ <string name="menu_managePublicKeys">Manage Public Keys</string>
+ <string name="menu_manageSecretKeys">Manage Secret Keys</string>
+ <string name="menu_preferences">Settings</string>
+ <string name="menu_importKeys">Import Keys</string>
+ <string name="menu_exportKeys">Export Keys</string>
+ <string name="menu_exportKey">Export Key</string>
+ <string name="menu_deleteKey">Delete Key</string>
+ <string name="menu_createKey">Create Key</string>
+ <string name="menu_editKey">Edit Key</string>
+ <string name="menu_search">Search</string>
+ <string name="menu_help">Help</string>
+ <string name="menu_keyServer">Key Server</string>
+ <string name="menu_updateKey">Update</string>
+ <string name="menu_exportKeyToServer">Export To Server</string>
+ <string name="menu_share">Share</string>
+ <string name="menu_scanQRCode">Scan QR Code</string>
+ <string name="menu_signKey">Sign Key</string>
+
+ <!-- label_lowerCase: capitalized words, no punctuation -->
+ <string name="label_sign">Sign</string>
+ <string name="label_message">Message</string>
+ <string name="label_file">File</string>
+ <string name="label_passPhrase">Pass Phrase</string>
+ <string name="label_passPhraseAgain">Again</string>
+ <string name="label_algorithm">Algorithm</string>
+ <string name="label_asciiArmour">ASCII Armour</string>
+ <string name="label_selectPublicKeys">Public Key(s)</string>
+ <string name="label_deleteAfterEncryption">Delete After Encryption</string>
+ <string name="label_deleteAfterDecryption">Delete After Decryption</string>
+ <string name="label_deleteAfterImport">Delete After Import</string>
+ <string name="label_encryptionAlgorithm">Encryption Algorithm</string>
+ <string name="label_hashAlgorithm">Hash Algorithm</string>
+ <string name="label_asymmetric">Public Key</string>
+ <string name="label_symmetric">Pass Phrase</string>
+ <string name="label_passPhraseCacheTtl">Pass Phrase Cache</string>
+ <string name="label_messageCompression">Message Compression</string>
+ <string name="label_fileCompression">File Compression</string>
+ <string name="label_language">Language</string>
+ <string name="label_forceV3Signature">Force V3 Signatures</string>
+ <string name="label_keyServers">Key Servers</string>
+ <string name="label_keyId">Key ID</string>
+ <string name="label_creation">Creation</string>
+ <string name="label_expiry">Expiry</string>
+ <string name="label_usage">Usage</string>
+ <string name="label_keySize">Key Size</string>
+ <string name="label_mainUserId">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_sendKey">Send Key to Server?</string>
+ <string name="noKeysSelected">Select</string>
+ <string name="oneKeySelected">1 Selected</string>
+ <string name="nKeysSelected">Selected</string>
+ <string name="unknownUserId">&lt;unknown></string>
+ <string name="none">&lt;none></string>
+ <string name="noKey">&lt;no key></string>
+ <string name="noDate">-</string>
+ <string name="noExpiry">&lt;no expiry></string>
+ <string name="unknownStatus"></string>
+ <string name="canEncrypt">can encrypt</string>
+ <string name="canSign">can sign</string>
+ <string name="expired">expired</string>
+ <string name="notValid">not valid</string>
+ <string name="nKeyServers">%s key server(s)</string>
+ <string name="fingerprint">fingerprint</string>
+
+ <!-- choice_lowerCase: capitalized first word, no punctuation -->
+ <string name="choice_none">None</string>
+ <string name="choice_signOnly">Sign only</string>
+ <string name="choice_encryptOnly">Encrypt only</string>
+ <string name="choice_signAndEncrypt">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_untilQuit">until quit</string>
+ <string name="choice_language_system">System default</string>
+ <string name="dsa">DSA</string>
+ <string name="elgamal">ElGamal</string>
+ <string name="rsa">RSA</string>
+ <string name="filemanager_titleOpen">Open...</string>
+ <string name="filemanager_titleSave">Save As...</string>
+ <string name="filemanager_titleEncrypt">Select File To Encrypt...</string>
+ <string name="filemanager_titleDecrypt">Select File To Decrypt...</string>
+ <string name="filemanager_btnOpen">Open</string>
+ <string name="filemanager_btnSave">Save</string>
+ <string name="warning">Warning</string>
+ <string name="error">Error</string>
+ <string name="warningMessage">Warning: %s</string>
+ <string name="errorMessage">Error: %s</string>
+
+ <!-- sentences -->
+ <string name="wrongPassPhrase">Wrong pass phrase.</string>
+ <string name="usingClipboardContent">Using clipboard content.</string>
+ <string name="keySaved">Key saved.</string>
+ <string name="setAPassPhrase">Set a pass phrase first.</string>
+ <string name="noFilemanagerInstalled">No compatible file manager installed.</string>
+ <string name="passPhrasesDoNotMatch">The pass phrases didn\'t match.</string>
+ <string name="passPhraseMustNotBeEmpty">Empty pass phrases are not allowed.</string>
+ <string name="passPhraseForSymmetricEncryption">Symmetric encryption.</string>
+ <string name="passPhraseFor">%s</string>
+ <string name="fileDeleteConfirmation">Are you sure you want to delete\n%s?</string>
+ <string name="fileDeleteSuccessful">Successfully deleted.</string>
+ <string name="noFileSelected">Select a file first.</string>
+ <string name="decryptionSuccessful">Successfully decrypted.</string>
+ <string name="encryptionSuccessful">Successfully encrypted.</string>
+ <string name="encryptionToClipboardSuccessful">Successfully encrypted to clipboard.</string>
+ <string name="enterPassPhraseTwice">Enter the pass phrase twice.</string>
+ <string name="selectEncryptionKey">Select at least one encryption key.</string>
+ <string name="selectEncryptionOrSignatureKey">Select at least one encryption key or a signature key.</string>
+ <string name="specifyFileToEncryptTo">Please specify which file to encrypt to.\nWARNING! File will be overwritten if it exists.</string>
+ <string name="specifyFileToDecryptTo">Please specify which file to decrypt to.\nWARNING! File will be overwritten if it exists.</string>
+ <string name="specifyGoogleMailAccount">Specify the Google Mail account you want to add.</string>
+ <string name="specifyFileToImportFrom">Please specify which file to import keys from. (.asc or .gpg)</string>
+ <string name="specifyFileToExportTo">Please specify which file to export to.\nWARNING! File will be overwritten if it exists.</string>
+ <string name="specifyFileToExportSecretKeysTo">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="keyDeletionConfirmation">Do you really want to delete the key \'%s\'?\nYou can\'t undo this!</string>
+ <string name="secretKeyDeletionConfirmation">Do you really want to delete the SECRET key \'%s\'?\nYou can\'t undo this!</string>
+ <string name="keysAddedAndUpdated">Successfully added %1$s key(s) and updated %2$s key(s).</string>
+ <string name="keysAdded">Successfully added %s key(s).</string>
+ <string name="keysUpdated">Successfully updated %s key(s).</string>
+ <string name="noKeysAddedOrUpdated">No keys added or updated.</string>
+ <string name="keyExported">Successfully exported 1 key.</string>
+ <string name="keysExported">Successfully exported %s keys.</string>
+ <string name="noKeysExported">No keys exported.</string>
+ <string name="keyCreationElGamalInfo">Note: only subkeys support ElGamal, and for ElGamal the nearest keysize of 1536, 2048, 3072, 4096, or 8192 will be used.</string>
+ <string name="keyNotFound">Couldn\'t find key %08X.</string>
+ <string name="keysFound">Found %s key(s).</string>
+ <string name="unknownSignatureKeyTouchToLookUp">Unknown signature, touch to look up key.</string>
+ <string name="keyEditingIsBeta">Key editing is still kind of beta.</string>
+ <string name="badKeysEncountered">%s bad secret key(s) ignored. Perhaps you exported with the option\n --export-secret-subkeys\nMake sure you export with\n --export-secret-keys\ninstead.</string>
+ <string name="lookupUnknownKey">Unknown key %s, do you want to try finding it on a keyserver?</string>
+ <string name="keySendSuccess">Successfully sent key to server</string>
+ <string name="keySignSuccess">Successfully signed key</string>
+ <string name="qrScanImportSuccess">Successfully validated and imported key</string>
+
+ <!--
+ error_lowerCase: phrases, no punctuation, all lowercase,
+ they will be put after "errorMessage", e.g. "Error: file not found"
+ -->
+ <string name="error_fileDeleteFailed">deleting \'%s\' failed</string>
+ <string name="error_fileNotFound">file not found</string>
+ <string name="error_noSecretKeyFound">no suitable secret key found</string>
+ <string name="error_noKnownEncryptionFound">no known kind of encryption found</string>
+ <string name="error_externalStorageNotReady">external storage not ready</string>
+ <string name="error_accountNotFound">account \'%s\' not found</string>
+ <string name="error_accountReadingNotAllowed">no permission to read the account</string>
+ <string name="error_addingAccountFailed">adding account \'%s\' failed</string>
+ <string name="error_invalidEmail">invalid email \'%s\'</string>
+ <string name="error_keySizeMinimum512bit">key size must be at least 512bit</string>
+ <string name="error_masterKeyMustNotBeElGamal">the master key cannot be an ElGamal key</string>
+ <string name="error_unknownAlgorithmChoice">unknown algorithm choice</string>
+ <string name="error_userIdNeedsAName">you need to specify a name</string>
+ <string name="error_userIdNeedsAnEmailAddress">you need to specify an email address</string>
+ <string name="error_keyNeedsAUserId">need at least one user id</string>
+ <string name="error_mainUserIdMustNotBeEmpty">main user id must not be empty</string>
+ <string name="error_keyNeedsMasterKey">need at least a master key</string>
+ <string name="error_expiryMustComeAfterCreation">expiry date must come after creation date</string>
+ <string name="error_noEncryptionKeysOrPassPhrase">no encryption key(s) or pass phrase given</string>
+ <string name="error_signatureFailed">signature failed</string>
+ <string name="error_noSignaturePassPhrase">no pass phrase given</string>
+ <string name="error_noSignatureKey">no signature key given</string>
+ <string name="error_invalidData">not valid encryption data</string>
+ <string name="error_corruptData">corrupt data</string>
+ <string name="error_noSymmetricEncryptionPacket">couldn\'t find a packet with symmetric encryption</string>
+ <string name="error_wrongPassPhrase">wrong pass phrase</string>
+ <string name="error_savingKeys">error saving some key(s)</string>
+ <string name="error_couldNotExtractPrivateKey">could not extract private key</string>
+
+ <!-- progress_lowerCase: lowercase, phrases, usually ending in '...' -->
+ <string name="progress_done">done.</string>
+ <string name="progress_initializing">initializing...</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_buildingKey">building key...</string>
+ <string name="progress_preparingMasterKey">preparing master key...</string>
+ <string name="progress_certifyingMasterKey">certifying master key...</string>
+ <string name="progress_buildingMasterKeyRing">building master key ring...</string>
+ <string name="progress_addingSubKeys">adding sub keys...</string>
+ <string name="progress_savingKeyRing">saving key ring...</string>
+ <string name="progress_importingSecretKeys">importing secret keys...</string>
+ <string name="progress_importingPublicKeys">importing public keys...</string>
+ <string name="progress_reloadingKeys">reloading keys...</string>
+ <string name="progress_exportingKey">exporting key...</string>
+ <string name="progress_exportingKeys">exporting keys...</string>
+ <string name="progress_extractingSignatureKey">extracting signature key...</string>
+ <string name="progress_extractingKey">extracting key...</string>
+ <string name="progress_preparingStreams">preparing streams...</string>
+ <string name="progress_encrypting">encrypting data...</string>
+ <string name="progress_decrypting">decrypting data...</string>
+ <string name="progress_preparingSignature">preparing signature...</string>
+ <string name="progress_generatingSignature">generating signature...</string>
+ <string name="progress_processingSignature">processing signature...</string>
+ <string name="progress_verifyingSignature">verifying signature...</string>
+ <string name="progress_signing">signing...</string>
+ <string name="progress_readingData">reading data...</string>
+ <string name="progress_findingKey">finding key...</string>
+ <string name="progress_decompressingData">decompressing data...</string>
+ <string name="progress_verifyingIntegrity">verifying integrity...</string>
+ <string name="progress_deletingSecurely">deleting \'%s\' securely...</string>
+ <string name="progress_querying">querying...</string>
+ <string name="progress_queryingServer">querying %s...</string>
+
+ <!-- permission strings -->
+ <string name="permission_read_key_details_label">Read key details from APG.</string>
+ <string name="permission_read_key_details_description">Read key details of public and secret keys stored in APG, such as key ID and user IDs. The keys themselves can NOT be read.</string>
+ <string name="permission_store_blobs_label">Store blobs to en/decrypt with APG.</string>
+ <string name="permission_store_blobs_description">Store and read files on the android file system through APG. It cannot read files of other applications.</string>
+
+ <!-- action strings -->
+ <string name="action_encrypt">Encrypt</string>
+ <string name="action_decrypt">Decrypt</string>
+ <string name="action_importPublic">Import Public Keys</string>
+ <string name="action_importSecret">Import Secret Keys</string>
+ <string name="hint_publicKeys">Search Public Keys</string>
+ <string name="hint_secretKeys">Search Secret Keys</string>
+ <string name="filterInfo">Filter: \"%s\"</string>
+
+ <!-- misc -->
+ <string name="fast">fast</string>
+ <string name="slow">slow</string>
+ <string name="very_slow">very slow</string>
+
+ <!-- about -->
+ <string name="about_description">Android Privacy Guard (APG) is a OpenPGP implementation for Android.</string>
+ <string name="about_license">License: Apache License 2.0</string>
+ <string name="about_version">Version:</string>
+ <string name="about_developer">Developer: Thialfihar (Main developer), Senecaso (QRCode, send key, sign key), Markus Doits (AIDL), Oliver Runge, Dominik Schürmann</string>
+
+ <!-- texts -->
+ <!-- "OI File Manager", "ASTRO", and "K-9 Mail" must not be translated in order for the links to the market to work. -->
+ <string name="text_help">Install K-9 Mail for the best integration, it supports APG for PGP/INLINE and lets you directly encrypt/decrypt emails.
+\n\nIt is recommended that you install OI File Manager or ASTRO to be able to use the browse button for file selection in APG.
+\n\nFirst you need some keys. Either import them via the option menus in \"Manage Public Keys\" and \"Manage Secret Keys\" or create them in \"Manage Secret Keys\".
+\n\nYou can also add a GMail account in the main activity via \"Add Account\", which simplifies decrypting emails received there.
+\n\nCheck out the option menus in the various activities to find more functions.</string>
+
+</resources> \ No newline at end of file
diff --git a/org_apg/res/values/styles.xml b/org_apg/res/values/styles.xml
new file mode 100644
index 000000000..2a2a69341
--- /dev/null
+++ b/org_apg/res/values/styles.xml
@@ -0,0 +1,30 @@
+<?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.
+-->
+
+<resources>
+
+ <style name="MinusButton">
+ <item name="android:background">@drawable/btn_circle</item>
+ <item name="android:src">@drawable/ic_btn_round_minus</item>
+ </style>
+
+ <style name="PlusButton">
+ <item name="android:background">@drawable/btn_circle</item>
+ <item name="android:src">@drawable/ic_btn_round_plus</item>
+ </style>
+
+</resources> \ No newline at end of file
diff --git a/org_apg/res/xml/apg_preferences.xml b/org_apg/res/xml/apg_preferences.xml
new file mode 100644
index 000000000..7c0dd2206
--- /dev/null
+++ b/org_apg/res/xml/apg_preferences.xml
@@ -0,0 +1,70 @@
+<?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" >
+ <ListPreference
+ android:dialogTitle="@string/label_language"
+ android:entries="@array/language_entries"
+ android:entryValues="@array/language_values"
+ android:key="language"
+ android:title="@string/label_language" />
+
+ <org.apg.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_passPhraseCacheTtl" />
+
+ <PreferenceScreen
+ android:key="keyServers"
+ android:persistent="false"
+ android:title="@string/label_keyServers" />
+ </PreferenceCategory>
+ <PreferenceCategory android:title="@string/section_defaults" >
+ <org.apg.ui.widget.IntegerListPreference
+ android:key="defaultEncryptionAlgorithm"
+ android:persistent="false"
+ android:title="@string/label_encryptionAlgorithm" />
+ <org.apg.ui.widget.IntegerListPreference
+ android:key="defaultHashAlgorithm"
+ android:persistent="false"
+ android:title="@string/label_hashAlgorithm" />
+ <org.apg.ui.widget.IntegerListPreference
+ android:key="defaultMessageCompression"
+ android:persistent="false"
+ android:title="@string/label_messageCompression" />
+ <org.apg.ui.widget.IntegerListPreference
+ android:key="defaultFileCompression"
+ android:persistent="false"
+ android:title="@string/label_fileCompression" />
+
+ <CheckBoxPreference
+ android:key="defaultAsciiArmour"
+ android:persistent="false"
+ android:title="@string/label_asciiArmour" />
+ </PreferenceCategory>
+ <PreferenceCategory android:title="@string/section_advanced" >
+ <CheckBoxPreference
+ android:key="forceV3Signatures"
+ android:persistent="false"
+ android:title="@string/label_forceV3Signature" />
+ </PreferenceCategory>
+
+</PreferenceScreen> \ No newline at end of file
diff --git a/org_apg/res/xml/searchable_public_keys.xml b/org_apg/res/xml/searchable_public_keys.xml
new file mode 100644
index 000000000..e9602b121
--- /dev/null
+++ b/org_apg/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_publicKeys"
+ android:label="@string/app_name" >
+
+</searchable> \ No newline at end of file
diff --git a/org_apg/res/xml/searchable_secret_keys.xml b/org_apg/res/xml/searchable_secret_keys.xml
new file mode 100644
index 000000000..a7e8873d6
--- /dev/null
+++ b/org_apg/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_secretKeys"
+ android:label="@string/app_name" >
+
+</searchable> \ No newline at end of file
diff --git a/org_apg/src/org/apg/Apg.java b/org_apg/src/org/apg/Apg.java
new file mode 100644
index 000000000..2bfdb31e6
--- /dev/null
+++ b/org_apg/src/org/apg/Apg.java
@@ -0,0 +1,2283 @@
+/*
+ * 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.apg;
+
+import org.apg.KeyServer.AddKeyException;
+import org.apg.provider.DataProvider;
+import org.apg.provider.Database;
+import org.apg.provider.KeyRings;
+import org.apg.provider.Keys;
+import org.apg.provider.UserIds;
+import org.apg.ui.BaseActivity;
+import org.apg.ui.widget.KeyEditor;
+import org.apg.ui.widget.SectionView;
+import org.apg.ui.widget.UserIdEditor;
+import org.apg.util.IterableIterator;
+import org.spongycastle.bcpg.ArmoredInputStream;
+import org.spongycastle.bcpg.ArmoredOutputStream;
+import org.spongycastle.bcpg.BCPGOutputStream;
+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.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.PGPKeyPair;
+import org.spongycastle.openpgp.PGPKeyRing;
+import org.spongycastle.openpgp.PGPKeyRingGenerator;
+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.apg.R;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Message;
+import android.view.ViewGroup;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+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.Security;
+import java.security.SignatureException;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Vector;
+import java.util.regex.Pattern;
+
+public class Apg {
+ private static final String mApgPackageName = "org.thialfihar.android.apg";
+
+ public static class Intent {
+ public static final String DECRYPT = "org.thialfihar.android.apg.intent.DECRYPT";
+ public static final String ENCRYPT = "org.thialfihar.android.apg.intent.ENCRYPT";
+ public static final String DECRYPT_FILE = "org.thialfihar.android.apg.intent.DECRYPT_FILE";
+ public static final String ENCRYPT_FILE = "org.thialfihar.android.apg.intent.ENCRYPT_FILE";
+ public static final String DECRYPT_AND_RETURN = "org.thialfihar.android.apg.intent.DECRYPT_AND_RETURN";
+ public static final String ENCRYPT_AND_RETURN = "org.thialfihar.android.apg.intent.ENCRYPT_AND_RETURN";
+ public static final String SELECT_PUBLIC_KEYS = "org.thialfihar.android.apg.intent.SELECT_PUBLIC_KEYS";
+ public static final String SELECT_SECRET_KEY = "org.thialfihar.android.apg.intent.SELECT_SECRET_KEY";
+ public static final String IMPORT = "org.thialfihar.android.apg.intent.IMPORT";
+ public static final String LOOK_UP_KEY_ID = "org.thialfihar.android.apg.intent.LOOK_UP_KEY_ID";
+ public static final String LOOK_UP_KEY_ID_AND_RETURN = "org.thialfihar.android.apg.intent.LOOK_UP_KEY_ID_AND_RETURN";
+ public static final String GENERATE_SIGNATURE = "org.thialfihar.android.apg.intent.GENERATE_SIGNATURE";
+ public static final String EXPORT_KEY_TO_SERVER = "org.thialfihar.android.apg.intent.EXPORT_KEY_TO_SERVER";
+ public static final String IMPORT_FROM_QR_CODE = "org.thialfihar.android.apg.intent.IMPORT_FROM_QR_CODE";
+ }
+
+ public static final String EXTRA_TEXT = "text";
+ public static final String EXTRA_DATA = "data";
+ public static final String EXTRA_ERROR = "error";
+ public static final String EXTRA_DECRYPTED_MESSAGE = "decryptedMessage";
+ public static final String EXTRA_DECRYPTED_DATA = "decryptedData";
+ public static final String EXTRA_ENCRYPTED_MESSAGE = "encryptedMessage";
+ public static final String EXTRA_ENCRYPTED_DATA = "encryptedData";
+ public static final String EXTRA_RESULT_URI = "resultUri";
+ public static final String EXTRA_SIGNATURE = "signature";
+ public static final String EXTRA_SIGNATURE_KEY_ID = "signatureKeyId";
+ public static final String EXTRA_SIGNATURE_USER_ID = "signatureUserId";
+ public static final String EXTRA_SIGNATURE_SUCCESS = "signatureSuccess";
+ public static final String EXTRA_SIGNATURE_UNKNOWN = "signatureUnknown";
+ public static final String EXTRA_SIGNATURE_DATA = "signatureData";
+ public static final String EXTRA_SIGNATURE_TEXT = "signatureText";
+ public static final String EXTRA_USER_ID = "userId";
+ public static final String EXTRA_USER_IDS = "userIds";
+ public static final String EXTRA_KEY_ID = "keyId";
+ public static final String EXTRA_REPLY_TO = "replyTo";
+ public static final String EXTRA_SEND_TO = "sendTo";
+ public static final String EXTRA_SUBJECT = "subject";
+ public static final String EXTRA_ENCRYPTION_KEY_IDS = "encryptionKeyIds";
+ public static final String EXTRA_SELECTION = "selection";
+ public static final String EXTRA_ASCII_ARMOUR = "asciiArmour";
+ public static final String EXTRA_BINARY = "binary";
+ public static final String EXTRA_KEY_SERVERS = "keyServers";
+ public static final String EXTRA_EXPECTED_FINGERPRINT = "org.thialfihar.android.apg.EXPECTED_FINGERPRINT";
+
+ public static final String AUTHORITY = DataProvider.AUTHORITY;
+
+ public static final Uri CONTENT_URI_SECRET_KEY_RINGS = Uri.parse("content://" + AUTHORITY
+ + "/key_rings/secret/");
+ public static final Uri CONTENT_URI_SECRET_KEY_RING_BY_KEY_ID = Uri.parse("content://"
+ + AUTHORITY + "/key_rings/secret/key_id/");
+ public static final Uri CONTENT_URI_SECRET_KEY_RING_BY_EMAILS = Uri.parse("content://"
+ + AUTHORITY + "/key_rings/secret/emails/");
+
+ public static final Uri CONTENT_URI_PUBLIC_KEY_RINGS = Uri.parse("content://" + AUTHORITY
+ + "/key_rings/public/");
+ public static final Uri CONTENT_URI_PUBLIC_KEY_RING_BY_KEY_ID = Uri.parse("content://"
+ + AUTHORITY + "/key_rings/public/key_id/");
+ public static final Uri CONTENT_URI_PUBLIC_KEY_RING_BY_EMAILS = Uri.parse("content://"
+ + AUTHORITY + "/key_rings/public/emails/");
+
+ private static String VERSION = null;
+
+ 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 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);
+
+ private static HashMap<Long, CachedPassPhrase> mPassPhraseCache = new HashMap<Long, CachedPassPhrase>();
+ private static String mEditPassPhrase = null;
+
+ private static Database mDatabase = null;
+
+ public static class GeneralException extends Exception {
+ static final long serialVersionUID = 0xf812773342L;
+
+ public GeneralException(String message) {
+ super(message);
+ }
+ }
+
+ public static class NoAsymmetricEncryptionException extends Exception {
+ static final long serialVersionUID = 0xf812773343L;
+
+ public NoAsymmetricEncryptionException() {
+ super();
+ }
+ }
+
+ public static void initialize(Context context) {
+ if (mDatabase == null) {
+ mDatabase = new Database(context);
+ }
+ }
+
+ public static Database getDatabase() {
+ return mDatabase;
+ }
+
+ public static void setEditPassPhrase(String passPhrase) {
+ mEditPassPhrase = passPhrase;
+ }
+
+ public static String getEditPassPhrase() {
+ return mEditPassPhrase;
+ }
+
+ public static void setCachedPassPhrase(long keyId, String passPhrase) {
+ mPassPhraseCache.put(keyId, new CachedPassPhrase(new Date().getTime(), passPhrase));
+ }
+
+ public static String getCachedPassPhrase(long keyId) {
+ long realId = keyId;
+ if (realId != Id.key.symmetric) {
+ PGPSecretKeyRing keyRing = getSecretKeyRing(keyId);
+ if (keyRing == null) {
+ return null;
+ }
+ PGPSecretKey masterKey = getMasterKey(keyRing);
+ if (masterKey == null) {
+ return null;
+ }
+ realId = masterKey.getKeyID();
+ }
+ CachedPassPhrase cpp = mPassPhraseCache.get(realId);
+ if (cpp == null) {
+ return null;
+ }
+ // set it again to reset the cache life cycle
+ setCachedPassPhrase(realId, cpp.passPhrase);
+ return cpp.passPhrase;
+ }
+
+ public static int cleanUpCache(int ttl, int initialDelay) {
+ int delay = initialDelay;
+ long realTtl = ttl * 1000;
+ long now = new Date().getTime();
+ Vector<Long> oldKeys = new Vector<Long>();
+ for (Map.Entry<Long, CachedPassPhrase> pair : mPassPhraseCache.entrySet()) {
+ long lived = now - pair.getValue().timestamp;
+ if (lived >= realTtl) {
+ oldKeys.add(pair.getKey());
+ } else {
+ // see, whether the remaining time for this cache entry improves our
+ // check delay
+ long nextCheck = realTtl - lived + 1000;
+ if (nextCheck < delay) {
+ delay = (int) nextCheck;
+ }
+ }
+ }
+
+ for (long keyId : oldKeys) {
+ mPassPhraseCache.remove(keyId);
+ }
+
+ return delay;
+ }
+
+ public static PGPSecretKey createKey(Context context, int algorithmChoice, int keySize,
+ String passPhrase, PGPSecretKey masterKey) throws NoSuchAlgorithmException,
+ PGPException, NoSuchProviderException, GeneralException,
+ InvalidAlgorithmParameterException {
+
+ if (keySize < 512) {
+ throw new GeneralException(context.getString(R.string.error_keySizeMinimum512bit));
+ }
+
+ Security.addProvider(new BouncyCastleProvider());
+
+ if (passPhrase == null) {
+ passPhrase = "";
+ }
+
+ int algorithm = 0;
+ KeyPairGenerator keyGen = null;
+
+ switch (algorithmChoice) {
+ case Id.choice.algorithm.dsa: {
+ keyGen = KeyPairGenerator.getInstance("DSA", new BouncyCastleProvider());
+ keyGen.initialize(keySize, new SecureRandom());
+ algorithm = PGPPublicKey.DSA;
+ break;
+ }
+
+ case Id.choice.algorithm.elgamal: {
+ if (masterKey == null) {
+ throw new GeneralException(
+ context.getString(R.string.error_masterKeyMustNotBeElGamal));
+ }
+ keyGen = KeyPairGenerator.getInstance("ELGAMAL", new BouncyCastleProvider());
+ 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", new BouncyCastleProvider());
+ keyGen.initialize(keySize, new SecureRandom());
+
+ algorithm = PGPPublicKey.RSA_GENERAL;
+ break;
+ }
+
+ default: {
+ throw new GeneralException(context.getString(R.string.error_unknownAlgorithmChoice));
+ }
+ }
+
+ PGPKeyPair keyPair = new PGPKeyPair(algorithm, keyGen.generateKeyPair(), new Date());
+
+ PGPSecretKey secretKey = null;
+ if (masterKey == null) {
+ // enough for now, as we assemble the key again later anyway
+ secretKey = new PGPSecretKey(PGPSignature.DEFAULT_CERTIFICATION, keyPair, "",
+ PGPEncryptedData.CAST5, passPhrase.toCharArray(), null, null,
+ new SecureRandom(), new BouncyCastleProvider().getName());
+
+ } else {
+ PGPPublicKey tmpKey = masterKey.getPublicKey();
+ PGPPublicKey masterPublicKey = new PGPPublicKey(tmpKey.getAlgorithm(),
+ tmpKey.getKey(new BouncyCastleProvider()), tmpKey.getCreationTime());
+ PGPPrivateKey masterPrivateKey = masterKey.extractPrivateKey(passPhrase.toCharArray(),
+ new BouncyCastleProvider());
+
+ PGPKeyPair masterKeyPair = new PGPKeyPair(masterPublicKey, masterPrivateKey);
+ PGPKeyRingGenerator ringGen = new PGPKeyRingGenerator(
+ PGPSignature.POSITIVE_CERTIFICATION, masterKeyPair, "", PGPEncryptedData.CAST5,
+ passPhrase.toCharArray(), null, null, new SecureRandom(),
+ new BouncyCastleProvider().getName());
+ ringGen.addSubKey(keyPair);
+ PGPSecretKeyRing secKeyRing = ringGen.generateSecretKeyRing();
+ Iterator<PGPSecretKey> it = secKeyRing.getSecretKeys();
+ // first one is the master key
+ it.next();
+ secretKey = it.next();
+ }
+
+ return secretKey;
+ }
+
+ private 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;
+ }
+
+ public static void buildSecretKey(Activity context, SectionView userIdsView,
+ SectionView keysView, String oldPassPhrase, String newPassPhrase,
+ ProgressDialogUpdater progress) throws Apg.GeneralException, NoSuchProviderException,
+ PGPException, NoSuchAlgorithmException, SignatureException, IOException,
+ Database.GeneralException {
+
+ if (progress != null)
+ progress.setProgress(R.string.progress_buildingKey, 0, 100);
+
+ Security.addProvider(new BouncyCastleProvider());
+
+ if (oldPassPhrase == null || oldPassPhrase.equals("")) {
+ oldPassPhrase = "";
+ }
+
+ if (newPassPhrase == null || newPassPhrase.equals("")) {
+ newPassPhrase = "";
+ }
+
+ Vector<String> userIds = new Vector<String>();
+ Vector<PGPSecretKey> keys = new Vector<PGPSecretKey>();
+
+ ViewGroup userIdEditors = userIdsView.getEditors();
+ ViewGroup keyEditors = keysView.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 Apg.GeneralException(context.getString(R.string.error_userIdNeedsAName));
+ } catch (UserIdEditor.NoEmailException e) {
+ throw new Apg.GeneralException(
+ context.getString(R.string.error_userIdNeedsAnEmailAddress));
+ } catch (UserIdEditor.InvalidEmailException e) {
+ throw new Apg.GeneralException("" + e);
+ }
+
+ if (userId.equals("")) {
+ continue;
+ }
+
+ if (editor.isMainUserId()) {
+ userIds.insertElementAt(userId, 0);
+ gotMainUserId = true;
+ } else {
+ userIds.add(userId);
+ }
+ }
+
+ if (userIds.size() == 0) {
+ throw new Apg.GeneralException(context.getString(R.string.error_keyNeedsAUserId));
+ }
+
+ if (!gotMainUserId) {
+ throw new Apg.GeneralException(
+ context.getString(R.string.error_mainUserIdMustNotBeEmpty));
+ }
+
+ if (keyEditors.getChildCount() == 0) {
+ throw new Apg.GeneralException(context.getString(R.string.error_keyNeedsMasterKey));
+ }
+
+ for (int i = 0; i < keyEditors.getChildCount(); ++i) {
+ KeyEditor editor = (KeyEditor) keyEditors.getChildAt(i);
+ keys.add(editor.getValue());
+ }
+
+ if (progress != null)
+ progress.setProgress(R.string.progress_preparingMasterKey, 10, 100);
+ KeyEditor keyEditor = (KeyEditor) keyEditors.getChildAt(0);
+ int usageId = keyEditor.getUsage();
+ 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);
+ PGPPublicKey tmpKey = masterKey.getPublicKey();
+ PGPPublicKey masterPublicKey = new PGPPublicKey(tmpKey.getAlgorithm(),
+ tmpKey.getKey(new BouncyCastleProvider()), tmpKey.getCreationTime());
+ PGPPrivateKey masterPrivateKey = masterKey.extractPrivateKey(oldPassPhrase.toCharArray(),
+ new BouncyCastleProvider());
+
+ if (progress != null)
+ progress.setProgress(R.string.progress_certifyingMasterKey, 20, 100);
+ for (int i = 0; i < userIds.size(); ++i) {
+ String userId = userIds.get(i);
+
+ PGPSignatureGenerator sGen = new PGPSignatureGenerator(masterPublicKey.getAlgorithm(),
+ HashAlgorithmTags.SHA1, new BouncyCastleProvider());
+
+ sGen.initSign(PGPSignature.POSITIVE_CERTIFICATION, masterPrivateKey);
+
+ PGPSignature certification = sGen.generateCertification(userId, masterPublicKey);
+
+ masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, userId, certification);
+ }
+
+ // TODO: cross-certify the master key with every sub key
+
+ 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
+ if (keyEditor.getExpiryDate() != null) {
+ GregorianCalendar creationDate = new GregorianCalendar();
+ creationDate.setTime(getCreationDate(masterKey));
+ GregorianCalendar expiryDate = keyEditor.getExpiryDate();
+ long numDays = getNumDaysBetween(creationDate, expiryDate);
+ if (numDays <= 0) {
+ throw new GeneralException(
+ context.getString(R.string.error_expiryMustComeAfterCreation));
+ }
+ hashedPacketsGen.setKeyExpirationTime(true, numDays * 86400);
+ }
+
+ if (progress != null)
+ progress.setProgress(R.string.progress_buildingMasterKeyRing, 30, 100);
+ PGPKeyRingGenerator keyGen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION,
+ masterKeyPair, mainUserId, PGPEncryptedData.CAST5, newPassPhrase.toCharArray(),
+ hashedPacketsGen.generate(), unhashedPacketsGen.generate(), new SecureRandom(),
+ new BouncyCastleProvider().getName());
+
+ if (progress != null)
+ progress.setProgress(R.string.progress_addingSubKeys, 40, 100);
+ for (int i = 1; i < keys.size(); ++i) {
+ if (progress != null)
+ progress.setProgress(40 + 50 * (i - 1) / (keys.size() - 1), 100);
+ PGPSecretKey subKey = keys.get(i);
+ keyEditor = (KeyEditor) keyEditors.getChildAt(i);
+ PGPPublicKey subPublicKey = subKey.getPublicKey();
+ PGPPrivateKey subPrivateKey = subKey.extractPrivateKey(oldPassPhrase.toCharArray(),
+ new BouncyCastleProvider());
+ PGPKeyPair subKeyPair = new PGPKeyPair(subPublicKey.getAlgorithm(),
+ subPublicKey.getKey(new BouncyCastleProvider()), subPrivateKey.getKey(),
+ subPublicKey.getCreationTime());
+
+ hashedPacketsGen = new PGPSignatureSubpacketGenerator();
+ unhashedPacketsGen = new PGPSignatureSubpacketGenerator();
+
+ keyFlags = 0;
+ usageId = keyEditor.getUsage();
+ 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) {
+ keyFlags |= KeyFlags.SIGN_DATA;
+ }
+ if (canEncrypt) {
+ keyFlags |= KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE;
+ }
+ hashedPacketsGen.setKeyFlags(true, keyFlags);
+
+ // TODO: this doesn't work quite right yet
+ if (keyEditor.getExpiryDate() != null) {
+ GregorianCalendar creationDate = new GregorianCalendar();
+ creationDate.setTime(getCreationDate(masterKey));
+ GregorianCalendar expiryDate = keyEditor.getExpiryDate();
+ long numDays = 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();
+
+ if (progress != null)
+ progress.setProgress(R.string.progress_savingKeyRing, 90, 100);
+ mDatabase.saveKeyRing(secretKeyRing);
+ mDatabase.saveKeyRing(publicKeyRing);
+
+ if (progress != null)
+ progress.setProgress(R.string.progress_done, 100, 100);
+ }
+
+ public static PGPKeyRing decodeKeyRing(InputStream is) throws IOException {
+ InputStream in = PGPUtil.getDecoderStream(is);
+ PGPObjectFactory objectFactory = new PGPObjectFactory(in);
+ Object obj = objectFactory.nextObject();
+
+ if (obj instanceof PGPKeyRing) {
+ return (PGPKeyRing) obj;
+ }
+
+ return null;
+ }
+
+ public static int storeKeyRingInCache(PGPKeyRing keyring) {
+ int status = Integer.MIN_VALUE; // out of bounds value (Id.retrun_value.*)
+ try {
+ if (keyring instanceof PGPSecretKeyRing) {
+ PGPSecretKeyRing secretKeyRing = (PGPSecretKeyRing) keyring;
+ boolean save = true;
+ try {
+ PGPPrivateKey testKey = secretKeyRing.getSecretKey().extractPrivateKey(
+ new char[] {}, new BouncyCastleProvider());
+ if (testKey == null) {
+ // this is bad, something is very wrong... likely a --export-secret-subkeys
+ // export
+ save = false;
+ status = Id.return_value.bad;
+ }
+ } catch (PGPException e) {
+ // all good if this fails, we likely didn't use the right password
+ }
+
+ if (save) {
+ status = mDatabase.saveKeyRing(secretKeyRing);
+ }
+ } else if (keyring instanceof PGPPublicKeyRing) {
+ PGPPublicKeyRing publicKeyRing = (PGPPublicKeyRing) keyring;
+ status = mDatabase.saveKeyRing(publicKeyRing);
+ }
+ } catch (IOException e) {
+ status = Id.return_value.error;
+ } catch (Database.GeneralException e) {
+ status = Id.return_value.error;
+ }
+
+ return status;
+ }
+
+ public static 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) {
+ }
+ }
+ }
+
+ public static Bundle importKeyRings(Activity context, int type, InputData data,
+ ProgressDialogUpdater progress) throws GeneralException, FileNotFoundException,
+ PGPException, IOException {
+ Bundle returnData = new Bundle();
+
+ if (type == Id.type.secret_key) {
+ if (progress != null)
+ progress.setProgress(R.string.progress_importingSecretKeys, 0, 100);
+ } else {
+ if (progress != null)
+ progress.setProgress(R.string.progress_importingPublicKeys, 0, 100);
+ }
+
+ if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+ throw new GeneralException(context.getString(R.string.error_externalStorageNotReady));
+ }
+
+ 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 {
+ PGPKeyRing keyring = decodeKeyRing(bufferedInput);
+ while (keyring != null) {
+ int status = Integer.MIN_VALUE; // out of bounds value
+
+ // if this key is what we expect it to be, save it
+ if ((type == Id.type.secret_key && keyring instanceof PGPSecretKeyRing)
+ || (type == Id.type.public_key && keyring instanceof PGPPublicKeyRing)) {
+ status = storeKeyRingInCache(keyring);
+ }
+
+ if (status == Id.return_value.error) {
+ throw new GeneralException(context.getString(R.string.error_savingKeys));
+ }
+
+ // 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;
+ }
+
+ if (progress != null) {
+ progress.setProgress((int) (100 * progressIn.position() / data.getSize()), 100);
+ }
+ // TODO: needed?
+ // obj = objectFactory.nextObject();
+
+ keyring = decodeKeyRing(bufferedInput);
+ }
+ } catch (EOFException e) {
+ // nothing to do, we are done
+ }
+
+ returnData.putInt("added", newKeys);
+ returnData.putInt("updated", oldKeys);
+ returnData.putInt("bad", badKeys);
+
+ if (progress != null)
+ progress.setProgress(R.string.progress_done, 100, 100);
+
+ return returnData;
+ }
+
+ public static Bundle exportKeyRings(Activity context, Vector<Integer> keyRingIds,
+ OutputStream outStream, ProgressDialogUpdater progress) throws GeneralException,
+ FileNotFoundException, PGPException, IOException {
+ Bundle returnData = new Bundle();
+
+ if (keyRingIds.size() == 1) {
+ if (progress != null)
+ progress.setProgress(R.string.progress_exportingKey, 0, 100);
+ } else {
+ if (progress != null)
+ progress.setProgress(R.string.progress_exportingKeys, 0, 100);
+ }
+
+ if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+ throw new GeneralException(context.getString(R.string.error_externalStorageNotReady));
+ }
+ ArmoredOutputStream out = new ArmoredOutputStream(outStream);
+
+ int numKeys = 0;
+ for (int i = 0; i < keyRingIds.size(); ++i) {
+ if (progress != null)
+ progress.setProgress(i * 100 / keyRingIds.size(), 100);
+ Object obj = mDatabase.getKeyRing(keyRingIds.get(i));
+ PGPPublicKeyRing publicKeyRing;
+ PGPSecretKeyRing secretKeyRing;
+
+ if (obj instanceof PGPSecretKeyRing) {
+ secretKeyRing = (PGPSecretKeyRing) obj;
+ secretKeyRing.encode(out);
+ } else if (obj instanceof PGPPublicKeyRing) {
+ publicKeyRing = (PGPPublicKeyRing) obj;
+ publicKeyRing.encode(out);
+ } else {
+ continue;
+ }
+ ++numKeys;
+ }
+ out.close();
+ returnData.putInt("exported", numKeys);
+
+ if (progress != null)
+ progress.setProgress(R.string.progress_done, 100, 100);
+
+ return returnData;
+ }
+
+ 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 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;
+ }
+
+ 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)) {
+ 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> 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(long masterKeyId) {
+ PGPPublicKeyRing keyRing = getPublicKeyRing(masterKeyId);
+ if (keyRing == null) {
+ return null;
+ }
+ Vector<PGPPublicKey> encryptKeys = getUsableEncryptKeys(keyRing);
+ if (encryptKeys.size() == 0) {
+ return null;
+ }
+ return encryptKeys.get(0);
+ }
+
+ public static PGPSecretKey getSigningKey(long masterKeyId) {
+ PGPSecretKeyRing keyRing = getSecretKeyRing(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 = context.getResources().getString(R.string.unknownUserId);
+ }
+ return userId;
+ }
+
+ public static String getMainUserIdSafe(Context context, PGPSecretKey key) {
+ String userId = getMainUserId(key);
+ if (userId == null) {
+ userId = context.getResources().getString(R.string.unknownUserId);
+ }
+ 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());
+ }
+
+ 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 = "???";
+ break;
+ }
+ }
+ return algorithmStr + ", " + keySize + "bit";
+ }
+
+ public static String convertToHex(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();
+ while (chunk.length() < 2) {
+ chunk = "0" + chunk;
+ }
+ fingerPrint += chunk;
+ }
+
+ return fingerPrint;
+
+ }
+
+ public static String getFingerPrint(long keyId) {
+ PGPPublicKey key = Apg.getPublicKey(keyId);
+ if (key == null) {
+ PGPSecretKey secretKey = Apg.getSecretKey(keyId);
+ if (secretKey == null) {
+ return "";
+ }
+ key = secretKey.getPublicKey();
+ }
+
+ return convertToHex(key.getFingerprint());
+ }
+
+ public static String getSmallFingerPrint(long keyId) {
+ String fingerPrint = Long.toHexString(keyId & 0xffffffffL).toUpperCase();
+ while (fingerPrint.length() < 8) {
+ fingerPrint = "0" + fingerPrint;
+ }
+ return fingerPrint;
+ }
+
+ public static String keyToHex(long keyId) {
+ return getSmallFingerPrint(keyId >> 32) + getSmallFingerPrint(keyId);
+ }
+
+ public static long keyFromHex(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);
+ }
+
+ public static void deleteKey(int keyRingId) {
+ mDatabase.deleteKeyRing(keyRingId);
+ }
+
+ public static PGPKeyRing getKeyRing(int keyRingId) {
+ return (PGPKeyRing) mDatabase.getKeyRing(keyRingId);
+ }
+
+ public static PGPSecretKeyRing getSecretKeyRing(long keyId) {
+ byte[] data = mDatabase.getKeyRingDataFromKeyId(Id.database.type_secret, keyId);
+ if (data == null) {
+ return null;
+ }
+ try {
+ return new PGPSecretKeyRing(data);
+ } catch (IOException e) {
+ // no good way to handle this, return null
+ // TODO: some info?
+ } catch (PGPException e) {
+ // no good way to handle this, return null
+ // TODO: some info?
+ }
+ return null;
+ }
+
+ public static PGPPublicKeyRing getPublicKeyRing(long keyId) {
+ byte[] data = mDatabase.getKeyRingDataFromKeyId(Id.database.type_public, keyId);
+ if (data == null) {
+ return null;
+ }
+ try {
+ return new PGPPublicKeyRing(data);
+ } catch (IOException e) {
+ // no good way to handle this, return null
+ // TODO: some info?
+ }
+ return null;
+ }
+
+ public static PGPSecretKey getSecretKey(long keyId) {
+ PGPSecretKeyRing keyRing = getSecretKeyRing(keyId);
+ if (keyRing == null) {
+ return null;
+ }
+ return keyRing.getSecretKey(keyId);
+ }
+
+ public static PGPPublicKey getPublicKey(long keyId) {
+ PGPPublicKeyRing keyRing = getPublicKeyRing(keyId);
+ if (keyRing == null) {
+ return null;
+ }
+
+ return keyRing.getPublicKey(keyId);
+ }
+
+ public static Vector<Integer> getKeyRingIds(int type) {
+ SQLiteDatabase db = mDatabase.db();
+ Vector<Integer> keyIds = new Vector<Integer>();
+ Cursor c = db.query(KeyRings.TABLE_NAME, new String[] { KeyRings._ID }, KeyRings.TYPE
+ + " = ?", new String[] { "" + type }, null, null, null);
+ if (c != null && c.moveToFirst()) {
+ do {
+ keyIds.add(c.getInt(0));
+ } while (c.moveToNext());
+ }
+
+ if (c != null) {
+ c.close();
+ }
+
+ return keyIds;
+ }
+
+ public static String getMainUserId(long keyId, int type) {
+ SQLiteDatabase db = mDatabase.db();
+ Cursor c = db.query(Keys.TABLE_NAME + " INNER JOIN " + KeyRings.TABLE_NAME + " ON ("
+ + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " + Keys.TABLE_NAME + "."
+ + Keys.KEY_RING_ID + ") " + " INNER JOIN " + Keys.TABLE_NAME + " AS masterKey ON ("
+ + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " + "masterKey."
+ + Keys.KEY_RING_ID + " AND " + "masterKey." + Keys.IS_MASTER_KEY + " = '1') "
+ + " INNER JOIN " + UserIds.TABLE_NAME + " ON (" + UserIds.TABLE_NAME + "."
+ + UserIds.KEY_ID + " = " + "masterKey." + Keys._ID + " AND " + UserIds.TABLE_NAME
+ + "." + UserIds.RANK + " = '0')", new String[] { UserIds.USER_ID }, Keys.TABLE_NAME
+ + "." + Keys.KEY_ID + " = ? AND " + KeyRings.TABLE_NAME + "." + KeyRings.TYPE
+ + " = ?", new String[] { "" + keyId, "" + type, }, null, null, null);
+ String userId = "";
+ if (c != null && c.moveToFirst()) {
+ do {
+ userId = c.getString(0);
+ } while (c.moveToNext());
+ }
+
+ if (c != null) {
+ c.close();
+ }
+
+ return userId;
+ }
+
+ public static void encrypt(Context context, InputData data, OutputStream outStream,
+ boolean armored, long encryptionKeyIds[], long signatureKeyId,
+ String signaturePassPhrase, ProgressDialogUpdater progress, int symmetricAlgorithm,
+ int hashAlgorithm, int compression, boolean forceV3Signature, String passPhrase)
+ throws IOException, GeneralException, PGPException, NoSuchProviderException,
+ NoSuchAlgorithmException, SignatureException {
+ Security.addProvider(new BouncyCastleProvider());
+
+ if (encryptionKeyIds == null) {
+ encryptionKeyIds = new long[0];
+ }
+
+ ArmoredOutputStream armorOut = null;
+ OutputStream out = null;
+ OutputStream encryptOut = null;
+ if (armored) {
+ armorOut = new ArmoredOutputStream(outStream);
+ armorOut.setHeader("Version", getFullVersion(context));
+ out = armorOut;
+ } else {
+ out = outStream;
+ }
+ PGPSecretKey signingKey = null;
+ PGPSecretKeyRing signingKeyRing = null;
+ PGPPrivateKey signaturePrivateKey = null;
+
+ if (encryptionKeyIds.length == 0 && passPhrase == null) {
+ throw new GeneralException(
+ context.getString(R.string.error_noEncryptionKeysOrPassPhrase));
+ }
+
+ if (signatureKeyId != 0) {
+ signingKeyRing = getSecretKeyRing(signatureKeyId);
+ signingKey = getSigningKey(signatureKeyId);
+ if (signingKey == null) {
+ throw new GeneralException(context.getString(R.string.error_signatureFailed));
+ }
+
+ if (signaturePassPhrase == null) {
+ throw new GeneralException(context.getString(R.string.error_noSignaturePassPhrase));
+ }
+ if (progress != null)
+ progress.setProgress(R.string.progress_extractingSignatureKey, 0, 100);
+ signaturePrivateKey = signingKey.extractPrivateKey(signaturePassPhrase.toCharArray(),
+ new BouncyCastleProvider());
+ if (signaturePrivateKey == null) {
+ throw new GeneralException(
+ context.getString(R.string.error_couldNotExtractPrivateKey));
+ }
+ }
+ if (progress != null)
+ progress.setProgress(R.string.progress_preparingStreams, 5, 100);
+
+ // encrypt and compress input file content
+ PGPEncryptedDataGenerator cPk = new PGPEncryptedDataGenerator(symmetricAlgorithm, true,
+ new SecureRandom(), new BouncyCastleProvider());
+
+ if (encryptionKeyIds.length == 0) {
+ // symmetric encryption
+ cPk.addMethod(passPhrase.toCharArray());
+ }
+ for (int i = 0; i < encryptionKeyIds.length; ++i) {
+ PGPPublicKey key = getEncryptPublicKey(encryptionKeyIds[i]);
+ if (key != null) {
+ cPk.addMethod(key);
+ }
+ }
+ encryptOut = cPk.open(out, new byte[1 << 16]);
+
+ PGPSignatureGenerator signatureGenerator = null;
+ PGPV3SignatureGenerator signatureV3Generator = null;
+
+ if (signatureKeyId != 0) {
+ if (progress != null)
+ progress.setProgress(R.string.progress_preparingSignature, 10, 100);
+ if (forceV3Signature) {
+ signatureV3Generator = new PGPV3SignatureGenerator(signingKey.getPublicKey()
+ .getAlgorithm(), hashAlgorithm, new BouncyCastleProvider());
+ signatureV3Generator.initSign(PGPSignature.BINARY_DOCUMENT, signaturePrivateKey);
+ } else {
+ signatureGenerator = new PGPSignatureGenerator(signingKey.getPublicKey()
+ .getAlgorithm(), hashAlgorithm, new BouncyCastleProvider());
+ signatureGenerator.initSign(PGPSignature.BINARY_DOCUMENT, signaturePrivateKey);
+
+ String userId = getMainUserId(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 != 0) {
+ if (forceV3Signature) {
+ 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]);
+ if (progress != null)
+ progress.setProgress(R.string.progress_encrypting, 20, 100);
+
+ long done = 0;
+ int n = 0;
+ byte[] buffer = new byte[1 << 16];
+ InputStream in = data.getInputStream();
+ while ((n = in.read(buffer)) > 0) {
+ pOut.write(buffer, 0, n);
+ if (signatureKeyId != 0) {
+ if (forceV3Signature) {
+ signatureV3Generator.update(buffer, 0, n);
+ } else {
+ signatureGenerator.update(buffer, 0, n);
+ }
+ }
+ done += n;
+ if (data.getSize() != 0) {
+ if (progress != null)
+ progress.setProgress((int) (20 + (95 - 20) * done / data.getSize()), 100);
+ }
+ }
+
+ literalGen.close();
+
+ if (signatureKeyId != 0) {
+ if (progress != null)
+ progress.setProgress(R.string.progress_generatingSignature, 95, 100);
+ if (forceV3Signature) {
+ signatureV3Generator.generate().encode(pOut);
+ } else {
+ signatureGenerator.generate().encode(pOut);
+ }
+ }
+ if (compressGen != null) {
+ compressGen.close();
+ }
+ encryptOut.close();
+ if (armored) {
+ armorOut.close();
+ }
+
+ if (progress != null)
+ progress.setProgress(R.string.progress_done, 100, 100);
+ }
+
+ public static void signText(Context context, InputData data, OutputStream outStream,
+ long signatureKeyId, String signaturePassPhrase, int hashAlgorithm,
+ boolean forceV3Signature, ProgressDialogUpdater progress) throws GeneralException,
+ PGPException, IOException, NoSuchAlgorithmException, SignatureException {
+ Security.addProvider(new BouncyCastleProvider());
+
+ ArmoredOutputStream armorOut = new ArmoredOutputStream(outStream);
+ armorOut.setHeader("Version", getFullVersion(context));
+
+ PGPSecretKey signingKey = null;
+ PGPSecretKeyRing signingKeyRing = null;
+ PGPPrivateKey signaturePrivateKey = null;
+
+ if (signatureKeyId == 0) {
+ throw new GeneralException(context.getString(R.string.error_noSignatureKey));
+ }
+
+ signingKeyRing = getSecretKeyRing(signatureKeyId);
+ signingKey = getSigningKey(signatureKeyId);
+ if (signingKey == null) {
+ throw new GeneralException(context.getString(R.string.error_signatureFailed));
+ }
+
+ if (signaturePassPhrase == null) {
+ throw new GeneralException(context.getString(R.string.error_noSignaturePassPhrase));
+ }
+ signaturePrivateKey = signingKey.extractPrivateKey(signaturePassPhrase.toCharArray(),
+ new BouncyCastleProvider());
+ if (signaturePrivateKey == null) {
+ throw new GeneralException(context.getString(R.string.error_couldNotExtractPrivateKey));
+ }
+ if (progress != null)
+ progress.setProgress(R.string.progress_preparingStreams, 0, 100);
+
+ if (progress != null)
+ progress.setProgress(R.string.progress_preparingSignature, 30, 100);
+
+ PGPSignatureGenerator signatureGenerator = null;
+ PGPV3SignatureGenerator signatureV3Generator = null;
+
+ if (forceV3Signature) {
+ signatureV3Generator = new PGPV3SignatureGenerator(signingKey.getPublicKey()
+ .getAlgorithm(), hashAlgorithm, new BouncyCastleProvider());
+ signatureV3Generator
+ .initSign(PGPSignature.CANONICAL_TEXT_DOCUMENT, signaturePrivateKey);
+ } else {
+ signatureGenerator = new PGPSignatureGenerator(
+ signingKey.getPublicKey().getAlgorithm(), hashAlgorithm,
+ new BouncyCastleProvider());
+ signatureGenerator.initSign(PGPSignature.CANONICAL_TEXT_DOCUMENT, signaturePrivateKey);
+
+ PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
+ String userId = getMainUserId(getMasterKey(signingKeyRing));
+ spGen.setSignerUserID(false, userId);
+ signatureGenerator.setHashedSubpackets(spGen.generate());
+ }
+
+ if (progress != null)
+ progress.setProgress(R.string.progress_signing, 40, 100);
+
+ armorOut.beginClearText(hashAlgorithm);
+
+ InputStream inStream = data.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();
+
+ if (progress != null)
+ progress.setProgress(R.string.progress_done, 100, 100);
+ }
+
+ public static void generateSignature(Context context, InputData data, OutputStream outStream,
+ boolean armored, boolean binary, long signatureKeyId, String signaturePassPhrase,
+ int hashAlgorithm, boolean forceV3Signature, ProgressDialogUpdater progress)
+ throws GeneralException, PGPException, IOException, NoSuchAlgorithmException,
+ SignatureException {
+ Security.addProvider(new BouncyCastleProvider());
+
+ ArmoredOutputStream armorOut = null;
+ OutputStream out = null;
+ if (armored) {
+ armorOut = new ArmoredOutputStream(outStream);
+ armorOut.setHeader("Version", getFullVersion(context));
+ out = armorOut;
+ } else {
+ out = outStream;
+ }
+
+ PGPSecretKey signingKey = null;
+ PGPSecretKeyRing signingKeyRing = null;
+ PGPPrivateKey signaturePrivateKey = null;
+
+ if (signatureKeyId == 0) {
+ throw new GeneralException(context.getString(R.string.error_noSignatureKey));
+ }
+
+ signingKeyRing = getSecretKeyRing(signatureKeyId);
+ signingKey = getSigningKey(signatureKeyId);
+ if (signingKey == null) {
+ throw new GeneralException(context.getString(R.string.error_signatureFailed));
+ }
+
+ if (signaturePassPhrase == null) {
+ throw new GeneralException(context.getString(R.string.error_noSignaturePassPhrase));
+ }
+ signaturePrivateKey = signingKey.extractPrivateKey(signaturePassPhrase.toCharArray(),
+ new BouncyCastleProvider());
+ if (signaturePrivateKey == null) {
+ throw new GeneralException(context.getString(R.string.error_couldNotExtractPrivateKey));
+ }
+ if (progress != null)
+ progress.setProgress(R.string.progress_preparingStreams, 0, 100);
+
+ if (progress != null)
+ progress.setProgress(R.string.progress_preparingSignature, 30, 100);
+
+ PGPSignatureGenerator signatureGenerator = null;
+ PGPV3SignatureGenerator signatureV3Generator = null;
+
+ int type = PGPSignature.CANONICAL_TEXT_DOCUMENT;
+ if (binary) {
+ type = PGPSignature.BINARY_DOCUMENT;
+ }
+
+ if (forceV3Signature) {
+ signatureV3Generator = new PGPV3SignatureGenerator(signingKey.getPublicKey()
+ .getAlgorithm(), hashAlgorithm, new BouncyCastleProvider());
+ signatureV3Generator.initSign(type, signaturePrivateKey);
+ } else {
+ signatureGenerator = new PGPSignatureGenerator(
+ signingKey.getPublicKey().getAlgorithm(), hashAlgorithm,
+ new BouncyCastleProvider());
+ signatureGenerator.initSign(type, signaturePrivateKey);
+
+ PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
+ String userId = getMainUserId(getMasterKey(signingKeyRing));
+ spGen.setSignerUserID(false, userId);
+ signatureGenerator.setHashedSubpackets(spGen.generate());
+ }
+
+ if (progress != null)
+ progress.setProgress(R.string.progress_signing, 40, 100);
+
+ InputStream inStream = data.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();
+ outStream.close();
+
+ if (progress != null)
+ progress.setProgress(R.string.progress_done, 100, 100);
+ }
+
+ public static long getDecryptionKeyId(Context context, InputData data) throws GeneralException,
+ NoAsymmetricEncryptionException, IOException {
+ InputStream in = PGPUtil.getDecoderStream(data.getInputStream());
+ 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 GeneralException(context.getString(R.string.error_invalidData));
+ }
+
+ // 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 = getSecretKey(pbe.getKeyID());
+ if (secretKey != null) {
+ break;
+ }
+ }
+ }
+
+ if (!gotAsymmetricEncryption) {
+ throw new NoAsymmetricEncryptionException();
+ }
+
+ if (secretKey == null) {
+ return Id.key.none;
+ }
+
+ return secretKey.getKeyID();
+ }
+
+ public static boolean hasSymmetricEncryption(Context context, InputData data)
+ throws GeneralException, IOException {
+ InputStream in = PGPUtil.getDecoderStream(data.getInputStream());
+ 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 GeneralException(context.getString(R.string.error_invalidData));
+ }
+
+ Iterator<?> it = enc.getEncryptedDataObjects();
+ while (it.hasNext()) {
+ Object obj = it.next();
+ if (obj instanceof PGPPBEEncryptedData) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public static Bundle decrypt(Context context, InputData data, OutputStream outStream,
+ String passPhrase, ProgressDialogUpdater progress, boolean assumeSymmetric)
+ throws IOException, GeneralException, PGPException, SignatureException {
+ if (passPhrase == null) {
+ passPhrase = "";
+ }
+ Bundle returnData = new Bundle();
+ InputStream in = PGPUtil.getDecoderStream(data.getInputStream());
+ PGPObjectFactory pgpF = new PGPObjectFactory(in);
+ PGPEncryptedDataList enc;
+ Object o = pgpF.nextObject();
+ long signatureKeyId = 0;
+
+ int currentProgress = 0;
+ if (progress != null)
+ progress.setProgress(R.string.progress_readingData, currentProgress, 100);
+
+ if (o instanceof PGPEncryptedDataList) {
+ enc = (PGPEncryptedDataList) o;
+ } else {
+ enc = (PGPEncryptedDataList) pgpF.nextObject();
+ }
+
+ if (enc == null) {
+ throw new GeneralException(context.getString(R.string.error_invalidData));
+ }
+
+ 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 GeneralException(
+ context.getString(R.string.error_noSymmetricEncryptionPacket));
+ }
+
+ if (progress != null)
+ progress.setProgress(R.string.progress_preparingStreams, currentProgress, 100);
+ clear = pbe.getDataStream(passPhrase.toCharArray(), new BouncyCastleProvider());
+ encryptedData = pbe;
+ currentProgress += 5;
+ } else {
+ if (progress != null)
+ progress.setProgress(R.string.progress_findingKey, 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 = getSecretKey(encData.getKeyID());
+ if (secretKey != null) {
+ pbe = encData;
+ break;
+ }
+ }
+ }
+
+ if (secretKey == null) {
+ throw new GeneralException(context.getString(R.string.error_noSecretKeyFound));
+ }
+
+ currentProgress += 5;
+ if (progress != null)
+ progress.setProgress(R.string.progress_extractingKey, currentProgress, 100);
+ PGPPrivateKey privateKey = null;
+ try {
+ privateKey = secretKey.extractPrivateKey(passPhrase.toCharArray(),
+ new BouncyCastleProvider());
+ } catch (PGPException e) {
+ throw new PGPException(context.getString(R.string.error_wrongPassPhrase));
+ }
+ if (privateKey == null) {
+ throw new GeneralException(
+ context.getString(R.string.error_couldNotExtractPrivateKey));
+ }
+ currentProgress += 5;
+ if (progress != null)
+ progress.setProgress(R.string.progress_preparingStreams, currentProgress, 100);
+ clear = pbe.getDataStream(privateKey, new BouncyCastleProvider());
+ 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) {
+ if (progress != null)
+ progress.setProgress(R.string.progress_decompressingData, currentProgress, 100);
+ PGPObjectFactory fact = new PGPObjectFactory(
+ ((PGPCompressedData) dataChunk).getDataStream());
+ dataChunk = fact.nextObject();
+ plainFact = fact;
+ currentProgress += 10;
+ }
+
+ if (dataChunk instanceof PGPOnePassSignatureList) {
+ if (progress != null)
+ progress.setProgress(R.string.progress_processingSignature, currentProgress, 100);
+ returnData.putBoolean(EXTRA_SIGNATURE, true);
+ PGPOnePassSignatureList sigList = (PGPOnePassSignatureList) dataChunk;
+ for (int i = 0; i < sigList.size(); ++i) {
+ signature = sigList.get(i);
+ signatureKey = getPublicKey(signature.getKeyID());
+ if (signatureKeyId == 0) {
+ signatureKeyId = signature.getKeyID();
+ }
+ if (signatureKey == null) {
+ signature = null;
+ } else {
+ signatureIndex = i;
+ signatureKeyId = signature.getKeyID();
+ String userId = null;
+ PGPPublicKeyRing sigKeyRing = getPublicKeyRing(signatureKeyId);
+ if (sigKeyRing != null) {
+ userId = getMainUserId(getMasterKey(sigKeyRing));
+ }
+ returnData.putString(EXTRA_SIGNATURE_USER_ID, userId);
+ break;
+ }
+ }
+
+ returnData.putLong(EXTRA_SIGNATURE_KEY_ID, signatureKeyId);
+
+ if (signature != null) {
+ signature.initVerify(signatureKey, new BouncyCastleProvider());
+ } else {
+ returnData.putBoolean(EXTRA_SIGNATURE_UNKNOWN, true);
+ }
+
+ dataChunk = plainFact.nextObject();
+ currentProgress += 10;
+ }
+
+ if (dataChunk instanceof PGPSignatureList) {
+ dataChunk = plainFact.nextObject();
+ }
+
+ if (dataChunk instanceof PGPLiteralData) {
+ if (progress != null)
+ progress.setProgress(R.string.progress_decrypting, currentProgress, 100);
+ PGPLiteralData literalData = (PGPLiteralData) dataChunk;
+ OutputStream out = outStream;
+
+ 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 = data.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(EXTRA_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 (data.getSize() - startPos == 0) {
+ currentProgress = endProgress;
+ } else {
+ currentProgress = (int) (startProgress + (endProgress - startProgress)
+ * (data.getStreamPosition() - startPos) / (data.getSize() - startPos));
+ }
+ if (progress != null)
+ progress.setProgress(currentProgress, 100);
+ }
+
+ if (signature != null) {
+ if (progress != null)
+ progress.setProgress(R.string.progress_verifyingSignature, 90, 100);
+ PGPSignatureList signatureList = (PGPSignatureList) plainFact.nextObject();
+ PGPSignature messageSignature = signatureList.get(signatureIndex);
+ if (signature.verify(messageSignature)) {
+ returnData.putBoolean(EXTRA_SIGNATURE_SUCCESS, true);
+ } else {
+ returnData.putBoolean(EXTRA_SIGNATURE_SUCCESS, false);
+ }
+ }
+ }
+
+ // TODO: add integrity somewhere
+ if (encryptedData.isIntegrityProtected()) {
+ if (progress != null)
+ progress.setProgress(R.string.progress_verifyingIntegrity, 95, 100);
+ if (encryptedData.verify()) {
+ // passed
+ } else {
+ // failed
+ }
+ } else {
+ // no integrity check
+ }
+
+ if (progress != null)
+ progress.setProgress(R.string.progress_done, 100, 100);
+ return returnData;
+ }
+
+ public static Bundle verifyText(BaseActivity context, InputData data, OutputStream outStream,
+ ProgressDialogUpdater progress) throws IOException, GeneralException, PGPException,
+ SignatureException {
+ Bundle returnData = new Bundle();
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ ArmoredInputStream aIn = new ArmoredInputStream(data.getInputStream());
+
+ if (progress != null)
+ progress.setProgress(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();
+ outStream.write(clearText);
+
+ returnData.putBoolean(EXTRA_SIGNATURE, true);
+
+ if (progress != null)
+ progress.setProgress(R.string.progress_processingSignature, 60, 100);
+ PGPObjectFactory pgpFact = new PGPObjectFactory(aIn);
+
+ PGPSignatureList sigList = (PGPSignatureList) pgpFact.nextObject();
+ if (sigList == null) {
+ throw new GeneralException(context.getString(R.string.error_corruptData));
+ }
+ PGPSignature signature = null;
+ long signatureKeyId = 0;
+ PGPPublicKey signatureKey = null;
+ for (int i = 0; i < sigList.size(); ++i) {
+ signature = sigList.get(i);
+ signatureKey = getPublicKey(signature.getKeyID());
+ if (signatureKeyId == 0) {
+ signatureKeyId = signature.getKeyID();
+ }
+ if (signatureKey == null) {
+ Bundle pauseData = new Bundle();
+ pauseData.putInt(Constants.extras.STATUS, Id.message.unknown_signature_key);
+ pauseData.putLong(Constants.extras.KEY_ID, signatureKeyId);
+ Message msg = new Message();
+ msg.setData(pauseData);
+ context.sendMessage(msg);
+ // pause here
+ context.getRunningThread().pause();
+ // see whether the key was found in the meantime
+ signatureKey = getPublicKey(signature.getKeyID());
+ }
+
+ if (signatureKey == null) {
+ signature = null;
+ } else {
+ signatureKeyId = signature.getKeyID();
+ String userId = null;
+ PGPPublicKeyRing sigKeyRing = getPublicKeyRing(signatureKeyId);
+ if (sigKeyRing != null) {
+ userId = getMainUserId(getMasterKey(sigKeyRing));
+ }
+ returnData.putString(EXTRA_SIGNATURE_USER_ID, userId);
+ break;
+ }
+ }
+
+ returnData.putLong(EXTRA_SIGNATURE_KEY_ID, signatureKeyId);
+
+ if (signature == null) {
+ returnData.putBoolean(EXTRA_SIGNATURE_UNKNOWN, true);
+ if (progress != null)
+ progress.setProgress(R.string.progress_done, 100, 100);
+ return returnData;
+ }
+
+ signature.initVerify(signatureKey, new BouncyCastleProvider());
+
+ 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);
+ }
+
+ returnData.putBoolean(EXTRA_SIGNATURE_SUCCESS, signature.verify());
+
+ if (progress != null)
+ progress.setProgress(R.string.progress_done, 100, 100);
+ return returnData;
+ }
+
+ 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;
+ }
+
+ 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;
+ }
+
+ public static boolean isReleaseVersion(Context context) {
+ try {
+ PackageInfo pi = context.getPackageManager().getPackageInfo(mApgPackageName, 0);
+ if (pi.versionCode % 100 == 99) {
+ return true;
+ } else {
+ return false;
+ }
+ } catch (NameNotFoundException e) {
+ // unpossible!
+ return false;
+ }
+ }
+
+ public static String getVersion(Context context) {
+ if (VERSION != null) {
+ return VERSION;
+ }
+ try {
+ PackageInfo pi = context.getPackageManager().getPackageInfo(mApgPackageName, 0);
+ VERSION = pi.versionName;
+ return VERSION;
+ } catch (NameNotFoundException e) {
+ // unpossible!
+ return "0.0.0";
+ }
+ }
+
+ public static String getFullVersion(Context context) {
+ return "APG v" + getVersion(context);
+ }
+
+ public static String generateRandomString(int length) {
+ SecureRandom random = new SecureRandom();
+ /*
+ * try { random = SecureRandom.getInstance("SHA1PRNG", new BouncyCastleProvider()); } catch
+ * (NoSuchAlgorithmException e) { // TODO: need to handle this case somehow return null; }
+ */
+ 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;
+ }
+
+ 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;
+ }
+
+ public static void deleteFileSecurely(Context context, File file, ProgressDialogUpdater progress)
+ 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_deletingSecurely, 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/org_apg/src/org/apg/ApgService.java b/org_apg/src/org/apg/ApgService.java
new file mode 100644
index 000000000..82e85371d
--- /dev/null
+++ b/org_apg/src/org/apg/ApgService.java
@@ -0,0 +1,643 @@
+package org.apg;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+
+import org.apg.provider.KeyRings;
+import org.apg.provider.Keys;
+import org.apg.provider.UserIds;
+import org.apg.IApgService;
+
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.util.Log;
+
+public class ApgService extends Service {
+ private final static String TAG = "ApgService";
+ public static final boolean LOCAL_LOGV = true;
+ public static final boolean LOCAL_LOGD = true;
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ if (LOCAL_LOGD)
+ Log.d(TAG, "bound");
+ return mBinder;
+ }
+
+ /** error status */
+ private static enum error {
+ ARGUMENTS_MISSING, APG_FAILURE, NO_MATCHING_SECRET_KEY, PRIVATE_KEY_PASSPHRASE_WRONG, PRIVATE_KEY_PASSPHRASE_MISSING;
+
+ public int shiftedOrdinal() {
+ return ordinal() + 100;
+ }
+ }
+
+ private static enum call {
+ encrypt_with_passphrase, encrypt_with_public_key, decrypt, get_keys
+ }
+
+ /** all arguments that can be passed by calling application */
+ public static enum arg {
+ MESSAGE, // message to encrypt or to decrypt
+ SYMMETRIC_PASSPHRASE, // key for symmetric en/decryption
+ PUBLIC_KEYS, // public keys for encryption
+ ENCRYPTION_ALGORYTHM, // encryption algorithm
+ HASH_ALGORYTHM, // hash algorithm
+ ARMORED_OUTPUT, // whether to armor output
+ FORCE_V3_SIGNATURE, // whether to force v3 signature
+ COMPRESSION, // what compression to use for encrypted output
+ SIGNATURE_KEY, // key for signing
+ PRIVATE_KEY_PASSPHRASE, // passphrase for encrypted private key
+ KEY_TYPE, // type of key (private or public)
+ BLOB, // blob passed
+ }
+
+ /** all things that might be returned */
+ private static enum ret {
+ ERRORS, // string array list with errors
+ WARNINGS, // string array list with warnings
+ ERROR, // numeric error
+ RESULT, // en-/decrypted
+ FINGERPRINTS, // fingerprints of keys
+ USER_IDS, // user ids
+ }
+
+ /** required arguments for each AIDL function */
+ private static final HashMap<String, HashSet<arg>> FUNCTIONS_REQUIRED_ARGS = new HashMap<String, HashSet<arg>>();
+ static {
+ HashSet<arg> args = new HashSet<arg>();
+ args.add(arg.SYMMETRIC_PASSPHRASE);
+ FUNCTIONS_REQUIRED_ARGS.put(call.encrypt_with_passphrase.name(), args);
+
+ args = new HashSet<arg>();
+ args.add(arg.PUBLIC_KEYS);
+ FUNCTIONS_REQUIRED_ARGS.put(call.encrypt_with_public_key.name(), args);
+
+ args = new HashSet<arg>();
+ FUNCTIONS_REQUIRED_ARGS.put(call.decrypt.name(), args);
+
+ args = new HashSet<arg>();
+ args.add(arg.KEY_TYPE);
+ FUNCTIONS_REQUIRED_ARGS.put(call.get_keys.name(), args);
+ }
+
+ /** optional arguments for each AIDL function */
+ private static final HashMap<String, HashSet<arg>> FUNCTIONS_OPTIONAL_ARGS = new HashMap<String, HashSet<arg>>();
+ static {
+ HashSet<arg> args = new HashSet<arg>();
+ args.add(arg.ENCRYPTION_ALGORYTHM);
+ args.add(arg.HASH_ALGORYTHM);
+ args.add(arg.ARMORED_OUTPUT);
+ args.add(arg.FORCE_V3_SIGNATURE);
+ args.add(arg.COMPRESSION);
+ args.add(arg.PRIVATE_KEY_PASSPHRASE);
+ args.add(arg.SIGNATURE_KEY);
+ args.add(arg.BLOB);
+ args.add(arg.MESSAGE);
+ FUNCTIONS_OPTIONAL_ARGS.put(call.encrypt_with_passphrase.name(), args);
+ FUNCTIONS_OPTIONAL_ARGS.put(call.encrypt_with_public_key.name(), args);
+
+ args = new HashSet<arg>();
+ args.add(arg.SYMMETRIC_PASSPHRASE);
+ args.add(arg.PUBLIC_KEYS);
+ args.add(arg.PRIVATE_KEY_PASSPHRASE);
+ args.add(arg.MESSAGE);
+ args.add(arg.BLOB);
+ FUNCTIONS_OPTIONAL_ARGS.put(call.decrypt.name(), args);
+ }
+
+ /** a map from ApgService parameters to function calls to get the default */
+ private static final HashMap<arg, String> FUNCTIONS_DEFAULTS = new HashMap<arg, String>();
+ static {
+ FUNCTIONS_DEFAULTS.put(arg.ENCRYPTION_ALGORYTHM, "getDefaultEncryptionAlgorithm");
+ FUNCTIONS_DEFAULTS.put(arg.HASH_ALGORYTHM, "getDefaultHashAlgorithm");
+ FUNCTIONS_DEFAULTS.put(arg.ARMORED_OUTPUT, "getDefaultAsciiArmour");
+ FUNCTIONS_DEFAULTS.put(arg.FORCE_V3_SIGNATURE, "getForceV3Signatures");
+ FUNCTIONS_DEFAULTS.put(arg.COMPRESSION, "getDefaultMessageCompression");
+ }
+
+ /** a map of the default function names to their method */
+ private static final HashMap<String, Method> FUNCTIONS_DEFAULTS_METHODS = new HashMap<String, Method>();
+ static {
+ try {
+ FUNCTIONS_DEFAULTS_METHODS.put("getDefaultEncryptionAlgorithm",
+ Preferences.class.getMethod("getDefaultEncryptionAlgorithm"));
+ FUNCTIONS_DEFAULTS_METHODS.put("getDefaultHashAlgorithm",
+ Preferences.class.getMethod("getDefaultHashAlgorithm"));
+ FUNCTIONS_DEFAULTS_METHODS.put("getDefaultAsciiArmour",
+ Preferences.class.getMethod("getDefaultAsciiArmour"));
+ FUNCTIONS_DEFAULTS_METHODS.put("getForceV3Signatures",
+ Preferences.class.getMethod("getForceV3Signatures"));
+ FUNCTIONS_DEFAULTS_METHODS.put("getDefaultMessageCompression",
+ Preferences.class.getMethod("getDefaultMessageCompression"));
+ } catch (Exception e) {
+ Log.e(TAG, "Function method exception: " + e.getMessage());
+ }
+ }
+
+ private static void writeToOutputStream(InputStream is, OutputStream os) throws IOException {
+ byte[] buffer = new byte[8];
+ int len = 0;
+ while ((len = is.read(buffer)) != -1) {
+ os.write(buffer, 0, len);
+ }
+ }
+
+ private static Cursor getKeyEntries(HashMap<String, Object> pParams) {
+ SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+ qb.setTables(KeyRings.TABLE_NAME + " INNER JOIN " + Keys.TABLE_NAME + " ON " + "("
+ + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " + Keys.TABLE_NAME + "."
+ + Keys.KEY_RING_ID + " AND " + Keys.TABLE_NAME + "." + Keys.IS_MASTER_KEY
+ + " = '1'" + ") " + " INNER JOIN " + UserIds.TABLE_NAME + " ON " + "("
+ + Keys.TABLE_NAME + "." + Keys._ID + " = " + UserIds.TABLE_NAME + "."
+ + UserIds.KEY_ID + " AND " + UserIds.TABLE_NAME + "." + UserIds.RANK + " = '0') ");
+
+ String orderBy = pParams.containsKey("order_by") ? (String) pParams.get("order_by")
+ : UserIds.TABLE_NAME + "." + UserIds.USER_ID + " ASC";
+
+ String typeVal[] = null;
+ String typeWhere = null;
+ if (pParams.containsKey("key_type")) {
+ typeWhere = KeyRings.TABLE_NAME + "." + KeyRings.TYPE + " = ?";
+ typeVal = new String[] { "" + pParams.get("key_type") };
+ }
+ return qb.query(Apg.getDatabase().db(), (String[]) pParams.get("columns"), typeWhere,
+ typeVal, null, null, orderBy);
+ }
+
+ /**
+ * maps a fingerprint or user id of a key to a master key in database
+ *
+ * @param search_key
+ * fingerprint or user id to search for
+ * @return master key if found, or 0
+ */
+ private static long getMasterKey(String pSearchKey, Bundle pReturn) {
+ if (pSearchKey == null || pSearchKey.length() != 8) {
+ return 0;
+ }
+ ArrayList<String> keyList = new ArrayList<String>();
+ keyList.add(pSearchKey);
+ long[] keys = getMasterKey(keyList, pReturn);
+ if (keys.length > 0) {
+ return keys[0];
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * maps fingerprints or user ids of keys to master keys in database
+ *
+ * @param search_keys
+ * a list of keys (fingerprints or user ids) to look for in database
+ * @return an array of master keys
+ */
+ private static long[] getMasterKey(ArrayList<String> pSearchKeys, Bundle pReturn) {
+
+ HashMap<String, Object> qParams = new HashMap<String, Object>();
+ qParams.put("columns", new String[] { KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID, // 0
+ UserIds.TABLE_NAME + "." + UserIds.USER_ID, // 1
+ });
+ qParams.put("key_type", Id.database.type_public);
+
+ Cursor mCursor = getKeyEntries(qParams);
+
+ if (LOCAL_LOGV)
+ Log.v(TAG, "going through installed user keys");
+ ArrayList<Long> masterKeys = new ArrayList<Long>();
+ while (mCursor.moveToNext()) {
+ long curMkey = mCursor.getLong(0);
+ String curUser = mCursor.getString(1);
+
+ String curFprint = Apg.getSmallFingerPrint(curMkey);
+ if (LOCAL_LOGV)
+ Log.v(TAG, "current user: " + curUser + " (" + curFprint + ")");
+ if (pSearchKeys.contains(curFprint) || pSearchKeys.contains(curUser)) {
+ if (LOCAL_LOGV)
+ Log.v(TAG, "master key found for: " + curFprint);
+ masterKeys.add(curMkey);
+ pSearchKeys.remove(curFprint);
+ } else {
+ if (LOCAL_LOGV)
+ Log.v(TAG, "Installed key " + curFprint
+ + " is not in the list of public keys to encrypt with");
+ }
+ }
+ mCursor.close();
+
+ long[] masterKeyLongs = new long[masterKeys.size()];
+ int i = 0;
+ for (Long key : masterKeys) {
+ masterKeyLongs[i++] = key;
+ }
+
+ if (i == 0) {
+ Log.w(TAG, "Found not one public key");
+ pReturn.getStringArrayList(ret.WARNINGS.name()).add(
+ "Searched for public key(s) but found not one");
+ }
+
+ for (String key : pSearchKeys) {
+ Log.w(TAG, "Searched for key " + key + " but cannot find it in APG");
+ pReturn.getStringArrayList(ret.WARNINGS.name()).add(
+ "Searched for key " + key + " but cannot find it in APG");
+ }
+
+ return masterKeyLongs;
+ }
+
+ /**
+ * Add default arguments if missing
+ *
+ * @param args
+ * the bundle to add default parameters to if missing
+ */
+ private void addDefaultArguments(String pCall, Bundle pArgs) {
+ // check whether there are optional elements defined for that call
+ if (FUNCTIONS_OPTIONAL_ARGS.containsKey(pCall)) {
+ Preferences preferences = Preferences.getPreferences(getBaseContext(), true);
+
+ Iterator<arg> iter = FUNCTIONS_DEFAULTS.keySet().iterator();
+ while (iter.hasNext()) {
+ arg currentArg = iter.next();
+ String currentKey = currentArg.name();
+ if (!pArgs.containsKey(currentKey)
+ && FUNCTIONS_OPTIONAL_ARGS.get(pCall).contains(currentArg)) {
+ String currentFunctionName = FUNCTIONS_DEFAULTS.get(currentArg);
+ try {
+ Class<?> returnType = FUNCTIONS_DEFAULTS_METHODS.get(currentFunctionName)
+ .getReturnType();
+ if (returnType == String.class) {
+ pArgs.putString(currentKey,
+ (String) FUNCTIONS_DEFAULTS_METHODS.get(currentFunctionName)
+ .invoke(preferences));
+ } else if (returnType == boolean.class) {
+ pArgs.putBoolean(currentKey,
+ (Boolean) FUNCTIONS_DEFAULTS_METHODS.get(currentFunctionName)
+ .invoke(preferences));
+ } else if (returnType == int.class) {
+ pArgs.putInt(currentKey,
+ (Integer) FUNCTIONS_DEFAULTS_METHODS.get(currentFunctionName)
+ .invoke(preferences));
+ } else {
+ Log.e(TAG, "Unknown return type " + returnType.toString()
+ + " for default option");
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Exception in add_default_arguments " + e.getMessage());
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * updates a Bundle with default return values
+ *
+ * @param pReturn
+ * the Bundle to update
+ */
+ private void addDefaultReturns(Bundle pReturn) {
+ ArrayList<String> errors = new ArrayList<String>();
+ ArrayList<String> warnings = new ArrayList<String>();
+
+ pReturn.putStringArrayList(ret.ERRORS.name(), errors);
+ pReturn.putStringArrayList(ret.WARNINGS.name(), warnings);
+ }
+
+ /**
+ * checks for required arguments and adds them to the error if missing
+ *
+ * @param function
+ * the functions required arguments to check for
+ * @param pArgs
+ * the Bundle of arguments to check
+ * @param pReturn
+ * the bundle to write errors to
+ */
+ private void checkForRequiredArgs(String pFunction, Bundle pArgs, Bundle pReturn) {
+ if (FUNCTIONS_REQUIRED_ARGS.containsKey(pFunction)) {
+ Iterator<arg> iter = FUNCTIONS_REQUIRED_ARGS.get(pFunction).iterator();
+ while (iter.hasNext()) {
+ String curArg = iter.next().name();
+ if (!pArgs.containsKey(curArg)) {
+ pReturn.getStringArrayList(ret.ERRORS.name())
+ .add("Argument missing: " + curArg);
+ }
+ }
+ }
+
+ if (pFunction.equals(call.encrypt_with_passphrase.name())
+ || pFunction.equals(call.encrypt_with_public_key.name())
+ || pFunction.equals(call.decrypt.name())) {
+ // check that either MESSAGE or BLOB are there
+ if (!pArgs.containsKey(arg.MESSAGE.name()) && !pArgs.containsKey(arg.BLOB.name())) {
+ pReturn.getStringArrayList(ret.ERRORS.name()).add(
+ "Arguments missing: Neither MESSAGE nor BLOG found");
+ }
+
+ }
+ }
+
+ /**
+ * checks for unknown arguments and add them to warning if found
+ *
+ * @param function
+ * the functions name to check against
+ * @param pArgs
+ * the Bundle of arguments to check
+ * @param pReturn
+ * the bundle to write warnings to
+ */
+ private void checkForUnknownArgs(String pFunction, Bundle pArgs, Bundle pReturn) {
+
+ HashSet<arg> allArgs = new HashSet<arg>();
+ if (FUNCTIONS_REQUIRED_ARGS.containsKey(pFunction)) {
+ allArgs.addAll(FUNCTIONS_REQUIRED_ARGS.get(pFunction));
+ }
+ if (FUNCTIONS_OPTIONAL_ARGS.containsKey(pFunction)) {
+ allArgs.addAll(FUNCTIONS_OPTIONAL_ARGS.get(pFunction));
+ }
+
+ ArrayList<String> unknownArgs = new ArrayList<String>();
+ Iterator<String> iter = pArgs.keySet().iterator();
+ while (iter.hasNext()) {
+ String curKey = iter.next();
+ try {
+ arg curArg = arg.valueOf(curKey);
+ if (!allArgs.contains(curArg)) {
+ pReturn.getStringArrayList(ret.WARNINGS.name()).add(
+ "Unknown argument: " + curKey);
+ unknownArgs.add(curKey);
+ }
+ } catch (Exception e) {
+ pReturn.getStringArrayList(ret.WARNINGS.name()).add("Unknown argument: " + curKey);
+ unknownArgs.add(curKey);
+ }
+ }
+
+ // remove unknown arguments so our bundle has just what we need
+ for (String arg : unknownArgs) {
+ pArgs.remove(arg);
+ }
+ }
+
+ private boolean prepareArgs(String pCall, Bundle pArgs, Bundle pReturn) {
+ Apg.initialize(getBaseContext());
+
+ /* add default return values for all functions */
+ addDefaultReturns(pReturn);
+
+ /* add default arguments if missing */
+ addDefaultArguments(pCall, pArgs);
+ if (LOCAL_LOGV)
+ Log.v(TAG, "add_default_arguments");
+
+ /* check for required arguments */
+ checkForRequiredArgs(pCall, pArgs, pReturn);
+ if (LOCAL_LOGV)
+ Log.v(TAG, "check_required_args");
+
+ /* check for unknown arguments and add to warning if found */
+ checkForUnknownArgs(pCall, pArgs, pReturn);
+ if (LOCAL_LOGV)
+ Log.v(TAG, "check_unknown_args");
+
+ /* return if errors happened */
+ if (pReturn.getStringArrayList(ret.ERRORS.name()).size() != 0) {
+ if (LOCAL_LOGV)
+ Log.v(TAG, "Errors after preparing, not executing " + pCall);
+ pReturn.putInt(ret.ERROR.name(), error.ARGUMENTS_MISSING.shiftedOrdinal());
+ return false;
+ }
+ if (LOCAL_LOGV)
+ Log.v(TAG, "error return");
+
+ return true;
+ }
+
+ private boolean encrypt(Bundle pArgs, Bundle pReturn) {
+ boolean isBlob = pArgs.containsKey(arg.BLOB.name());
+
+ long pubMasterKeys[] = {};
+ if (pArgs.containsKey(arg.PUBLIC_KEYS.name())) {
+ ArrayList<String> list = pArgs.getStringArrayList(arg.PUBLIC_KEYS.name());
+ ArrayList<String> pubKeys = new ArrayList<String>();
+ if (LOCAL_LOGV)
+ Log.v(TAG, "Long size: " + list.size());
+ Iterator<String> iter = list.iterator();
+ while (iter.hasNext()) {
+ pubKeys.add(iter.next());
+ }
+ pubMasterKeys = getMasterKey(pubKeys, pReturn);
+ }
+
+ InputStream inStream = null;
+ if (isBlob) {
+ ContentResolver cr = getContentResolver();
+ try {
+ inStream = cr.openInputStream(Uri.parse(pArgs.getString(arg.BLOB.name())));
+ } catch (Exception e) {
+ Log.e(TAG, "... exception on opening blob", e);
+ }
+ } else {
+ inStream = new ByteArrayInputStream(pArgs.getString(arg.MESSAGE.name()).getBytes());
+ }
+ InputData in = new InputData(inStream, 0); // XXX Size second param?
+
+ OutputStream out = new ByteArrayOutputStream();
+ if (LOCAL_LOGV)
+ Log.v(TAG, "About to encrypt");
+ try {
+ Apg.encrypt(getBaseContext(), // context
+ in, // input stream
+ out, // output stream
+ pArgs.getBoolean(arg.ARMORED_OUTPUT.name()), // ARMORED_OUTPUT
+ pubMasterKeys, // encryption keys
+ getMasterKey(pArgs.getString(arg.SIGNATURE_KEY.name()), pReturn), // signature
+ // key
+ pArgs.getString(arg.PRIVATE_KEY_PASSPHRASE.name()), // signature passphrase
+ null, // progress
+ pArgs.getInt(arg.ENCRYPTION_ALGORYTHM.name()), // encryption
+ pArgs.getInt(arg.HASH_ALGORYTHM.name()), // hash
+ pArgs.getInt(arg.COMPRESSION.name()), // compression
+ pArgs.getBoolean(arg.FORCE_V3_SIGNATURE.name()), // mPreferences.getForceV3Signatures(),
+ pArgs.getString(arg.SYMMETRIC_PASSPHRASE.name()) // passPhrase
+ );
+ } catch (Exception e) {
+ Log.e(TAG, "Exception in encrypt");
+ String msg = e.getMessage();
+ if (msg.equals(getBaseContext().getString(R.string.error_noSignaturePassPhrase))) {
+ pReturn.getStringArrayList(ret.ERRORS.name()).add(
+ "Cannot encrypt (" + arg.PRIVATE_KEY_PASSPHRASE.name() + " missing): "
+ + msg);
+ pReturn.putInt(ret.ERROR.name(),
+ error.PRIVATE_KEY_PASSPHRASE_MISSING.shiftedOrdinal());
+ } else if (msg.equals(getBaseContext().getString(
+ R.string.error_couldNotExtractPrivateKey))) {
+ pReturn.getStringArrayList(ret.ERRORS.name()).add(
+ "Cannot encrypt (" + arg.PRIVATE_KEY_PASSPHRASE.name()
+ + " probably wrong): " + msg);
+ pReturn.putInt(ret.ERROR.name(),
+ error.PRIVATE_KEY_PASSPHRASE_WRONG.shiftedOrdinal());
+ } else {
+ pReturn.getStringArrayList(ret.ERRORS.name()).add(
+ "Internal failure (" + e.getClass() + ") in APG when encrypting: "
+ + e.getMessage());
+ pReturn.putInt(ret.ERROR.name(), error.APG_FAILURE.shiftedOrdinal());
+ }
+ return false;
+ }
+ if (LOCAL_LOGV)
+ Log.v(TAG, "Encrypted");
+ if (isBlob) {
+ ContentResolver cr = getContentResolver();
+ try {
+ OutputStream outStream = cr.openOutputStream(Uri.parse(pArgs.getString(arg.BLOB
+ .name())));
+ writeToOutputStream(new ByteArrayInputStream(out.toString().getBytes()), outStream);
+ outStream.close();
+ } catch (Exception e) {
+ Log.e(TAG, "... exception on writing blob", e);
+ }
+ } else {
+ pReturn.putString(ret.RESULT.name(), out.toString());
+ }
+ return true;
+ }
+
+ private final IApgService.Stub mBinder = new IApgService.Stub() {
+
+ public boolean getKeys(Bundle pArgs, Bundle pReturn) {
+
+ prepareArgs("get_keys", pArgs, pReturn);
+
+ HashMap<String, Object> qParams = new HashMap<String, Object>();
+ qParams.put("columns", new String[] {
+ KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID, // 0
+ UserIds.TABLE_NAME + "." + UserIds.USER_ID, // 1
+ });
+
+ qParams.put("key_type", pArgs.getInt(arg.KEY_TYPE.name()));
+
+ Cursor cursor = getKeyEntries(qParams);
+ ArrayList<String> fPrints = new ArrayList<String>();
+ ArrayList<String> ids = new ArrayList<String>();
+ while (cursor.moveToNext()) {
+ if (LOCAL_LOGV)
+ Log.v(TAG, "adding key " + Apg.getSmallFingerPrint(cursor.getLong(0)));
+ fPrints.add(Apg.getSmallFingerPrint(cursor.getLong(0)));
+ ids.add(cursor.getString(1));
+ }
+ cursor.close();
+
+ pReturn.putStringArrayList(ret.FINGERPRINTS.name(), fPrints);
+ pReturn.putStringArrayList(ret.USER_IDS.name(), ids);
+ return true;
+ }
+
+ public boolean encryptWithPublicKey(Bundle pArgs, Bundle pReturn) {
+ if (!prepareArgs("encrypt_with_public_key", pArgs, pReturn)) {
+ return false;
+ }
+
+ return encrypt(pArgs, pReturn);
+ }
+
+ public boolean encryptWithPassphrase(Bundle pArgs, Bundle pReturn) {
+ if (!prepareArgs("encrypt_with_passphrase", pArgs, pReturn)) {
+ return false;
+ }
+
+ return encrypt(pArgs, pReturn);
+
+ }
+
+ public boolean decrypt(Bundle pArgs, Bundle pReturn) {
+ if (!prepareArgs("decrypt", pArgs, pReturn)) {
+ return false;
+ }
+
+ boolean isBlob = pArgs.containsKey(arg.BLOB.name());
+
+ String passphrase = pArgs.getString(arg.SYMMETRIC_PASSPHRASE.name()) != null ? pArgs
+ .getString(arg.SYMMETRIC_PASSPHRASE.name()) : pArgs
+ .getString(arg.PRIVATE_KEY_PASSPHRASE.name());
+
+ InputStream inStream = null;
+ if (isBlob) {
+ ContentResolver cr = getContentResolver();
+ try {
+ inStream = cr.openInputStream(Uri.parse(pArgs.getString(arg.BLOB.name())));
+ } catch (Exception e) {
+ Log.e(TAG, "... exception on opening blob", e);
+ }
+ } else {
+ inStream = new ByteArrayInputStream(pArgs.getString(arg.MESSAGE.name()).getBytes());
+ }
+
+ InputData in = new InputData(inStream, 0); // XXX what size in second parameter?
+ OutputStream out = new ByteArrayOutputStream();
+ if (LOCAL_LOGV)
+ Log.v(TAG, "About to decrypt");
+ try {
+ Apg.decrypt(getBaseContext(), in, out, passphrase, null, // progress
+ pArgs.getString(arg.SYMMETRIC_PASSPHRASE.name()) != null // symmetric
+ );
+ } catch (Exception e) {
+ Log.e(TAG, "Exception in decrypt");
+ String msg = e.getMessage();
+ if (msg.equals(getBaseContext().getString(R.string.error_noSecretKeyFound))) {
+ pReturn.getStringArrayList(ret.ERRORS.name()).add("Cannot decrypt: " + msg);
+ pReturn.putInt(ret.ERROR.name(), error.NO_MATCHING_SECRET_KEY.shiftedOrdinal());
+ } else if (msg.equals(getBaseContext().getString(R.string.error_wrongPassPhrase))) {
+ pReturn.getStringArrayList(ret.ERRORS.name()).add(
+ "Cannot decrypt (" + arg.PRIVATE_KEY_PASSPHRASE.name()
+ + " wrong/missing): " + msg);
+ pReturn.putInt(ret.ERROR.name(),
+ error.PRIVATE_KEY_PASSPHRASE_WRONG.shiftedOrdinal());
+ } else {
+ pReturn.getStringArrayList(ret.ERRORS.name()).add(
+ "Internal failure (" + e.getClass() + ") in APG when decrypting: "
+ + msg);
+ pReturn.putInt(ret.ERROR.name(), error.APG_FAILURE.shiftedOrdinal());
+ }
+ return false;
+ }
+ if (LOCAL_LOGV)
+ Log.v(TAG, "... decrypted");
+
+ if (isBlob) {
+ ContentResolver cr = getContentResolver();
+ try {
+ OutputStream outStream = cr.openOutputStream(Uri.parse(pArgs.getString(arg.BLOB
+ .name())));
+ writeToOutputStream(new ByteArrayInputStream(out.toString().getBytes()),
+ outStream);
+ outStream.close();
+ } catch (Exception e) {
+ Log.e(TAG, "... exception on writing blob", e);
+ }
+ } else {
+ pReturn.putString(ret.RESULT.name(), out.toString());
+ }
+ return true;
+ }
+
+ };
+}
diff --git a/org_apg/src/org/apg/AskForSecretKeyPassPhrase.java b/org_apg/src/org/apg/AskForSecretKeyPassPhrase.java
new file mode 100644
index 000000000..ebed75667
--- /dev/null
+++ b/org_apg/src/org/apg/AskForSecretKeyPassPhrase.java
@@ -0,0 +1,115 @@
+/*
+ * 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.apg;
+
+import org.spongycastle.jce.provider.BouncyCastleProvider;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPPrivateKey;
+import org.spongycastle.openpgp.PGPSecretKey;
+import org.apg.R;
+
+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.view.LayoutInflater;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.Toast;
+
+public class AskForSecretKeyPassPhrase {
+ public static interface PassPhraseCallbackInterface {
+ void passPhraseCallback(long keyId, String passPhrase);
+ }
+
+ public static Dialog createDialog(Activity context, long secretKeyId,
+ PassPhraseCallbackInterface callback) {
+ AlertDialog.Builder alert = new AlertDialog.Builder(context);
+
+ alert.setTitle(R.string.title_authentication);
+
+ final PGPSecretKey secretKey;
+ final Activity activity = context;
+
+ if (secretKeyId == Id.key.symmetric || secretKeyId == Id.key.none) {
+ secretKey = null;
+ alert.setMessage(context.getString(R.string.passPhraseForSymmetricEncryption));
+ } else {
+ secretKey = Apg.getMasterKey(Apg.getSecretKeyRing(secretKeyId));
+ if (secretKey == null) {
+ alert.setTitle(R.string.title_keyNotFound);
+ alert.setMessage(context.getString(R.string.keyNotFound, secretKeyId));
+ alert.setPositiveButton(android.R.string.ok, new OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ activity.removeDialog(Id.dialog.pass_phrase);
+ }
+ });
+ alert.setCancelable(false);
+ return alert.create();
+ }
+ String userId = Apg.getMainUserIdSafe(context, secretKey);
+ alert.setMessage(context.getString(R.string.passPhraseFor, userId));
+ }
+
+ LayoutInflater inflater = (LayoutInflater) context
+ .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ View view = inflater.inflate(R.layout.pass_phrase, null);
+ final EditText input = (EditText) view.findViewById(R.id.passPhrase);
+ final EditText inputNotUsed = (EditText) view.findViewById(R.id.passPhraseAgain);
+ inputNotUsed.setVisibility(View.GONE);
+
+ alert.setView(view);
+
+ final PassPhraseCallbackInterface cb = callback;
+ alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ activity.removeDialog(Id.dialog.pass_phrase);
+ String passPhrase = "" + input.getText();
+ long keyId;
+ if (secretKey != null) {
+ try {
+ PGPPrivateKey testKey = secretKey.extractPrivateKey(
+ passPhrase.toCharArray(), new BouncyCastleProvider());
+ if (testKey == null) {
+ Toast.makeText(activity, R.string.error_couldNotExtractPrivateKey,
+ Toast.LENGTH_SHORT).show();
+ return;
+ }
+ } catch (PGPException e) {
+ Toast.makeText(activity, R.string.wrongPassPhrase, Toast.LENGTH_SHORT)
+ .show();
+ return;
+ }
+ keyId = secretKey.getKeyID();
+ } else {
+ keyId = Id.key.symmetric;
+ }
+ cb.passPhraseCallback(keyId, passPhrase);
+ }
+ });
+
+ alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ activity.removeDialog(Id.dialog.pass_phrase);
+ }
+ });
+
+ return alert.create();
+ }
+}
diff --git a/org_apg/src/org/apg/CachedPassPhrase.java b/org_apg/src/org/apg/CachedPassPhrase.java
new file mode 100644
index 000000000..2d67a300d
--- /dev/null
+++ b/org_apg/src/org/apg/CachedPassPhrase.java
@@ -0,0 +1,48 @@
+package org.apg;
+
+public class CachedPassPhrase {
+ public final long timestamp;
+ public final String passPhrase;
+
+ public CachedPassPhrase(long timestamp, String passPhrase) {
+ super();
+ this.timestamp = timestamp;
+ this.passPhrase = passPhrase;
+ }
+
+ @Override
+ public int hashCode() {
+ int hc1 = (int) (this.timestamp & 0xffffffff);
+ int hc2 = (this.passPhrase == null ? 0 : this.passPhrase.hashCode());
+ return (hc1 + hc2) * hc2 + hc1;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof CachedPassPhrase)) {
+ return false;
+ }
+
+ CachedPassPhrase o = (CachedPassPhrase) other;
+ if (timestamp != o.timestamp) {
+ return false;
+ }
+
+ if (passPhrase != o.passPhrase) {
+ if (passPhrase == null || o.passPhrase == null) {
+ return false;
+ }
+
+ if (!passPhrase.equals(o.passPhrase)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "(" + timestamp + ", *******)";
+ }
+}
diff --git a/org_apg/src/org/apg/Constants.java b/org_apg/src/org/apg/Constants.java
new file mode 100644
index 000000000..9669d4b0d
--- /dev/null
+++ b/org_apg/src/org/apg/Constants.java
@@ -0,0 +1,54 @@
+/*
+ * 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.apg;
+
+import android.os.Environment;
+
+public final class Constants {
+
+ public static final String TAG = "APG";
+
+ public static final class path {
+ public static final String APP_DIR = Environment.getExternalStorageDirectory() + "/APG";
+ }
+
+ public static final class pref {
+ public static final String HAS_SEEN_HELP = "seenHelp";
+ public static final String HAS_SEEN_CHANGE_LOG = "seenChangeLogDialog";
+ 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";
+ }
+
+ public static final class extras {
+ public static final String PROGRESS = "progress";
+ public static final String PROGRESS_MAX = "max";
+ public static final String STATUS = "status";
+ public static final String MESSAGE = "message";
+ public static final String KEY_ID = "keyId";
+ }
+}
diff --git a/org_apg/src/org/apg/DataDestination.java b/org_apg/src/org/apg/DataDestination.java
new file mode 100644
index 000000000..509670e69
--- /dev/null
+++ b/org_apg/src/org/apg/DataDestination.java
@@ -0,0 +1,81 @@
+package org.apg;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.apg.Apg.GeneralException;
+import org.apg.R;
+
+import android.content.Context;
+import android.os.Environment;
+
+public class DataDestination {
+ private String mStreamFilename;
+ private String mFilename;
+ private int mMode = Id.mode.undefined;
+
+ public DataDestination() {
+
+ }
+
+ public void setMode(int mode) {
+ mMode = mode;
+ }
+
+ public void setFilename(String filename) {
+ mFilename = filename;
+ }
+
+ public String getStreamFilename() {
+ return mStreamFilename;
+ }
+
+ public OutputStream getOutputStream(Context context) throws Apg.GeneralException,
+ FileNotFoundException, IOException {
+ OutputStream out = null;
+ mStreamFilename = null;
+
+ switch (mMode) {
+ case Id.mode.stream: {
+ try {
+ while (true) {
+ mStreamFilename = Apg.generateRandomString(32);
+ if (mStreamFilename == null) {
+ throw new Apg.GeneralException("couldn't generate random file name");
+ }
+ context.openFileInput(mStreamFilename).close();
+ }
+ } catch (FileNotFoundException e) {
+ // found a name that isn't used yet
+ }
+ out = context.openFileOutput(mStreamFilename, Context.MODE_PRIVATE);
+ break;
+ }
+
+ case Id.mode.byte_array: {
+ out = new ByteArrayOutputStream();
+ break;
+ }
+
+ case Id.mode.file: {
+ if (mFilename.startsWith(Environment.getExternalStorageDirectory().getAbsolutePath())) {
+ if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+ throw new GeneralException(
+ context.getString(R.string.error_externalStorageNotReady));
+ }
+ }
+ out = new FileOutputStream(mFilename);
+ break;
+ }
+
+ default: {
+ break;
+ }
+ }
+
+ return out;
+ }
+}
diff --git a/org_apg/src/org/apg/DataSource.java b/org_apg/src/org/apg/DataSource.java
new file mode 100644
index 000000000..340afa9f8
--- /dev/null
+++ b/org_apg/src/org/apg/DataSource.java
@@ -0,0 +1,104 @@
+package org.apg;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apg.Apg.GeneralException;
+import org.apg.R;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.Environment;
+
+public class DataSource {
+ private Uri mContentUri = null;
+ private String mText = null;
+ private byte[] mData = null;
+
+ public DataSource() {
+
+ }
+
+ public void setUri(Uri uri) {
+ mContentUri = uri;
+ mText = null;
+ mData = null;
+ }
+
+ public void setUri(String uri) {
+ if (uri.startsWith("/")) {
+ setUri(Uri.parse("file://" + uri));
+ } else {
+ setUri(Uri.parse(uri));
+ }
+ }
+
+ public void setText(String text) {
+ mText = text;
+ mData = null;
+ mContentUri = null;
+ }
+
+ public void setData(byte[] data) {
+ mData = data;
+ mText = null;
+ mContentUri = null;
+ }
+
+ public boolean isText() {
+ return mText != null;
+ }
+
+ public boolean isBinary() {
+ return mData != null || mContentUri != null;
+ }
+
+ public InputData getInputData(Context context, boolean withSize) throws GeneralException,
+ FileNotFoundException, IOException {
+ InputStream in = null;
+ long size = 0;
+
+ if (mContentUri != null) {
+ if (mContentUri.getScheme().equals("file")) {
+ // get the rest after "file://"
+ String path = Uri.decode(mContentUri.toString().substring(7));
+ if (path.startsWith(Environment.getExternalStorageDirectory().getAbsolutePath())) {
+ if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+ throw new GeneralException(
+ context.getString(R.string.error_externalStorageNotReady));
+ }
+ }
+ in = new FileInputStream(path);
+ File file = new File(path);
+ if (withSize) {
+ size = file.length();
+ }
+ } else {
+ in = context.getContentResolver().openInputStream(mContentUri);
+ if (withSize) {
+ InputStream tmp = context.getContentResolver().openInputStream(mContentUri);
+ size = Apg.getLengthOfStream(tmp);
+ tmp.close();
+ }
+ }
+ } else if (mText != null || mData != null) {
+ byte[] bytes = null;
+ if (mData != null) {
+ bytes = mData;
+ } else {
+ bytes = mText.getBytes();
+ }
+ in = new ByteArrayInputStream(bytes);
+ if (withSize) {
+ size = bytes.length;
+ }
+ }
+
+ return new InputData(in, size);
+ }
+
+}
diff --git a/org_apg/src/org/apg/FileDialog.java b/org_apg/src/org/apg/FileDialog.java
new file mode 100644
index 000000000..6ad498b90
--- /dev/null
+++ b/org_apg/src/org/apg/FileDialog.java
@@ -0,0 +1,133 @@
+/*
+ * 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.apg;
+
+import org.apg.R;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.net.Uri;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.CheckBox;
+import android.widget.EditText;
+import android.widget.ImageButton;
+import android.widget.Toast;
+
+public class FileDialog {
+ private static EditText mFilename;
+ private static ImageButton mBrowse;
+ private static CheckBox mCheckBox;
+ private static Activity mActivity;
+ private static String mFileManagerTitle;
+ private static String mFileManagerButton;
+ private static int mRequestCode;
+
+ public static interface OnClickListener {
+ public void onCancelClick();
+
+ public void onOkClick(String filename, boolean checkbox);
+ }
+
+ public static AlertDialog build(Activity activity, String title, String message,
+ String defaultFile, OnClickListener onClickListener, String fileManagerTitle,
+ String fileManagerButton, String checkboxText, int requestCode) {
+ // TODO: fileManagerTitle and fileManagerButton are deprecated, no use for them right now,
+ // but maybe the Intent now used will someday support them again, so leaving them in
+ 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);
+
+ mActivity = activity;
+ mFilename = (EditText) view.findViewById(R.id.input);
+ mFilename.setText(defaultFile);
+ mBrowse = (ImageButton) view.findViewById(R.id.btn_browse);
+ mBrowse.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ openFile();
+ }
+ });
+ mFileManagerTitle = fileManagerTitle;
+ mFileManagerButton = fileManagerButton;
+ mRequestCode = requestCode;
+ 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);
+
+ final OnClickListener clickListener = onClickListener;
+
+ alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ boolean checked = false;
+ if (mCheckBox.isEnabled()) {
+ checked = mCheckBox.isChecked();
+ }
+ clickListener.onOkClick(mFilename.getText().toString(), checked);
+ }
+ });
+
+ alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ clickListener.onCancelClick();
+ }
+ });
+ return alert.create();
+ }
+
+ public static void setFilename(String filename) {
+ if (mFilename != null) {
+ mFilename.setText(filename);
+ }
+ }
+
+ /**
+ * Opens the file manager to select a file to open.
+ */
+ private static void openFile() {
+ String filename = mFilename.getText().toString();
+
+ Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+ intent.addCategory(Intent.CATEGORY_OPENABLE);
+
+ intent.setData(Uri.parse("file://" + filename));
+ intent.setType("*/*");
+
+ try {
+ mActivity.startActivityForResult(intent, mRequestCode);
+ } catch (ActivityNotFoundException e) {
+ // No compatible file manager was found.
+ Toast.makeText(mActivity, R.string.noFilemanagerInstalled, Toast.LENGTH_SHORT).show();
+ }
+ }
+}
diff --git a/org_apg/src/org/apg/HkpKeyServer.java b/org_apg/src/org/apg/HkpKeyServer.java
new file mode 100644
index 000000000..294a60cb2
--- /dev/null
+++ b/org_apg/src/org/apg/HkpKeyServer.java
@@ -0,0 +1,242 @@
+package org.apg;
+
+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.Vector;
+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 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);
+ }
+
+ // TODO: replace this with httpclient
+ 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");
+ }
+
+ // TODO: replace this with httpclient
+ @Override
+ public List<KeyInfo> search(String query) throws QueryException, TooManyResponses,
+ InsufficientQuery {
+ Vector<KeyInfo> results = new Vector<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 = Apg.keyFromHex(matcher.group(3));
+ info.fingerPrint = Apg.getSmallFingerPrint(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 Vector<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" + Apg.keyToHex(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 = Apg.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
+ void add(String armouredText) 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", armouredText));
+ 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/org_apg/src/org/apg/IApgService.aidl b/org_apg/src/org/apg/IApgService.aidl
new file mode 100644
index 000000000..8101bd2a4
--- /dev/null
+++ b/org_apg/src/org/apg/IApgService.aidl
@@ -0,0 +1,125 @@
+package org.apg;
+
+interface IApgService {
+
+ /* All functions fill the returnVals Bundle with the following keys:
+ *
+ * ArrayList<String> "WARNINGS" = Warnings, if any
+ * ArrayList<String> "ERRORS" = Human readable error descriptions, if any
+ * int "ERROR" = Numeric representation of error, if any
+ * starting with 100:
+ * 100: Required argument missing
+ * 101: Generic failure of APG
+ * 102: No matching private key found
+ * 103: Private key's passphrase wrong
+ * 104: Private key's passphrase missing
+ */
+
+ /* ********************************************************
+ * Encryption
+ * ********************************************************/
+
+ /* All encryption function's arguments
+ *
+ * Bundle params' keys:
+ * (optional/required)
+ * TYPE "STRING KEY" = EXPLANATION / VALUES
+ *
+ * (required)
+ * String "MESSAGE" = Message to encrypt
+ * OR
+ * String "BLOB" = ContentUri to a file handle
+ * with binary data to encrypt
+ * (Attention: file will be overwritten
+ * with encrypted content!)
+ *
+ * (optional)
+ * int "ENCRYPTION_ALGORYTHM" = Encryption Algorithm
+ * 7: AES-128, 8: AES-192, 9: AES-256,
+ * 4: Blowfish, 10: Twofish, 3: CAST5,
+ * 6: DES, 2: Triple DES, 1: IDEA
+ * (optional)
+ * int "HASH_ALGORYTHM" = Hash Algorithm
+ * 1: MD5, 3: RIPEMD-160, 2: SHA-1,
+ * 11: SHA-224, 8: SHA-256, 9: SHA-384,
+ * 10: SHA-512
+ * (optional)
+ * Boolean "ARMORED_OUTPUT" = Armor output
+ *
+ * (optional)
+ * Boolean "FORCE_V3_SIGNATURE" = Force V3 Signatures
+ *
+ * (optional)
+ * int "COMPRESSION" = Compression to use
+ * 0x21070001: none, 1: Zip, 2: Zlib,
+ * 3: BZip2
+ * (optional)
+ * String "SIGNATURE_KEY" = Key to sign with
+ *
+ * (optional)
+ * String "PRIVATE_KEY_PASSPHRASE" = Passphrase for signing key
+ *
+ * Bundle returnVals (in addition to the ERRORS/WARNINGS above):
+ * If "MESSAGE" was set:
+ * String "RESULT" = Encrypted message
+ */
+
+ /* Additional argument for function below:
+ * (required)
+ * String "SYMMETRIC_PASSPHRASE" = Symmetric passphrase to use
+ */
+ boolean encryptWithPassphrase(in Bundle params, out Bundle returnVals);
+
+ /* Additional argument:
+ * (required)
+ * ArrayList<String> "PUBLIC_KEYS" = Public keys (8char fingerprint "123ABC12" OR
+ * complete id "Alice Meyer <ab@email.com>")
+ */
+ boolean encryptWithPublicKey(in Bundle params, out Bundle returnVals);
+
+ /* ********************************************************
+ * Decryption
+ * ********************************************************/
+
+ /* Bundle params:
+ * (required)
+ * String "MESSAGE" = Message to dencrypt
+ * OR
+ * String "BLOB" = ContentUri to a file handle
+ * with binary data to dencrypt
+ * (Attention: file will be overwritten
+ * with dencrypted content!)
+ *
+ * (optional)
+ * String "SYMMETRIC_PASSPHRASE" = Symmetric passphrase for decryption
+ *
+ * (optional)
+ * String "PRIVATE_KEY_PASSPHRASE" = Private keys's passphrase on asymmetric encryption
+ *
+ * Bundle return_vals:
+ * If "MESSAGE" was set:
+ * String "RESULT" = Decrypted message
+ */
+ boolean decrypt(in Bundle params, out Bundle returnVals);
+
+ /* ********************************************************
+ * Get key information
+ * ********************************************************/
+
+ /* Get info about all available keys
+ *
+ * Bundle params:
+ * (required)
+ * int "KEY_TYPE" = info about what type of keys to return
+ * 0: public keys
+ * 1: private keys
+ *
+ * Returns:
+ * StringArrayList "FINGERPRINTS" = Short fingerprints of keys
+ *
+ * StringArrayList "USER_IDS" = User ids of corresponding fingerprints
+ * (order is the same as in FINGERPRINTS)
+ */
+ boolean getKeys(in Bundle params, out Bundle returnVals);
+
+} \ No newline at end of file
diff --git a/org_apg/src/org/apg/Id.java b/org_apg/src/org/apg/Id.java
new file mode 100644
index 000000000..adcad0534
--- /dev/null
+++ b/org_apg/src/org/apg/Id.java
@@ -0,0 +1,185 @@
+/*
+ * 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.apg;
+
+import org.spongycastle.bcpg.CompressionAlgorithmTags;
+
+public final class Id {
+
+ public static final String TAG = "APG";
+
+ public static final class menu {
+ public static final int export = 0x21070001;
+ public static final int delete = 0x21070002;
+ public static final int edit = 0x21070003;
+ public static final int update = 0x21070004;
+ public static final int exportToServer = 0x21070005;
+ public static final int share = 0x21070006;
+ public static final int signKey = 0x21070007;
+
+ 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 import_keys = 0x21070006;
+ 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 class message {
+ public static final int progress_update = 0x21070001;
+ public static final int done = 0x21070002;
+ public static final int import_keys = 0x21070003;
+ public static final int export_keys = 0x21070004;
+ public static final int import_done = 0x21070005;
+ public static final int export_done = 0x21070006;
+ public static final int create_key = 0x21070007;
+ public static final int edit_key = 0x21070008;
+ public static final int delete_done = 0x21070009;
+ public static final int query_done = 0x21070010;
+ public static final int unknown_signature_key = 0x21070011;
+ }
+
+ public static final class request {
+ public static final int public_keys = 0x21070001;
+ public static final int secret_keys = 0x21070002;
+ public static final int filename = 0x21070003;
+ public static final int output_filename = 0x21070004;
+ public static final int key_server_preference = 0x21070005;
+ public static final int look_up_key_id = 0x21070006;
+ public static final int export_to_server = 0x21070007;
+ public static final int import_from_qr_code = 0x21070008;
+ public static final int sign_key = 0x21070009;
+ }
+
+ 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 about = 0x2107000f;
+ 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 database {
+ public static final int type_public = 0;
+ public static final int type_secret = 1;
+ }
+
+ 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/org_apg/src/org/apg/InputData.java b/org_apg/src/org/apg/InputData.java
new file mode 100644
index 000000000..ae5b51fe4
--- /dev/null
+++ b/org_apg/src/org/apg/InputData.java
@@ -0,0 +1,25 @@
+package org.apg;
+
+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/org_apg/src/org/apg/KeyServer.java b/org_apg/src/org/apg/KeyServer.java
new file mode 100644
index 000000000..32158454f
--- /dev/null
+++ b/org_apg/src/org/apg/KeyServer.java
@@ -0,0 +1,46 @@
+package org.apg;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+import java.util.Vector;
+
+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 {
+ private static final long serialVersionUID = -7797972113284992662L;
+ public Vector<String> userIds;
+ public String revoked;
+ public Date date;
+ public String fingerPrint;
+ public long keyId;
+ public int size;
+ public String 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/org_apg/src/org/apg/PausableThread.java b/org_apg/src/org/apg/PausableThread.java
new file mode 100644
index 000000000..87258d36c
--- /dev/null
+++ b/org_apg/src/org/apg/PausableThread.java
@@ -0,0 +1,35 @@
+package org.apg;
+
+public class PausableThread extends Thread {
+ private boolean mPaused = false;
+
+ public PausableThread(Runnable runnable) {
+ super(runnable);
+ }
+
+ public void pause() {
+ synchronized (this) {
+ mPaused = true;
+ while (mPaused) {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ // ignore
+ }
+ }
+ }
+ }
+
+ public void unpause() {
+ synchronized (this) {
+ mPaused = false;
+ notify();
+ }
+ }
+
+ public boolean isPaused() {
+ synchronized (this) {
+ return mPaused;
+ }
+ }
+}
diff --git a/org_apg/src/org/apg/PositionAwareInputStream.java b/org_apg/src/org/apg/PositionAwareInputStream.java
new file mode 100644
index 000000000..c59459670
--- /dev/null
+++ b/org_apg/src/org/apg/PositionAwareInputStream.java
@@ -0,0 +1,67 @@
+package org.apg;
+
+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/org_apg/src/org/apg/Preferences.java b/org_apg/src/org/apg/Preferences.java
new file mode 100644
index 000000000..f4800e064
--- /dev/null
+++ b/org_apg/src/org/apg/Preferences.java
@@ -0,0 +1,170 @@
+package org.apg;
+
+import org.spongycastle.bcpg.HashAlgorithmTags;
+import org.spongycastle.openpgp.PGPEncryptedData;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import java.util.Vector;
+
+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 int 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 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.SHA256);
+ }
+
+ 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 boolean hasSeenChangeLog(String version) {
+ return mSharedPreferences.getBoolean(Constants.pref.HAS_SEEN_CHANGE_LOG + version, false);
+ }
+
+ public void setHasSeenChangeLog(String version, boolean value) {
+ SharedPreferences.Editor editor = mSharedPreferences.edit();
+ editor.putBoolean(Constants.pref.HAS_SEEN_CHANGE_LOG + version, value);
+ editor.commit();
+ }
+
+ public boolean hasSeenHelp() {
+ return mSharedPreferences.getBoolean(Constants.pref.HAS_SEEN_HELP, false);
+ }
+
+ public void setHasSeenHelp(boolean value) {
+ SharedPreferences.Editor editor = mSharedPreferences.edit();
+ editor.putBoolean(Constants.pref.HAS_SEEN_HELP, 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/org_apg/src/org/apg/Primes.java b/org_apg/src/org/apg/Primes.java
new file mode 100644
index 000000000..1fd6482b2
--- /dev/null
+++ b/org_apg/src/org/apg/Primes.java
@@ -0,0 +1,185 @@
+/*
+ * 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.apg;
+
+import java.math.BigInteger;
+
+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/org_apg/src/org/apg/ProgressDialogUpdater.java b/org_apg/src/org/apg/ProgressDialogUpdater.java
new file mode 100644
index 000000000..043cca906
--- /dev/null
+++ b/org_apg/src/org/apg/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.apg;
+
+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/org_apg/src/org/apg/Service.java b/org_apg/src/org/apg/Service.java
new file mode 100644
index 000000000..24c4daa11
--- /dev/null
+++ b/org_apg/src/org/apg/Service.java
@@ -0,0 +1,78 @@
+package org.apg;
+
+import android.content.Intent;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+
+public class Service extends android.app.Service {
+ private final IBinder mBinder = new LocalBinder();
+
+ public static final String EXTRA_TTL = "ttl";
+
+ private int mPassPhraseCacheTtl = 15;
+ private Handler mCacheHandler = new Handler();
+ private Runnable mCacheTask = new Runnable() {
+ public void run() {
+ // check every ttl/2 seconds, which shouldn't be heavy on the device (even if ttl = 15),
+ // and makes sure the longest a pass phrase survives in the cache is 1.5 * ttl
+ int delay = mPassPhraseCacheTtl * 1000 / 2;
+ // also make sure the delay is not longer than one minute
+ if (delay > 60000) {
+ delay = 60000;
+ }
+
+ delay = Apg.cleanUpCache(mPassPhraseCacheTtl, delay);
+ // don't check too often, even if we were close
+ if (delay < 5000) {
+ delay = 5000;
+ }
+
+ mCacheHandler.postDelayed(this, delay);
+ }
+ };
+
+ static private boolean mIsRunning = false;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ mIsRunning = true;
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ mIsRunning = false;
+ }
+
+ @Override
+ public void onStart(Intent intent, int startId) {
+ super.onStart(intent, startId);
+
+ if (intent != null) {
+ mPassPhraseCacheTtl = intent.getIntExtra(EXTRA_TTL, 15);
+ }
+ if (mPassPhraseCacheTtl < 15) {
+ mPassPhraseCacheTtl = 15;
+ }
+ mCacheHandler.removeCallbacks(mCacheTask);
+ mCacheHandler.postDelayed(mCacheTask, 1000);
+ }
+
+ static public boolean isRunning() {
+ return mIsRunning;
+ }
+
+ public class LocalBinder extends Binder {
+ Service getService() {
+ return Service.this;
+ }
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mBinder;
+ }
+}
diff --git a/org_apg/src/org/apg/provider/Accounts.java b/org_apg/src/org/apg/provider/Accounts.java
new file mode 100644
index 000000000..b95f079df
--- /dev/null
+++ b/org_apg/src/org/apg/provider/Accounts.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apg.provider;
+
+import android.provider.BaseColumns;
+
+public class Accounts implements BaseColumns {
+ public static final String TABLE_NAME = "accounts";
+
+ public static final String _ID_type = "INTEGER PRIMARY KEY";
+ public static final String NAME = "c_name";
+ public static final String NAME_type = "TEXT";
+}
diff --git a/org_apg/src/org/apg/provider/ApgServiceBlobDatabase.java b/org_apg/src/org/apg/provider/ApgServiceBlobDatabase.java
new file mode 100644
index 000000000..70b9cd64f
--- /dev/null
+++ b/org_apg/src/org/apg/provider/ApgServiceBlobDatabase.java
@@ -0,0 +1,54 @@
+package org.apg.provider;
+
+import org.apg.ApgService;
+
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.net.Uri;
+import android.util.Log;
+
+public class ApgServiceBlobDatabase extends SQLiteOpenHelper {
+
+ private static final String TAG = "ApgServiceBlobDatabase";
+
+ private static final int VERSION = 1;
+ private static final String NAME = "apg_service_blob_data";
+ private static final String TABLE = "data";
+
+ public ApgServiceBlobDatabase(Context context) {
+ super(context, NAME, null, VERSION);
+ if(ApgService.LOCAL_LOGD) Log.d(TAG, "constructor called");
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ if(ApgService.LOCAL_LOGD) Log.d(TAG, "onCreate() called");
+ db.execSQL("create table " + TABLE + " ( _id integer primary key autoincrement," +
+ "key text not null)");
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ if(ApgService.LOCAL_LOGD) Log.d(TAG, "onUpgrade() called");
+ // no upgrade necessary yet
+ }
+
+ public Uri insert(ContentValues vals) {
+ if(ApgService.LOCAL_LOGD) Log.d(TAG, "insert() called");
+ SQLiteDatabase db = this.getWritableDatabase();
+ long newId = db.insert(TABLE, null, vals);
+ return ContentUris.withAppendedId(ApgServiceBlobProvider.CONTENT_URI, newId);
+ }
+
+ public Cursor query(String id, String key) {
+ if(ApgService.LOCAL_LOGD) Log.d(TAG, "query() called");
+ SQLiteDatabase db = this.getReadableDatabase();
+ return db.query(TABLE, new String[] {"_id"},
+ "_id = ? and key = ?", new String[] {id, key},
+ null, null, null);
+ }
+}
diff --git a/org_apg/src/org/apg/provider/ApgServiceBlobProvider.java b/org_apg/src/org/apg/provider/ApgServiceBlobProvider.java
new file mode 100644
index 000000000..f2d5377a4
--- /dev/null
+++ b/org_apg/src/org/apg/provider/ApgServiceBlobProvider.java
@@ -0,0 +1,138 @@
+package org.apg.provider;
+
+import org.apg.ApgService;
+import org.apg.Constants;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.List;
+import java.util.UUID;
+
+public class ApgServiceBlobProvider extends ContentProvider {
+
+ private static final String TAG = "ApgServiceBlobProvider";
+
+ public static final Uri CONTENT_URI = Uri.parse("content://org.thialfihar.android.apg.provider.apgserviceblobprovider");
+
+ private static final String COLUMN_KEY = "key";
+
+ private static final String STORE_PATH = Constants.path.APP_DIR+"/ApgServiceBlobs";
+
+ private ApgServiceBlobDatabase mDb = null;
+
+ public ApgServiceBlobProvider() {
+ if(ApgService.LOCAL_LOGD) Log.d(TAG, "Constructor called");
+ File dir = new File(STORE_PATH);
+ dir.mkdirs();
+ if(ApgService.LOCAL_LOGD) Log.d(TAG, "Constructor finished");
+ }
+
+ @Override
+ public int delete(Uri arg0, String arg1, String[] arg2) {
+ if(ApgService.LOCAL_LOGD) Log.d(TAG, "delete() called");
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ @Override
+ public String getType(Uri arg0) {
+ if(ApgService.LOCAL_LOGD) Log.d(TAG, "getType() called");
+ // not needed for now
+ return null;
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues ignored) {
+ if(ApgService.LOCAL_LOGD) Log.d(TAG, "insert() called");
+ // 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(COLUMN_KEY, password);
+
+ Uri insertedUri = mDb.insert(vals);
+ return Uri.withAppendedPath(insertedUri, password);
+ }
+
+ @Override
+ public boolean onCreate() {
+ if(ApgService.LOCAL_LOGD) Log.d(TAG, "onCreate() called");
+ mDb = new ApgServiceBlobDatabase(getContext());
+ // TODO Auto-generated method stub
+ return true;
+ }
+
+ @Override
+ public Cursor query(Uri arg0, String[] arg1, String arg2, String[] arg3, String arg4) {
+ if(ApgService.LOCAL_LOGD) Log.d(TAG, "query() called");
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public int update(Uri arg0, ContentValues arg1, String arg2, String[] arg3) {
+ if(ApgService.LOCAL_LOGD) Log.d(TAG, "update() called");
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ @Override
+ public ParcelFileDescriptor openFile(Uri uri, String mode) throws SecurityException, FileNotFoundException {
+ if(ApgService.LOCAL_LOGD) Log.d(TAG, "openFile() called");
+ if(ApgService.LOCAL_LOGD) Log.d(TAG, "... with uri: "+uri.toString());
+ if(ApgService.LOCAL_LOGD) Log.d(TAG, "... with 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);
+
+ if(ApgService.LOCAL_LOGD) Log.d(TAG, "... got id: "+id);
+ if(ApgService.LOCAL_LOGD) Log.d(TAG, "... and key: "+key);
+
+ // get the data
+ Cursor result = mDb.query(id, key);
+
+ 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")) {
+ if(ApgService.LOCAL_LOGD) Log.d(TAG, "... will try to open file w");
+ if( !targetFile.exists() ) {
+ try {
+ targetFile.createNewFile();
+ } catch (IOException e) {
+ Log.e(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")) {
+ if(ApgService.LOCAL_LOGD) Log.d(TAG, "... will 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;
+ }
+
+}
diff --git a/org_apg/src/org/apg/provider/DataProvider.java b/org_apg/src/org/apg/provider/DataProvider.java
new file mode 100644
index 000000000..a97851e58
--- /dev/null
+++ b/org_apg/src/org/apg/provider/DataProvider.java
@@ -0,0 +1,381 @@
+/*
+ * 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.apg.provider;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.HashMap;
+
+import org.apg.Id;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.UriMatcher;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import android.text.TextUtils;
+
+public class DataProvider extends ContentProvider {
+ public static final String AUTHORITY = "org.thialfihar.android.apg.provider";
+
+ private static final int PUBLIC_KEY_RING = 101;
+ private static final int PUBLIC_KEY_RING_ID = 102;
+ private static final int PUBLIC_KEY_RING_BY_KEY_ID = 103;
+ private static final int PUBLIC_KEY_RING_BY_EMAILS = 104;
+ private static final int PUBLIC_KEY_RING_KEY = 111;
+ private static final int PUBLIC_KEY_RING_KEY_RANK = 112;
+ private static final int PUBLIC_KEY_RING_USER_ID = 121;
+ private static final int PUBLIC_KEY_RING_USER_ID_RANK = 122;
+
+ private static final int SECRET_KEY_RING = 201;
+ private static final int SECRET_KEY_RING_ID = 202;
+ private static final int SECRET_KEY_RING_BY_KEY_ID = 203;
+ private static final int SECRET_KEY_RING_BY_EMAILS = 204;
+ private static final int SECRET_KEY_RING_KEY = 211;
+ private static final int SECRET_KEY_RING_KEY_RANK = 212;
+ private static final int SECRET_KEY_RING_USER_ID = 221;
+ private static final int SECRET_KEY_RING_USER_ID_RANK = 222;
+
+ private static final int DATA_STREAM = 301;
+
+ private static final String PUBLIC_KEY_RING_CONTENT_DIR_TYPE =
+ "vnd.android.cursor.dir/vnd.thialfihar.apg.public.key_ring";
+ private static final String PUBLIC_KEY_RING_CONTENT_ITEM_TYPE =
+ "vnd.android.cursor.item/vnd.thialfihar.apg.public.key_ring";
+
+ private static final String PUBLIC_KEY_CONTENT_DIR_TYPE =
+ "vnd.android.cursor.dir/vnd.thialfihar.apg.public.key";
+ private static final String PUBLIC_KEY_CONTENT_ITEM_TYPE =
+ "vnd.android.cursor.item/vnd.thialfihar.apg.public.key";
+
+ private static final String SECRET_KEY_RING_CONTENT_DIR_TYPE =
+ "vnd.android.cursor.dir/vnd.thialfihar.apg.secret.key_ring";
+ private static final String SECRET_KEY_RING_CONTENT_ITEM_TYPE =
+ "vnd.android.cursor.item/vnd.thialfihar.apg.secret.key_ring";
+
+ private static final String SECRET_KEY_CONTENT_DIR_TYPE =
+ "vnd.android.cursor.dir/vnd.thialfihar.apg.secret.key";
+ private static final String SECRET_KEY_CONTENT_ITEM_TYPE =
+ "vnd.android.cursor.item/vnd.thialfihar.apg.secret.key";
+
+ private static final String USER_ID_CONTENT_DIR_TYPE =
+ "vnd.android.cursor.dir/vnd.thialfihar.apg.user_id";
+ private static final String USER_ID_CONTENT_ITEM_TYPE =
+ "vnd.android.cursor.item/vnd.thialfihar.apg.user_id";
+
+ public static final String _ID = "_id";
+ public static final String MASTER_KEY_ID = "master_key_id";
+ public static final String KEY_ID = "key_id";
+ public static final String USER_ID = "user_id";
+
+ private static final UriMatcher mUriMatcher;
+
+ private Database mDb;
+
+ static {
+ mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
+ mUriMatcher.addURI(AUTHORITY, "key_rings/public/key_id/*", PUBLIC_KEY_RING_BY_KEY_ID);
+ mUriMatcher.addURI(AUTHORITY, "key_rings/public/emails/*", PUBLIC_KEY_RING_BY_EMAILS);
+
+ mUriMatcher.addURI(AUTHORITY, "key_rings/public/*/keys", PUBLIC_KEY_RING_KEY);
+ mUriMatcher.addURI(AUTHORITY, "key_rings/public/*/keys/#", PUBLIC_KEY_RING_KEY_RANK);
+
+ mUriMatcher.addURI(AUTHORITY, "key_rings/public/*/user_ids", PUBLIC_KEY_RING_USER_ID);
+ mUriMatcher.addURI(AUTHORITY, "key_rings/public/*/user_ids/#", PUBLIC_KEY_RING_USER_ID_RANK);
+
+ mUriMatcher.addURI(AUTHORITY, "key_rings/public", PUBLIC_KEY_RING);
+ mUriMatcher.addURI(AUTHORITY, "key_rings/public/*", PUBLIC_KEY_RING_ID);
+
+ mUriMatcher.addURI(AUTHORITY, "key_rings/secret/key_id/*", SECRET_KEY_RING_BY_KEY_ID);
+ mUriMatcher.addURI(AUTHORITY, "key_rings/secret/emails/*", SECRET_KEY_RING_BY_EMAILS);
+
+ mUriMatcher.addURI(AUTHORITY, "key_rings/secret/*/keys", SECRET_KEY_RING_KEY);
+ mUriMatcher.addURI(AUTHORITY, "key_rings/secret/*/keys/#", SECRET_KEY_RING_KEY_RANK);
+
+ mUriMatcher.addURI(AUTHORITY, "key_rings/secret/*/user_ids", SECRET_KEY_RING_USER_ID);
+ mUriMatcher.addURI(AUTHORITY, "key_rings/secret/*/user_ids/#", SECRET_KEY_RING_USER_ID_RANK);
+
+ mUriMatcher.addURI(AUTHORITY, "key_rings/secret", SECRET_KEY_RING);
+ mUriMatcher.addURI(AUTHORITY, "key_rings/secret/*", SECRET_KEY_RING_ID);
+
+ mUriMatcher.addURI(AUTHORITY, "data/*", DATA_STREAM);
+ }
+
+ @Override
+ public boolean onCreate() {
+ mDb = new Database(getContext());
+ return true;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder) {
+ // TODO: implement the others, then use them for the lists
+ SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+ HashMap<String, String> projectionMap = new HashMap<String, String>();
+
+ int match = mUriMatcher.match(uri);
+ int type;
+ switch (match) {
+ case PUBLIC_KEY_RING:
+ case PUBLIC_KEY_RING_ID:
+ case PUBLIC_KEY_RING_BY_KEY_ID:
+ case PUBLIC_KEY_RING_BY_EMAILS:
+ case PUBLIC_KEY_RING_KEY:
+ case PUBLIC_KEY_RING_KEY_RANK:
+ case PUBLIC_KEY_RING_USER_ID:
+ case PUBLIC_KEY_RING_USER_ID_RANK:
+ type = Id.database.type_public;
+ break;
+
+ case SECRET_KEY_RING:
+ case SECRET_KEY_RING_ID:
+ case SECRET_KEY_RING_BY_KEY_ID:
+ case SECRET_KEY_RING_BY_EMAILS:
+ case SECRET_KEY_RING_KEY:
+ case SECRET_KEY_RING_KEY_RANK:
+ case SECRET_KEY_RING_USER_ID:
+ case SECRET_KEY_RING_USER_ID_RANK:
+ type = Id.database.type_secret;
+ break;
+
+ default: {
+ throw new IllegalArgumentException("Unknown URI " + uri);
+ }
+ }
+
+ qb.appendWhere(KeyRings.TABLE_NAME + "." + KeyRings.TYPE + " = " + type);
+
+ switch (match) {
+ case PUBLIC_KEY_RING_ID:
+ case SECRET_KEY_RING_ID: {
+ qb.appendWhere(" AND " +
+ KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID + " = ");
+ qb.appendWhereEscapeString(uri.getPathSegments().get(2));
+
+ // break omitted intentionally
+ }
+
+ case PUBLIC_KEY_RING:
+ case SECRET_KEY_RING: {
+ qb.setTables(KeyRings.TABLE_NAME + " INNER JOIN " + Keys.TABLE_NAME + " ON " +
+ "(" + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " +
+ Keys.TABLE_NAME + "." + Keys.KEY_RING_ID + " AND " +
+ Keys.TABLE_NAME + "." + Keys.IS_MASTER_KEY + " = '1'" +
+ ") " +
+ " INNER JOIN " + UserIds.TABLE_NAME + " ON " +
+ "(" + Keys.TABLE_NAME + "." + Keys._ID + " = " +
+ UserIds.TABLE_NAME + "." + UserIds.KEY_ID + " AND " +
+ UserIds.TABLE_NAME + "." + UserIds.RANK + " = '0') ");
+
+ projectionMap.put(_ID,
+ KeyRings.TABLE_NAME + "." + KeyRings._ID);
+ projectionMap.put(MASTER_KEY_ID,
+ KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID);
+ projectionMap.put(USER_ID,
+ UserIds.TABLE_NAME + "." + UserIds.USER_ID);
+
+ if (TextUtils.isEmpty(sortOrder)) {
+ sortOrder = UserIds.TABLE_NAME + "." + UserIds.USER_ID + " ASC";
+ }
+
+ break;
+ }
+
+ case SECRET_KEY_RING_BY_KEY_ID:
+ case PUBLIC_KEY_RING_BY_KEY_ID: {
+ qb.setTables(Keys.TABLE_NAME + " AS tmp INNER JOIN " +
+ KeyRings.TABLE_NAME + " ON (" +
+ KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " +
+ "tmp." + Keys.KEY_RING_ID + ")" +
+ " INNER JOIN " + Keys.TABLE_NAME + " ON " +
+ "(" + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " +
+ Keys.TABLE_NAME + "." + Keys.KEY_RING_ID + " AND " +
+ Keys.TABLE_NAME + "." + Keys.IS_MASTER_KEY + " = '1'" +
+ ") " +
+ " INNER JOIN " + UserIds.TABLE_NAME + " ON " +
+ "(" + Keys.TABLE_NAME + "." + Keys._ID + " = " +
+ UserIds.TABLE_NAME + "." + UserIds.KEY_ID + " AND " +
+ UserIds.TABLE_NAME + "." + UserIds.RANK + " = '0') ");
+
+ projectionMap.put(_ID,
+ KeyRings.TABLE_NAME + "." + KeyRings._ID);
+ projectionMap.put(MASTER_KEY_ID,
+ KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID);
+ projectionMap.put(USER_ID,
+ UserIds.TABLE_NAME + "." + UserIds.USER_ID);
+
+ qb.appendWhere(" AND tmp." + Keys.KEY_ID + " = ");
+ qb.appendWhereEscapeString(uri.getPathSegments().get(3));
+
+ break;
+ }
+
+ case SECRET_KEY_RING_BY_EMAILS:
+ case PUBLIC_KEY_RING_BY_EMAILS: {
+ qb.setTables(KeyRings.TABLE_NAME + " INNER JOIN " + Keys.TABLE_NAME + " ON " +
+ "(" + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " +
+ Keys.TABLE_NAME + "." + Keys.KEY_RING_ID + " AND " +
+ Keys.TABLE_NAME + "." + Keys.IS_MASTER_KEY + " = '1'" +
+ ") " +
+ " INNER JOIN " + UserIds.TABLE_NAME + " ON " +
+ "(" + Keys.TABLE_NAME + "." + Keys._ID + " = " +
+ UserIds.TABLE_NAME + "." + UserIds.KEY_ID + " AND " +
+ UserIds.TABLE_NAME + "." + UserIds.RANK + " = '0') ");
+
+ projectionMap.put(_ID,
+ KeyRings.TABLE_NAME + "." + KeyRings._ID);
+ projectionMap.put(MASTER_KEY_ID,
+ KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID);
+ projectionMap.put(USER_ID,
+ UserIds.TABLE_NAME + "." + UserIds.USER_ID);
+
+ String emails = uri.getPathSegments().get(3);
+ 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." + UserIds.USER_ID + " LIKE ";
+ // match '*<email>', so it has to be at the *end* of the user id
+ emailWhere += DatabaseUtils.sqlEscapeString("%<" + chunks[i] + ">");
+ gotCondition = true;
+ }
+
+ if (gotCondition) {
+ qb.appendWhere(" AND EXISTS (SELECT tmp." + UserIds._ID +
+ " FROM " + UserIds.TABLE_NAME +
+ " AS tmp WHERE tmp." + UserIds.KEY_ID + " = " +
+ Keys.TABLE_NAME + "." + Keys._ID +
+ " AND (" + emailWhere + "))");
+ }
+
+ break;
+ }
+
+ default: {
+ throw new IllegalArgumentException("Unknown URI " + uri);
+ }
+ }
+
+ qb.setProjectionMap(projectionMap);
+
+ // If no sort order is specified use the default
+ String orderBy;
+ if (TextUtils.isEmpty(sortOrder)) {
+ orderBy = null;
+ } else {
+ orderBy = sortOrder;
+ }
+
+ //System.out.println(qb.buildQuery(projection, selection, selectionArgs, null, null, sortOrder, null).replace("WHERE", "WHERE\n"));
+ Cursor c = qb.query(mDb.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);
+ return c;
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ switch (mUriMatcher.match(uri)) {
+ case PUBLIC_KEY_RING:
+ case PUBLIC_KEY_RING_BY_EMAILS:
+ return PUBLIC_KEY_RING_CONTENT_DIR_TYPE;
+
+ case PUBLIC_KEY_RING_ID:
+ return PUBLIC_KEY_RING_CONTENT_ITEM_TYPE;
+
+ case PUBLIC_KEY_RING_BY_KEY_ID:
+ return PUBLIC_KEY_RING_CONTENT_ITEM_TYPE;
+
+ case PUBLIC_KEY_RING_KEY:
+ return PUBLIC_KEY_CONTENT_DIR_TYPE;
+
+ case PUBLIC_KEY_RING_KEY_RANK:
+ return PUBLIC_KEY_CONTENT_ITEM_TYPE;
+
+ case PUBLIC_KEY_RING_USER_ID:
+ return USER_ID_CONTENT_DIR_TYPE;
+
+ case PUBLIC_KEY_RING_USER_ID_RANK:
+ return USER_ID_CONTENT_ITEM_TYPE;
+
+ case SECRET_KEY_RING:
+ case SECRET_KEY_RING_BY_EMAILS:
+ return SECRET_KEY_RING_CONTENT_DIR_TYPE;
+
+ case SECRET_KEY_RING_ID:
+ return SECRET_KEY_RING_CONTENT_ITEM_TYPE;
+
+ case SECRET_KEY_RING_BY_KEY_ID:
+ return SECRET_KEY_RING_CONTENT_ITEM_TYPE;
+
+ case SECRET_KEY_RING_KEY:
+ return SECRET_KEY_CONTENT_DIR_TYPE;
+
+ case SECRET_KEY_RING_KEY_RANK:
+ return SECRET_KEY_CONTENT_ITEM_TYPE;
+
+ case SECRET_KEY_RING_USER_ID:
+ return USER_ID_CONTENT_DIR_TYPE;
+
+ case SECRET_KEY_RING_USER_ID_RANK:
+ return USER_ID_CONTENT_ITEM_TYPE;
+
+ default:
+ throw new IllegalArgumentException("Unknown URI " + uri);
+ }
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues initialValues) {
+ // not supported
+ return null;
+ }
+
+ @Override
+ public int delete(Uri uri, String where, String[] whereArgs) {
+ // not supported
+ return 0;
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String where, String[] whereArgs) {
+ // not supported
+ return 0;
+ }
+
+ @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.getPathSegments().get(1);
+ File file = new File(getContext().getFilesDir().getAbsolutePath(), fileName);
+ return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
+ }
+}
diff --git a/org_apg/src/org/apg/provider/Database.java b/org_apg/src/org/apg/provider/Database.java
new file mode 100644
index 000000000..8b1aaa9e7
--- /dev/null
+++ b/org_apg/src/org/apg/provider/Database.java
@@ -0,0 +1,605 @@
+package org.apg.provider;
+
+import org.apg.Apg;
+import org.apg.Id;
+import org.apg.util.IterableIterator;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPPublicKey;
+import org.spongycastle.openpgp.PGPPublicKeyRing;
+import org.spongycastle.openpgp.PGPSecretKey;
+import org.spongycastle.openpgp.PGPSecretKeyRing;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Vector;
+
+public class Database extends SQLiteOpenHelper {
+ public static class GeneralException extends Exception {
+ static final long serialVersionUID = 0xf812773343L;
+
+ public GeneralException(String message) {
+ super(message);
+ }
+ }
+
+ private static final String DATABASE_NAME = "apg";
+ private static final int DATABASE_VERSION = 2;
+
+ public static final String AUTHORITY = "org.thialfihar.android.apg.database";
+
+ public static HashMap<String, String> sKeyRingsProjection;
+ public static HashMap<String, String> sKeysProjection;
+ public static HashMap<String, String> sUserIdsProjection;
+
+ private SQLiteDatabase mDb = null;
+ private int mStatus = 0;
+
+ static {
+ sKeyRingsProjection = new HashMap<String, String>();
+ sKeyRingsProjection.put(KeyRings._ID, KeyRings._ID);
+ sKeyRingsProjection.put(KeyRings.MASTER_KEY_ID, KeyRings.MASTER_KEY_ID);
+ sKeyRingsProjection.put(KeyRings.TYPE, KeyRings.TYPE);
+ sKeyRingsProjection.put(KeyRings.WHO_ID, KeyRings.WHO_ID);
+ sKeyRingsProjection.put(KeyRings.KEY_RING_DATA, KeyRings.KEY_RING_DATA);
+
+ sKeysProjection = new HashMap<String, String>();
+ sKeysProjection.put(Keys._ID, Keys._ID);
+ sKeysProjection.put(Keys.KEY_ID, Keys.KEY_ID);
+ sKeysProjection.put(Keys.TYPE, Keys.TYPE);
+ sKeysProjection.put(Keys.IS_MASTER_KEY, Keys.IS_MASTER_KEY);
+ sKeysProjection.put(Keys.ALGORITHM, Keys.ALGORITHM);
+ sKeysProjection.put(Keys.KEY_SIZE, Keys.KEY_SIZE);
+ sKeysProjection.put(Keys.CAN_SIGN, Keys.CAN_SIGN);
+ sKeysProjection.put(Keys.CAN_ENCRYPT, Keys.CAN_ENCRYPT);
+ sKeysProjection.put(Keys.IS_REVOKED, Keys.IS_REVOKED);
+ sKeysProjection.put(Keys.CREATION, Keys.CREATION);
+ sKeysProjection.put(Keys.EXPIRY, Keys.EXPIRY);
+ sKeysProjection.put(Keys.KEY_DATA, Keys.KEY_DATA);
+ sKeysProjection.put(Keys.RANK, Keys.RANK);
+
+ sUserIdsProjection = new HashMap<String, String>();
+ sUserIdsProjection.put(UserIds._ID, UserIds._ID);
+ sUserIdsProjection.put(UserIds.KEY_ID, UserIds.KEY_ID);
+ sUserIdsProjection.put(UserIds.USER_ID, UserIds.USER_ID);
+ sUserIdsProjection.put(UserIds.RANK, UserIds.RANK);
+ }
+
+ public Database(Context context) {
+ super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ // force upgrade to test things
+ //onUpgrade(getWritableDatabase(), 1, 2);
+ mDb = getWritableDatabase();
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ mDb.close();
+ super.finalize();
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ db.execSQL("CREATE TABLE " + KeyRings.TABLE_NAME + " (" +
+ KeyRings._ID + " " + KeyRings._ID_type + "," +
+ KeyRings.MASTER_KEY_ID + " " + KeyRings.MASTER_KEY_ID_type + ", " +
+ KeyRings.TYPE + " " + KeyRings.TYPE_type + ", " +
+ KeyRings.WHO_ID + " " + KeyRings.WHO_ID_type + ", " +
+ KeyRings.KEY_RING_DATA + " " + KeyRings.KEY_RING_DATA_type + ");");
+
+ db.execSQL("CREATE TABLE " + Keys.TABLE_NAME + " (" +
+ Keys._ID + " " + Keys._ID_type + "," +
+ Keys.KEY_ID + " " + Keys.KEY_ID_type + ", " +
+ Keys.TYPE + " " + Keys.TYPE_type + ", " +
+ Keys.IS_MASTER_KEY + " " + Keys.IS_MASTER_KEY_type + ", " +
+ Keys.ALGORITHM + " " + Keys.ALGORITHM_type + ", " +
+ Keys.KEY_SIZE + " " + Keys.KEY_SIZE_type + ", " +
+ Keys.CAN_SIGN + " " + Keys.CAN_SIGN_type + ", " +
+ Keys.CAN_ENCRYPT + " " + Keys.CAN_ENCRYPT_type + ", " +
+ Keys.IS_REVOKED + " " + Keys.IS_REVOKED_type + ", " +
+ Keys.CREATION + " " + Keys.CREATION_type + ", " +
+ Keys.EXPIRY + " " + Keys.EXPIRY_type + ", " +
+ Keys.KEY_RING_ID + " " + Keys.KEY_RING_ID_type + ", " +
+ Keys.KEY_DATA + " " + Keys.KEY_DATA_type +
+ Keys.RANK + " " + Keys.RANK_type + ");");
+
+ db.execSQL("CREATE TABLE " + UserIds.TABLE_NAME + " (" +
+ UserIds._ID + " " + UserIds._ID_type + "," +
+ UserIds.KEY_ID + " " + UserIds.KEY_ID_type + "," +
+ UserIds.USER_ID + " " + UserIds.USER_ID_type + "," +
+ UserIds.RANK + " " + UserIds.RANK_type + ");");
+
+ db.execSQL("CREATE TABLE " + Accounts.TABLE_NAME + " (" +
+ Accounts._ID + " " + Accounts._ID_type + "," +
+ Accounts.NAME + " " + Accounts.NAME_type + ");");
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ mDb = db;
+ for (int version = oldVersion; version < newVersion; ++version) {
+ switch (version) {
+ case 1: { // upgrade 1 to 2
+ db.execSQL("DROP TABLE IF EXISTS " + KeyRings.TABLE_NAME + ";");
+ db.execSQL("DROP TABLE IF EXISTS " + Keys.TABLE_NAME + ";");
+ db.execSQL("DROP TABLE IF EXISTS " + UserIds.TABLE_NAME + ";");
+
+ db.execSQL("CREATE TABLE " + KeyRings.TABLE_NAME + " (" +
+ KeyRings._ID + " " + KeyRings._ID_type + "," +
+ KeyRings.MASTER_KEY_ID + " " + KeyRings.MASTER_KEY_ID_type + ", " +
+ KeyRings.TYPE + " " + KeyRings.TYPE_type + ", " +
+ KeyRings.WHO_ID + " " + KeyRings.WHO_ID_type + ", " +
+ KeyRings.KEY_RING_DATA + " " + KeyRings.KEY_RING_DATA_type + ");");
+
+ db.execSQL("CREATE TABLE " + Keys.TABLE_NAME + " (" +
+ Keys._ID + " " + Keys._ID_type + "," +
+ Keys.KEY_ID + " " + Keys.KEY_ID_type + ", " +
+ Keys.TYPE + " " + Keys.TYPE_type + ", " +
+ Keys.IS_MASTER_KEY + " " + Keys.IS_MASTER_KEY_type + ", " +
+ Keys.ALGORITHM + " " + Keys.ALGORITHM_type + ", " +
+ Keys.KEY_SIZE + " " + Keys.KEY_SIZE_type + ", " +
+ Keys.CAN_SIGN + " " + Keys.CAN_SIGN_type + ", " +
+ Keys.CAN_ENCRYPT + " " + Keys.CAN_ENCRYPT_type + ", " +
+ Keys.IS_REVOKED + " " + Keys.IS_REVOKED_type + ", " +
+ Keys.CREATION + " " + Keys.CREATION_type + ", " +
+ Keys.EXPIRY + " " + Keys.EXPIRY_type + ", " +
+ Keys.KEY_RING_ID + " " + Keys.KEY_RING_ID_type + ", " +
+ Keys.KEY_DATA + " " + Keys.KEY_DATA_type +
+ Keys.RANK + " " + Keys.RANK_type + ");");
+
+ db.execSQL("CREATE TABLE " + UserIds.TABLE_NAME + " (" +
+ UserIds._ID + " " + UserIds._ID_type + "," +
+ UserIds.KEY_ID + " " + UserIds.KEY_ID_type + "," +
+ UserIds.USER_ID + " " + UserIds.USER_ID_type + "," +
+ UserIds.RANK + " " + UserIds.RANK_type + ");");
+
+ Cursor cursor = db.query("public_keys", new String[] { "c_key_data" },
+ null, null, null, null, null);
+ if (cursor != null && cursor.moveToFirst()) {
+ do {
+ byte[] data = cursor.getBlob(0);
+ try {
+ PGPPublicKeyRing keyRing = new PGPPublicKeyRing(data);
+ saveKeyRing(keyRing);
+ } catch (IOException e) {
+ Log.e("apg.db.upgrade", "key import failed: " + e);
+ } catch (GeneralException e) {
+ Log.e("apg.db.upgrade", "key import failed: " + e);
+ }
+ } while (cursor.moveToNext());
+ }
+
+ if (cursor != null) {
+ cursor.close();
+ }
+
+ cursor = db.query("secret_keys", new String[]{ "c_key_data" },
+ null, null, null, null, null);
+ if (cursor != null && cursor.moveToFirst()) {
+ do {
+ byte[] data = cursor.getBlob(0);
+ try {
+ PGPSecretKeyRing keyRing = new PGPSecretKeyRing(data);
+ saveKeyRing(keyRing);
+ } catch (IOException e) {
+ Log.e("apg.db.upgrade", "key import failed: " + e);
+ } catch (PGPException e) {
+ Log.e("apg.db.upgrade", "key import failed: " + e);
+ } catch (GeneralException e) {
+ Log.e("apg.db.upgrade", "key import failed: " + e);
+ }
+ } while (cursor.moveToNext());
+ }
+
+ if (cursor != null) {
+ cursor.close();
+ }
+
+ db.execSQL("DROP TABLE IF EXISTS public_keys;");
+ db.execSQL("DROP TABLE IF EXISTS secret_keys;");
+
+ break;
+ }
+
+ default: {
+ break;
+ }
+ }
+ }
+ mDb = null;
+ }
+
+ public int saveKeyRing(PGPPublicKeyRing keyRing) throws IOException, GeneralException {
+ mDb.beginTransaction();
+ ContentValues values = new ContentValues();
+ PGPPublicKey masterKey = keyRing.getPublicKey();
+ long masterKeyId = masterKey.getKeyID();
+
+ values.put(KeyRings.MASTER_KEY_ID, masterKeyId);
+ values.put(KeyRings.TYPE, Id.database.type_public);
+ values.put(KeyRings.KEY_RING_DATA, keyRing.getEncoded());
+
+ long rowId = insertOrUpdateKeyRing(values);
+ int returnValue = mStatus;
+
+ if (rowId == -1) {
+ throw new GeneralException("saving public key ring " + masterKeyId + " failed");
+ }
+
+ Vector<Integer> seenIds = new Vector<Integer>();
+ int rank = 0;
+ for (PGPPublicKey key : new IterableIterator<PGPPublicKey>(keyRing.getPublicKeys())) {
+ seenIds.add(saveKey(rowId, key, rank));
+ ++rank;
+ }
+
+ String seenIdsStr = "";
+ for (Integer id : seenIds) {
+ if (seenIdsStr.length() > 0) {
+ seenIdsStr += ",";
+ }
+ seenIdsStr += id;
+ }
+ mDb.delete(Keys.TABLE_NAME,
+ Keys.KEY_RING_ID + " = ? AND " +
+ Keys._ID + " NOT IN (" + seenIdsStr + ")",
+ new String[] { "" + rowId });
+
+ mDb.setTransactionSuccessful();
+ mDb.endTransaction();
+ return returnValue;
+ }
+
+ public int saveKeyRing(PGPSecretKeyRing keyRing) throws IOException, GeneralException {
+ mDb.beginTransaction();
+ ContentValues values = new ContentValues();
+ PGPSecretKey masterKey = keyRing.getSecretKey();
+ long masterKeyId = masterKey.getKeyID();
+
+ values.put(KeyRings.MASTER_KEY_ID, masterKeyId);
+ values.put(KeyRings.TYPE, Id.database.type_secret);
+ values.put(KeyRings.KEY_RING_DATA, keyRing.getEncoded());
+
+ long rowId = insertOrUpdateKeyRing(values);
+ int returnValue = mStatus;
+
+ if (rowId == -1) {
+ throw new GeneralException("saving secret key ring " + masterKeyId + " failed");
+ }
+
+ Vector<Integer> seenIds = new Vector<Integer>();
+ int rank = 0;
+ for (PGPSecretKey key : new IterableIterator<PGPSecretKey>(keyRing.getSecretKeys())) {
+ seenIds.add(saveKey(rowId, key, rank));
+ ++rank;
+ }
+
+ String seenIdsStr = "";
+ for (Integer id : seenIds) {
+ if (seenIdsStr.length() > 0) {
+ seenIdsStr += ",";
+ }
+ seenIdsStr += id;
+ }
+ mDb.delete(Keys.TABLE_NAME,
+ Keys.KEY_RING_ID + " = ? AND " +
+ Keys._ID + " NOT IN (" + seenIdsStr + ")",
+ new String[] { "" + rowId });
+
+ mDb.setTransactionSuccessful();
+ mDb.endTransaction();
+ return returnValue;
+ }
+
+ private int saveKey(long keyRingId, PGPPublicKey key, int rank)
+ throws IOException, GeneralException {
+ ContentValues values = new ContentValues();
+
+ values.put(Keys.KEY_ID, key.getKeyID());
+ values.put(Keys.TYPE, Id.database.type_public);
+ 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, Apg.isSigningKey(key));
+ values.put(Keys.CAN_ENCRYPT, Apg.isEncryptionKey(key));
+ values.put(Keys.IS_REVOKED, key.isRevoked());
+ values.put(Keys.CREATION, Apg.getCreationDate(key).getTime() / 1000);
+ Date expiryDate = Apg.getExpiryDate(key);
+ if (expiryDate != null) {
+ values.put(Keys.EXPIRY, expiryDate.getTime() / 1000);
+ }
+ values.put(Keys.KEY_RING_ID, keyRingId);
+ values.put(Keys.KEY_DATA, key.getEncoded());
+ values.put(Keys.RANK, rank);
+
+ long rowId = insertOrUpdateKey(values);
+
+ if (rowId == -1) {
+ throw new GeneralException("saving public key " + key.getKeyID() + " failed");
+ }
+
+ Vector<Integer> seenIds = new Vector<Integer>();
+ int userIdRank = 0;
+ for (String userId : new IterableIterator<String>(key.getUserIDs())) {
+ seenIds.add(saveUserId(rowId, userId, userIdRank));
+ ++userIdRank;
+ }
+
+ String seenIdsStr = "";
+ for (Integer id : seenIds) {
+ if (seenIdsStr.length() > 0) {
+ seenIdsStr += ",";
+ }
+ seenIdsStr += id;
+ }
+ mDb.delete(UserIds.TABLE_NAME,
+ UserIds.KEY_ID + " = ? AND " +
+ UserIds._ID + " NOT IN (" + seenIdsStr + ")",
+ new String[] { "" + rowId });
+
+ return (int)rowId;
+ }
+
+ private int saveKey(long keyRingId, PGPSecretKey key, int rank)
+ throws IOException, GeneralException {
+ ContentValues values = new ContentValues();
+
+ values.put(Keys.KEY_ID, key.getPublicKey().getKeyID());
+ values.put(Keys.TYPE, Id.database.type_secret);
+ 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_SIGN, Apg.isSigningKey(key));
+ values.put(Keys.CAN_ENCRYPT, Apg.isEncryptionKey(key));
+ values.put(Keys.IS_REVOKED, key.getPublicKey().isRevoked());
+ values.put(Keys.CREATION, Apg.getCreationDate(key).getTime() / 1000);
+ Date expiryDate = Apg.getExpiryDate(key);
+ if (expiryDate != null) {
+ values.put(Keys.EXPIRY, expiryDate.getTime() / 1000);
+ }
+ values.put(Keys.KEY_RING_ID, keyRingId);
+ values.put(Keys.KEY_DATA, key.getEncoded());
+ values.put(Keys.RANK, rank);
+
+ long rowId = insertOrUpdateKey(values);
+
+ if (rowId == -1) {
+ throw new GeneralException("saving secret key " + key.getPublicKey().getKeyID() + " failed");
+ }
+
+ Vector<Integer> seenIds = new Vector<Integer>();
+ int userIdRank = 0;
+ for (String userId : new IterableIterator<String>(key.getUserIDs())) {
+ seenIds.add(saveUserId(rowId, userId, userIdRank));
+ ++userIdRank;
+ }
+
+ String seenIdsStr = "";
+ for (Integer id : seenIds) {
+ if (seenIdsStr.length() > 0) {
+ seenIdsStr += ",";
+ }
+ seenIdsStr += id;
+ }
+ mDb.delete(UserIds.TABLE_NAME,
+ UserIds.KEY_ID + " = ? AND " +
+ UserIds._ID + " NOT IN (" + seenIdsStr + ")",
+ new String[] { "" + rowId });
+
+ return (int)rowId;
+ }
+
+ private int saveUserId(long keyId, String userId, int rank) throws GeneralException {
+ ContentValues values = new ContentValues();
+
+ values.put(UserIds.KEY_ID, keyId);
+ values.put(UserIds.USER_ID, userId);
+ values.put(UserIds.RANK, rank);
+
+ long rowId = insertOrUpdateUserId(values);
+
+ if (rowId == -1) {
+ throw new GeneralException("saving user id " + userId + " failed");
+ }
+
+ return (int)rowId;
+ }
+
+ private long insertOrUpdateKeyRing(ContentValues values) {
+ Cursor c = mDb.query(KeyRings.TABLE_NAME, new String[] { KeyRings._ID },
+ KeyRings.MASTER_KEY_ID + " = ? AND " + KeyRings.TYPE + " = ?",
+ new String[] {
+ values.getAsString(KeyRings.MASTER_KEY_ID),
+ values.getAsString(KeyRings.TYPE),
+ },
+ null, null, null);
+ long rowId = -1;
+ if (c != null && c.moveToFirst()) {
+ rowId = c.getLong(0);
+ mDb.update(KeyRings.TABLE_NAME, values,
+ KeyRings._ID + " = ?", new String[] { "" + rowId });
+ mStatus = Id.return_value.updated;
+ } else {
+ rowId = mDb.insert(KeyRings.TABLE_NAME, KeyRings.WHO_ID, values);
+ mStatus = Id.return_value.ok;
+ }
+
+ if (c != null) {
+ c.close();
+ }
+
+ return rowId;
+ }
+
+ private long insertOrUpdateKey(ContentValues values) {
+ Cursor c = mDb.query(Keys.TABLE_NAME, new String[] { Keys._ID },
+ Keys.KEY_ID + " = ? AND " + Keys.TYPE + " = ?",
+ new String[] {
+ values.getAsString(Keys.KEY_ID),
+ values.getAsString(Keys.TYPE),
+ },
+ null, null, null);
+ long rowId = -1;
+ if (c != null && c.moveToFirst()) {
+ rowId = c.getLong(0);
+ mDb.update(Keys.TABLE_NAME, values,
+ Keys._ID + " = ?", new String[] { "" + rowId });
+ } else {
+ rowId = mDb.insert(Keys.TABLE_NAME, Keys.KEY_DATA, values);
+ }
+
+ if (c != null) {
+ c.close();
+ }
+
+ return rowId;
+ }
+
+ private long insertOrUpdateUserId(ContentValues values) {
+ Cursor c = mDb.query(UserIds.TABLE_NAME, new String[] { UserIds._ID },
+ UserIds.KEY_ID + " = ? AND " + UserIds.USER_ID + " = ?",
+ new String[] {
+ values.getAsString(UserIds.KEY_ID),
+ values.getAsString(UserIds.USER_ID),
+ },
+ null, null, null);
+ long rowId = -1;
+ if (c != null && c.moveToFirst()) {
+ rowId = c.getLong(0);
+ mDb.update(UserIds.TABLE_NAME, values,
+ UserIds._ID + " = ?", new String[] { "" + rowId });
+ } else {
+ rowId = mDb.insert(UserIds.TABLE_NAME, UserIds.USER_ID, values);
+ }
+
+ if (c != null) {
+ c.close();
+ }
+
+ return rowId;
+ }
+
+ public Object getKeyRing(int keyRingId) {
+ Cursor c = mDb.query(KeyRings.TABLE_NAME,
+ new String[] { KeyRings.KEY_RING_DATA, KeyRings.TYPE },
+ KeyRings._ID + " = ?",
+ new String[] {
+ "" + keyRingId,
+ },
+ null, null, null);
+ byte[] data = null;
+ Object keyRing = null;
+ if (c != null && c.moveToFirst()) {
+ data = c.getBlob(0);
+ if (data != null) {
+ try {
+ if (c.getInt(1) == Id.database.type_public) {
+ keyRing = new PGPPublicKeyRing(data);
+ } else {
+ keyRing = new PGPSecretKeyRing(data);
+ }
+ } catch (IOException e) {
+ // can't load it, then
+ } catch (PGPException e) {
+ // can't load it, then
+ }
+ }
+ }
+
+ if (c != null) {
+ c.close();
+ }
+
+ return keyRing;
+ }
+
+ public byte[] getKeyRingDataFromKeyId(int type, long keyId) {
+ Cursor c = mDb.query(Keys.TABLE_NAME + " INNER JOIN " + KeyRings.TABLE_NAME + " ON (" +
+ KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " +
+ Keys.TABLE_NAME + "." + Keys.KEY_RING_ID + ")",
+ new String[] { KeyRings.TABLE_NAME + "." + KeyRings.KEY_RING_DATA },
+ Keys.TABLE_NAME + "." + Keys.KEY_ID + " = ? AND " +
+ KeyRings.TABLE_NAME + "." + KeyRings.TYPE + " = ?",
+ new String[] {
+ "" + keyId,
+ "" + type,
+ },
+ null, null, null);
+
+ byte[] data = null;
+ if (c != null && c.moveToFirst()) {
+ data = c.getBlob(0);
+ }
+
+ if (c != null) {
+ c.close();
+ }
+
+ return data;
+ }
+
+ public byte[] getKeyDataFromKeyId(int type, long keyId) {
+ Cursor c = mDb.query(Keys.TABLE_NAME, new String[] { Keys.KEY_DATA },
+ Keys.KEY_ID + " = ? AND " + Keys.TYPE + " = ?",
+ new String[] {
+ "" + keyId,
+ "" + type,
+ },
+ null, null, null);
+ byte[] data = null;
+ if (c != null && c.moveToFirst()) {
+ data = c.getBlob(0);
+ }
+
+ if (c != null) {
+ c.close();
+ }
+
+ return data;
+ }
+
+ public void deleteKeyRing(int keyRingId) {
+ mDb.beginTransaction();
+ mDb.delete(KeyRings.TABLE_NAME,
+ KeyRings._ID + " = ?", new String[] { "" + keyRingId });
+
+ Cursor c = mDb.query(Keys.TABLE_NAME, new String[] { Keys._ID },
+ Keys.KEY_RING_ID + " = ?",
+ new String[] {
+ "" + keyRingId,
+ },
+ null, null, null);
+ if (c != null && c.moveToFirst()) {
+ do {
+ int keyId = c.getInt(0);
+ deleteKey(keyId);
+ } while (c.moveToNext());
+ }
+
+ if (c != null) {
+ c.close();
+ }
+
+ mDb.setTransactionSuccessful();
+ mDb.endTransaction();
+ }
+
+ private void deleteKey(int keyId) {
+ mDb.delete(Keys.TABLE_NAME,
+ Keys._ID + " = ?", new String[] { "" + keyId });
+
+ mDb.delete(UserIds.TABLE_NAME,
+ UserIds.KEY_ID + " = ?", new String[] { "" + keyId });
+ }
+
+ public SQLiteDatabase db() {
+ return mDb;
+ }
+}
diff --git a/org_apg/src/org/apg/provider/KeyRings.java b/org_apg/src/org/apg/provider/KeyRings.java
new file mode 100644
index 000000000..304afd24f
--- /dev/null
+++ b/org_apg/src/org/apg/provider/KeyRings.java
@@ -0,0 +1,33 @@
+/*
+ * 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.apg.provider;
+
+import android.provider.BaseColumns;
+
+public class KeyRings implements BaseColumns {
+ public static final String TABLE_NAME = "key_rings";
+
+ public static final String _ID_type = "INTEGER PRIMARY KEY";
+ public static final String MASTER_KEY_ID = "c_master_key_id";
+ public static final String MASTER_KEY_ID_type = "INT64";
+ public static final String TYPE = "c_type";
+ public static final String TYPE_type = "INTEGER";
+ public static final String WHO_ID = "c_who_id";
+ public static final String WHO_ID_type = "INTEGER";
+ public static final String KEY_RING_DATA = "c_key_ring_data";
+ public static final String KEY_RING_DATA_type = "BLOB";
+}
diff --git a/org_apg/src/org/apg/provider/Keys.java b/org_apg/src/org/apg/provider/Keys.java
new file mode 100644
index 000000000..63eaee54f
--- /dev/null
+++ b/org_apg/src/org/apg/provider/Keys.java
@@ -0,0 +1,51 @@
+/*
+ * 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.apg.provider;
+
+import android.provider.BaseColumns;
+
+public class Keys implements BaseColumns {
+ public static final String TABLE_NAME = "keys";
+
+ public static final String _ID_type = "INTEGER PRIMARY KEY";
+ public static final String KEY_ID = "c_key_id";
+ public static final String KEY_ID_type = "INT64";
+ public static final String TYPE = "c_type";
+ public static final String TYPE_type = "INTEGER";
+ public static final String IS_MASTER_KEY = "c_is_master_key";
+ public static final String IS_MASTER_KEY_type = "INTEGER";
+ public static final String ALGORITHM = "c_algorithm";
+ public static final String ALGORITHM_type = "INTEGER";
+ public static final String KEY_SIZE = "c_key_size";
+ public static final String KEY_SIZE_type = "INTEGER";
+ public static final String CAN_SIGN = "c_can_sign";
+ public static final String CAN_SIGN_type = "INTEGER";
+ public static final String CAN_ENCRYPT = "c_can_encrypt";
+ public static final String CAN_ENCRYPT_type = "INTEGER";
+ public static final String IS_REVOKED = "c_is_revoked";
+ public static final String IS_REVOKED_type = "INTEGER";
+ public static final String CREATION = "c_creation";
+ public static final String CREATION_type = "INTEGER";
+ public static final String EXPIRY = "c_expiry";
+ public static final String EXPIRY_type = "INTEGER";
+ public static final String KEY_RING_ID = "c_key_ring_id";
+ public static final String KEY_RING_ID_type = "INTEGER";
+ public static final String KEY_DATA = "c_key_data";
+ public static final String KEY_DATA_type = "BLOB";
+ public static final String RANK = "c_key_data";
+ public static final String RANK_type = "INTEGER";
+}
diff --git a/org_apg/src/org/apg/provider/UserIds.java b/org_apg/src/org/apg/provider/UserIds.java
new file mode 100644
index 000000000..e8ddc677d
--- /dev/null
+++ b/org_apg/src/org/apg/provider/UserIds.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.apg.provider;
+
+import android.provider.BaseColumns;
+
+public class UserIds implements BaseColumns {
+ public static final String TABLE_NAME = "user_ids";
+
+ public static final String _ID_type = "INTEGER PRIMARY KEY";
+ public static final String KEY_ID = "c_key_id";
+ public static final String KEY_ID_type = "INTEGER";
+ public static final String USER_ID = "c_user_id";
+ public static final String USER_ID_type = "TEXT";
+ public static final String RANK = "c_rank";
+ public static final String RANK_type = "INTEGER";
+}
diff --git a/org_apg/src/org/apg/ui/AboutActivity.java b/org_apg/src/org/apg/ui/AboutActivity.java
new file mode 100644
index 000000000..308a1e06e
--- /dev/null
+++ b/org_apg/src/org/apg/ui/AboutActivity.java
@@ -0,0 +1,51 @@
+package org.apg.ui;
+
+import org.apg.Constants;
+import org.apg.R;
+
+import android.app.Activity;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Bundle;
+import android.util.Log;
+import android.widget.TextView;
+
+public class AboutActivity extends Activity {
+ Activity mActivity;
+
+ /**
+ * Instantiate View for this Activity
+ */
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.about_activity);
+
+ mActivity = this;
+
+ TextView versionText = (TextView) findViewById(R.id.about_version);
+ versionText.setText(getString(R.string.about_version) + " " + getVersion());
+ }
+
+ /**
+ * Get the current package version.
+ *
+ * @return The current version.
+ */
+ private String getVersion() {
+ String result = "";
+ try {
+ PackageManager manager = mActivity.getPackageManager();
+ PackageInfo info = manager.getPackageInfo(mActivity.getPackageName(), 0);
+
+ result = String.format("%s (%s)", info.versionName, info.versionCode);
+ } catch (NameNotFoundException e) {
+ Log.w(Constants.TAG, "Unable to get application version: " + e.getMessage());
+ result = "Unable to get application version.";
+ }
+
+ return result;
+ }
+}
diff --git a/org_apg/src/org/apg/ui/BaseActivity.java b/org_apg/src/org/apg/ui/BaseActivity.java
new file mode 100644
index 000000000..9b5039a5d
--- /dev/null
+++ b/org_apg/src/org/apg/ui/BaseActivity.java
@@ -0,0 +1,436 @@
+/*
+ * 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.apg.ui;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Locale;
+
+import org.apg.Apg;
+import org.apg.AskForSecretKeyPassPhrase;
+import org.apg.Constants;
+import org.apg.Id;
+import org.apg.PausableThread;
+import org.apg.Preferences;
+import org.apg.ProgressDialogUpdater;
+import org.apg.Service;
+import org.apg.R;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Message;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.TextView;
+import android.widget.Toast;
+
+public class BaseActivity extends Activity implements Runnable, ProgressDialogUpdater,
+ AskForSecretKeyPassPhrase.PassPhraseCallbackInterface {
+
+ private ProgressDialog mProgressDialog = null;
+ private PausableThread mRunningThread = null;
+ private Thread mDeletingThread = null;
+
+ private long mSecretKeyId = 0;
+ private String mDeleteFile = null;
+
+ protected Preferences mPreferences;
+
+ private Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ handlerCallback(msg);
+ }
+ };
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mPreferences = Preferences.getPreferences(this);
+ setLanguage(this, mPreferences.getLanguage());
+
+ Apg.initialize(this);
+
+ 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
+ }
+ }
+
+ startCacheService(this, mPreferences);
+ }
+
+ public static void startCacheService(Activity activity, Preferences preferences) {
+ Intent intent = new Intent(activity, Service.class);
+ intent.putExtra(Service.EXTRA_TTL, preferences.getPassPhraseCacheTtl());
+ activity.startService(intent);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ menu.add(0, Id.menu.option.preferences, 0, R.string.menu_preferences).setIcon(
+ android.R.drawable.ic_menu_preferences);
+ menu.add(0, Id.menu.option.about, 1, R.string.menu_about).setIcon(
+ android.R.drawable.ic_menu_info_details);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case Id.menu.option.about: {
+ startActivity(new Intent(this, AboutActivity.class));
+ return true;
+ }
+
+ case Id.menu.option.preferences: {
+ startActivity(new Intent(this, PreferencesActivity.class));
+ return true;
+ }
+
+ case Id.menu.option.search: {
+ startSearch("", false, null, false);
+ return true;
+ }
+
+ default: {
+ break;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ protected Dialog onCreateDialog(int id) {
+ // in case it is a progress dialog
+ mProgressDialog = new ProgressDialog(this);
+ mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
+ mProgressDialog.setCancelable(false);
+ switch (id) {
+ case Id.dialog.encrypting: {
+ mProgressDialog.setMessage(this.getString(R.string.progress_initializing));
+ return mProgressDialog;
+ }
+
+ case Id.dialog.decrypting: {
+ mProgressDialog.setMessage(this.getString(R.string.progress_initializing));
+ return mProgressDialog;
+ }
+
+ case Id.dialog.saving: {
+ mProgressDialog.setMessage(this.getString(R.string.progress_saving));
+ return mProgressDialog;
+ }
+
+ case Id.dialog.importing: {
+ mProgressDialog.setMessage(this.getString(R.string.progress_importing));
+ return mProgressDialog;
+ }
+
+ case Id.dialog.exporting: {
+ mProgressDialog.setMessage(this.getString(R.string.progress_exporting));
+ return mProgressDialog;
+ }
+
+ case Id.dialog.deleting: {
+ mProgressDialog.setMessage(this.getString(R.string.progress_initializing));
+ return mProgressDialog;
+ }
+
+ case Id.dialog.querying: {
+ mProgressDialog.setMessage(this.getString(R.string.progress_querying));
+ mProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
+ mProgressDialog.setCancelable(false);
+ return mProgressDialog;
+ }
+
+ case Id.dialog.signing: {
+ mProgressDialog.setMessage(this.getString(R.string.progress_signing));
+ mProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
+ mProgressDialog.setCancelable(false);
+ return mProgressDialog;
+ }
+
+ default: {
+ break;
+ }
+ }
+ mProgressDialog = null;
+
+ switch (id) {
+
+ case Id.dialog.pass_phrase: {
+ return AskForSecretKeyPassPhrase.createDialog(this, getSecretKeyId(), this);
+ }
+
+ case Id.dialog.pass_phrases_do_not_match: {
+ AlertDialog.Builder alert = new AlertDialog.Builder(this);
+
+ alert.setIcon(android.R.drawable.ic_dialog_alert);
+ alert.setTitle(R.string.error);
+ alert.setMessage(R.string.passPhrasesDoNotMatch);
+
+ alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ removeDialog(Id.dialog.pass_phrases_do_not_match);
+ }
+ });
+ alert.setCancelable(false);
+
+ return alert.create();
+ }
+
+ case Id.dialog.no_pass_phrase: {
+ AlertDialog.Builder alert = new AlertDialog.Builder(this);
+
+ alert.setIcon(android.R.drawable.ic_dialog_alert);
+ alert.setTitle(R.string.error);
+ alert.setMessage(R.string.passPhraseMustNotBeEmpty);
+
+ alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ removeDialog(Id.dialog.no_pass_phrase);
+ }
+ });
+ alert.setCancelable(false);
+
+ return alert.create();
+ }
+
+ case Id.dialog.delete_file: {
+ AlertDialog.Builder alert = new AlertDialog.Builder(this);
+
+ alert.setIcon(android.R.drawable.ic_dialog_alert);
+ alert.setTitle(R.string.warning);
+ alert.setMessage(this.getString(R.string.fileDeleteConfirmation, getDeleteFile()));
+
+ alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ removeDialog(Id.dialog.delete_file);
+ final File file = new File(getDeleteFile());
+ showDialog(Id.dialog.deleting);
+ mDeletingThread = new Thread(new Runnable() {
+ public void run() {
+ Bundle data = new Bundle();
+ data.putInt(Constants.extras.STATUS, Id.message.delete_done);
+ try {
+ Apg.deleteFileSecurely(BaseActivity.this, file, BaseActivity.this);
+ } catch (FileNotFoundException e) {
+ data.putString(Apg.EXTRA_ERROR, BaseActivity.this.getString(
+ R.string.error_fileNotFound, file));
+ } catch (IOException e) {
+ data.putString(Apg.EXTRA_ERROR, BaseActivity.this.getString(
+ R.string.error_fileDeleteFailed, file));
+ }
+ Message msg = new Message();
+ msg.setData(data);
+ sendMessage(msg);
+ }
+ });
+ mDeletingThread.start();
+ }
+ });
+ alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ removeDialog(Id.dialog.delete_file);
+ }
+ });
+ alert.setCancelable(true);
+
+ return alert.create();
+ }
+
+ default: {
+ break;
+ }
+ }
+
+ return super.onCreateDialog(id);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ switch (requestCode) {
+ case Id.request.secret_keys: {
+ if (resultCode == RESULT_OK) {
+ Bundle bundle = data.getExtras();
+ setSecretKeyId(bundle.getLong(Apg.EXTRA_KEY_ID));
+ } else {
+ setSecretKeyId(Id.key.none);
+ }
+ break;
+ }
+
+ default: {
+ break;
+ }
+ }
+
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+
+ public void setProgress(int resourceId, int progress, int max) {
+ setProgress(getString(resourceId), progress, max);
+ }
+
+ public void setProgress(int progress, int max) {
+ Message msg = new Message();
+ Bundle data = new Bundle();
+ data.putInt(Constants.extras.STATUS, Id.message.progress_update);
+ data.putInt(Constants.extras.PROGRESS, progress);
+ data.putInt(Constants.extras.PROGRESS_MAX, max);
+ msg.setData(data);
+ mHandler.sendMessage(msg);
+ }
+
+ public void setProgress(String message, int progress, int max) {
+ Message msg = new Message();
+ Bundle data = new Bundle();
+ data.putInt(Constants.extras.STATUS, Id.message.progress_update);
+ data.putString(Constants.extras.MESSAGE, message);
+ data.putInt(Constants.extras.PROGRESS, progress);
+ data.putInt(Constants.extras.PROGRESS_MAX, max);
+ msg.setData(data);
+ mHandler.sendMessage(msg);
+ }
+
+ public void handlerCallback(Message msg) {
+ Bundle data = msg.getData();
+ if (data == null) {
+ return;
+ }
+
+ int type = data.getInt(Constants.extras.STATUS);
+ switch (type) {
+ case Id.message.progress_update: {
+ String message = data.getString(Constants.extras.MESSAGE);
+ if (mProgressDialog != null) {
+ if (message != null) {
+ mProgressDialog.setMessage(message);
+ }
+ mProgressDialog.setMax(data.getInt(Constants.extras.PROGRESS_MAX));
+ mProgressDialog.setProgress(data.getInt(Constants.extras.PROGRESS));
+ }
+ break;
+ }
+
+ case Id.message.delete_done: {
+ mProgressDialog = null;
+ deleteDoneCallback(msg);
+ break;
+ }
+
+ case Id.message.import_done: // intentionally no break
+ case Id.message.export_done: // intentionally no break
+ case Id.message.query_done: // intentionally no break
+ case Id.message.done: {
+ mProgressDialog = null;
+ doneCallback(msg);
+ break;
+ }
+
+ default: {
+ break;
+ }
+ }
+ }
+
+ public void doneCallback(Message msg) {
+
+ }
+
+ public void deleteDoneCallback(Message msg) {
+ removeDialog(Id.dialog.deleting);
+ mDeletingThread = null;
+
+ Bundle data = msg.getData();
+ String error = data.getString(Apg.EXTRA_ERROR);
+ String message;
+ if (error != null) {
+ message = getString(R.string.errorMessage, error);
+ } else {
+ message = getString(R.string.fileDeleteSuccessful);
+ }
+
+ Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
+ }
+
+ public void passPhraseCallback(long keyId, String passPhrase) {
+ Apg.setCachedPassPhrase(keyId, passPhrase);
+ }
+
+ public void sendMessage(Message msg) {
+ mHandler.sendMessage(msg);
+ }
+
+ public PausableThread getRunningThread() {
+ return mRunningThread;
+ }
+
+ public void startThread() {
+ mRunningThread = new PausableThread(this);
+ mRunningThread.start();
+ }
+
+ public void run() {
+
+ }
+
+ public void setSecretKeyId(long id) {
+ mSecretKeyId = id;
+ }
+
+ public long getSecretKeyId() {
+ return mSecretKeyId;
+ }
+
+ protected void setDeleteFile(String deleteFile) {
+ mDeleteFile = deleteFile;
+ }
+
+ protected String getDeleteFile() {
+ return mDeleteFile;
+ }
+
+ public static void setLanguage(Context context, String language) {
+ Locale locale;
+ if (language == null || language.equals("")) {
+ locale = Locale.getDefault();
+ } else {
+ locale = new Locale(language);
+ }
+ Configuration config = new Configuration();
+ config.locale = locale;
+ context.getResources().updateConfiguration(config,
+ context.getResources().getDisplayMetrics());
+ }
+}
diff --git a/org_apg/src/org/apg/ui/DecryptActivity.java b/org_apg/src/org/apg/ui/DecryptActivity.java
new file mode 100644
index 000000000..cb58dfb09
--- /dev/null
+++ b/org_apg/src/org/apg/ui/DecryptActivity.java
@@ -0,0 +1,807 @@
+/*
+ * 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.apg.ui;
+
+import org.apg.Apg;
+import org.apg.Constants;
+import org.apg.DataDestination;
+import org.apg.DataSource;
+import org.apg.FileDialog;
+import org.apg.Id;
+import org.apg.InputData;
+import org.apg.PausableThread;
+import org.apg.provider.DataProvider;
+import org.apg.util.Compatibility;
+import org.spongycastle.jce.provider.BouncyCastleProvider;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPPublicKeyRing;
+import org.apg.R;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.ActivityNotFoundException;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Message;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.animation.AnimationUtils;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.EditText;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+import android.widget.ViewFlipper;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.Security;
+import java.security.SignatureException;
+import java.util.regex.Matcher;
+
+public class DecryptActivity extends BaseActivity {
+ private long mSignatureKeyId = 0;
+
+ private Intent mIntent;
+
+ private boolean mReturnResult = false;
+ private String mReplyTo = null;
+ private String mSubject = null;
+ 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 Button mDecryptButton = null;
+ private Button mReplyButton = null;
+
+ private int mDecryptTarget;
+
+ private EditText mFilename = null;
+ private CheckBox mDeleteAfter = null;
+ private ImageButton mBrowse = null;
+
+ private String mInputFilename = null;
+ private String mOutputFilename = null;
+
+ private Uri mContentUri = null;
+ private byte[] mData = null;
+ private boolean mReturnBinary = false;
+
+ private DataSource mDataSource = null;
+ private DataDestination mDataDestination = null;
+
+ private long mUnknownSignatureKeyId = 0;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.decrypt);
+
+ 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);
+ mDecryptButton = (Button) findViewById(R.id.btn_decrypt);
+ mReplyButton = (Button) findViewById(R.id.btn_reply);
+ 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 = (ImageButton) findViewById(R.id.btn_browse);
+ mBrowse.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ openFile();
+ }
+ });
+
+ mDeleteAfter = (CheckBox) findViewById(R.id.deleteAfterDecryption);
+
+ // default: message source
+ mSource.setInAnimation(null);
+ mSource.setOutAnimation(null);
+ while (mSource.getCurrentView().getId() != R.id.sourceMessage) {
+ mSource.showNext();
+ }
+
+ mIntent = getIntent();
+ if (Intent.ACTION_VIEW.equals(mIntent.getAction())) {
+ Uri uri = mIntent.getData();
+ try {
+ InputStream attachment = getContentResolver().openInputStream(uri);
+ ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
+ byte bytes[] = new byte[1 << 16];
+ int length;
+ while ((length = attachment.read(bytes)) > 0) {
+ byteOut.write(bytes, 0, length);
+ }
+ byteOut.close();
+ String data = new String(byteOut.toByteArray());
+ mMessage.setText(data);
+ } catch (FileNotFoundException e) {
+ // ignore, then
+ } catch (IOException e) {
+ // ignore, then
+ }
+ } else if (Apg.Intent.DECRYPT.equals(mIntent.getAction())) {
+ Log.d(Constants.TAG, "Apg Intent DECRYPT startet");
+ Bundle extras = mIntent.getExtras();
+ if (extras == null) {
+ Log.d(Constants.TAG, "extra bundle was null");
+ extras = new Bundle();
+ } else {
+ Log.d(Constants.TAG, "got extras");
+ }
+
+ mData = extras.getByteArray(Apg.EXTRA_DATA);
+ String textData = null;
+ if (mData == null) {
+ Log.d(Constants.TAG, "EXTRA_DATA was null");
+ textData = extras.getString(Apg.EXTRA_TEXT);
+ } else {
+ Log.d(Constants.TAG, "Got data from EXTRA_DATA");
+ }
+ if (textData != null) {
+ Log.d(Constants.TAG, "textData null, matching text ...");
+ Matcher matcher = Apg.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 = Apg.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);
+ mDecryptButton.setText(R.string.btn_verify);
+ } else {
+ Log.d(Constants.TAG, "Nothing matched!");
+ }
+ }
+ }
+ mReplyTo = extras.getString(Apg.EXTRA_REPLY_TO);
+ mSubject = extras.getString(Apg.EXTRA_SUBJECT);
+ } else if (Apg.Intent.DECRYPT_FILE.equals(mIntent.getAction())) {
+ mInputFilename = mIntent.getDataString();
+ if ("file".equals(mIntent.getScheme())) {
+ mInputFilename = Uri.decode(mInputFilename.substring(7));
+ }
+ mFilename.setText(mInputFilename);
+ guessOutputFilename();
+ mSource.setInAnimation(null);
+ mSource.setOutAnimation(null);
+ while (mSource.getCurrentView().getId() != R.id.sourceFile) {
+ mSource.showNext();
+ }
+ } else if (Apg.Intent.DECRYPT_AND_RETURN.equals(mIntent.getAction())) {
+ mContentUri = mIntent.getData();
+ Bundle extras = mIntent.getExtras();
+ if (extras == null) {
+ extras = new Bundle();
+ }
+
+ mReturnBinary = extras.getBoolean(Apg.EXTRA_BINARY, false);
+
+ if (mContentUri == null) {
+ mData = extras.getByteArray(Apg.EXTRA_DATA);
+ String data = extras.getString(Apg.EXTRA_TEXT);
+ if (data != null) {
+ Matcher matcher = Apg.PGP_MESSAGE.matcher(data);
+ if (matcher.matches()) {
+ data = matcher.group(1);
+ // replace non breakable spaces
+ data = data.replaceAll("\\xa0", " ");
+ mMessage.setText(data);
+ } else {
+ matcher = Apg.PGP_SIGNED_MESSAGE.matcher(data);
+ if (matcher.matches()) {
+ data = matcher.group(1);
+ // replace non breakable spaces
+ data = data.replaceAll("\\xa0", " ");
+ mMessage.setText(data);
+ mDecryptButton.setText(R.string.btn_verify);
+ }
+ }
+ }
+ }
+ mReturnResult = true;
+ }
+
+ if (mSource.getCurrentView().getId() == R.id.sourceMessage
+ && mMessage.getText().length() == 0) {
+
+ CharSequence clipboardText = Compatibility.getClipboardText(this);
+
+ String data = "";
+ if (clipboardText != null) {
+ Matcher matcher = Apg.PGP_MESSAGE.matcher(clipboardText);
+ if (!matcher.matches()) {
+ matcher = Apg.PGP_SIGNED_MESSAGE.matcher(clipboardText);
+ }
+ if (matcher.matches()) {
+ data = matcher.group(1);
+ mMessage.setText(data);
+ Toast.makeText(this, R.string.usingClipboardContent, Toast.LENGTH_SHORT).show();
+ }
+ }
+ }
+
+ mSignatureLayout.setVisibility(View.GONE);
+ mSignatureLayout.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ if (mSignatureKeyId == 0) {
+ return;
+ }
+ PGPPublicKeyRing key = Apg.getPublicKeyRing(mSignatureKeyId);
+ if (key != null) {
+ Intent intent = new Intent(DecryptActivity.this, KeyServerQueryActivity.class);
+ intent.setAction(Apg.Intent.LOOK_UP_KEY_ID);
+ intent.putExtra(Apg.EXTRA_KEY_ID, mSignatureKeyId);
+ startActivity(intent);
+ }
+ }
+ });
+
+ mDecryptButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ decryptClicked();
+ }
+ });
+
+ mReplyButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ replyClicked();
+ }
+ });
+ mReplyButton.setVisibility(View.INVISIBLE);
+
+ 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 (mSource.getCurrentView().getId() == R.id.sourceMessage
+ && (mMessage.getText().length() > 0 || mData != null || mContentUri != null)) {
+ mDecryptButton.performClick();
+ }
+ }
+
+ private void openFile() {
+ String filename = mFilename.getText().toString();
+
+ Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+ intent.addCategory(Intent.CATEGORY_OPENABLE);
+
+ intent.setData(Uri.parse("file://" + filename));
+ intent.setType("*/*");
+
+ try {
+ startActivityForResult(intent, Id.request.filename);
+ } catch (ActivityNotFoundException e) {
+ // No compatible file manager was found.
+ Toast.makeText(this, R.string.noFilemanagerInstalled, Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ 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);
+ mDecryptButton.setText(R.string.btn_decrypt);
+ break;
+ }
+
+ case R.id.sourceMessage: {
+ mSourceLabel.setText(R.string.label_message);
+ mDecryptButton.setText(R.string.btn_decrypt);
+ 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.noFileSelected, 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.errorMessage, getString(R.string.error_fileNotFound)),
+ Toast.LENGTH_SHORT).show();
+ return;
+ }
+ }
+ }
+
+ if (mDecryptTarget == Id.target.message) {
+ String messageData = mMessage.getText().toString();
+ Matcher matcher = Apg.PGP_SIGNED_MESSAGE.matcher(messageData);
+ if (matcher.matches()) {
+ mSignedOnly = true;
+ decryptStart();
+ return;
+ }
+ }
+
+ // else treat it as an decrypted message/file
+ mSignedOnly = false;
+ String error = null;
+ fillDataSource();
+ try {
+ InputData in = mDataSource.getInputData(this, false);
+ try {
+ setSecretKeyId(Apg.getDecryptionKeyId(this, in));
+ if (getSecretKeyId() == Id.key.none) {
+ throw new Apg.GeneralException(getString(R.string.error_noSecretKeyFound));
+ }
+ mAssumeSymmetricEncryption = false;
+ } catch (Apg.NoAsymmetricEncryptionException e) {
+ setSecretKeyId(Id.key.symmetric);
+ in = mDataSource.getInputData(this, false);
+ if (!Apg.hasSymmetricEncryption(this, in)) {
+ throw new Apg.GeneralException(getString(R.string.error_noKnownEncryptionFound));
+ }
+ mAssumeSymmetricEncryption = true;
+ }
+
+ if (getSecretKeyId() == Id.key.symmetric
+ || Apg.getCachedPassPhrase(getSecretKeyId()) == null) {
+ showDialog(Id.dialog.pass_phrase);
+ } else {
+ if (mDecryptTarget == Id.target.file) {
+ askForOutputFilename();
+ } else {
+ decryptStart();
+ }
+ }
+ } catch (FileNotFoundException e) {
+ error = getString(R.string.error_fileNotFound);
+ } catch (IOException e) {
+ error = "" + e;
+ } catch (Apg.GeneralException e) {
+ error = "" + e;
+ }
+ if (error != null) {
+ Toast.makeText(this, getString(R.string.errorMessage, error), Toast.LENGTH_SHORT)
+ .show();
+ }
+ }
+
+ private void replyClicked() {
+ Intent intent = new Intent(this, EncryptActivity.class);
+ intent.setAction(Apg.Intent.ENCRYPT);
+ String data = mMessage.getText().toString();
+ data = data.replaceAll("(?m)^", "> ");
+ data = "\n\n" + data;
+ intent.putExtra(Apg.EXTRA_TEXT, data);
+ intent.putExtra(Apg.EXTRA_SUBJECT, "Re: " + mSubject);
+ intent.putExtra(Apg.EXTRA_SEND_TO, mReplyTo);
+ intent.putExtra(Apg.EXTRA_SIGNATURE_KEY_ID, getSecretKeyId());
+ intent.putExtra(Apg.EXTRA_ENCRYPTION_KEY_IDS, new long[] { mSignatureKeyId });
+ startActivity(intent);
+ }
+
+ private void askForOutputFilename() {
+ showDialog(Id.dialog.output_filename);
+ }
+
+ @Override
+ public void passPhraseCallback(long keyId, String passPhrase) {
+ super.passPhraseCallback(keyId, passPhrase);
+ if (mDecryptTarget == Id.target.file) {
+ askForOutputFilename();
+ } else {
+ decryptStart();
+ }
+ }
+
+ private void decryptStart() {
+ showDialog(Id.dialog.decrypting);
+ startThread();
+ }
+
+ @Override
+ public void run() {
+ String error = null;
+ Security.addProvider(new BouncyCastleProvider());
+
+ Bundle data = new Bundle();
+ Message msg = new Message();
+ fillDataSource();
+ fillDataDestination();
+ try {
+ InputData in = mDataSource.getInputData(this, true);
+ OutputStream out = mDataDestination.getOutputStream(this);
+
+ if (mSignedOnly) {
+ data = Apg.verifyText(this, in, out, this);
+ } else {
+ data = Apg.decrypt(this, in, out, Apg.getCachedPassPhrase(getSecretKeyId()), this,
+ mAssumeSymmetricEncryption);
+ }
+
+ out.close();
+
+ if (mDataDestination.getStreamFilename() != null) {
+ data.putString(Apg.EXTRA_RESULT_URI, "content://" + DataProvider.AUTHORITY
+ + "/data/" + mDataDestination.getStreamFilename());
+ } else if (mDecryptTarget == Id.target.message) {
+ if (mReturnBinary) {
+ data.putByteArray(Apg.EXTRA_DECRYPTED_DATA,
+ ((ByteArrayOutputStream) out).toByteArray());
+ } else {
+ data.putString(Apg.EXTRA_DECRYPTED_MESSAGE, new String(
+ ((ByteArrayOutputStream) out).toByteArray()));
+ }
+ }
+ } catch (PGPException e) {
+ error = "" + e;
+ } catch (IOException e) {
+ error = "" + e;
+ } catch (SignatureException e) {
+ error = "" + e;
+ } catch (Apg.GeneralException e) {
+ error = "" + e;
+ }
+
+ data.putInt(Constants.extras.STATUS, Id.message.done);
+
+ if (error != null) {
+ data.putString(Apg.EXTRA_ERROR, error);
+ }
+
+ msg.setData(data);
+ sendMessage(msg);
+ }
+
+ @Override
+ public void handlerCallback(Message msg) {
+ Bundle data = msg.getData();
+ if (data == null) {
+ return;
+ }
+
+ if (data.getInt(Constants.extras.STATUS) == Id.message.unknown_signature_key) {
+ mUnknownSignatureKeyId = data.getLong(Constants.extras.KEY_ID);
+ showDialog(Id.dialog.lookup_unknown_key);
+ return;
+ }
+
+ super.handlerCallback(msg);
+ }
+
+ @Override
+ public void doneCallback(Message msg) {
+ super.doneCallback(msg);
+
+ Bundle data = msg.getData();
+ removeDialog(Id.dialog.decrypting);
+ mSignatureKeyId = 0;
+ mSignatureLayout.setVisibility(View.GONE);
+ mReplyButton.setVisibility(View.INVISIBLE);
+
+ String error = data.getString(Apg.EXTRA_ERROR);
+ if (error != null) {
+ Toast.makeText(this, getString(R.string.errorMessage, error), Toast.LENGTH_SHORT)
+ .show();
+ return;
+ }
+
+ Toast.makeText(this, R.string.decryptionSuccessful, Toast.LENGTH_SHORT).show();
+ if (mReturnResult) {
+ Intent intent = new Intent();
+ intent.putExtras(data);
+ setResult(RESULT_OK, intent);
+ finish();
+ return;
+ }
+
+ switch (mDecryptTarget) {
+ case Id.target.message: {
+ String decryptedMessage = data.getString(Apg.EXTRA_DECRYPTED_MESSAGE);
+ mMessage.setText(decryptedMessage);
+ mMessage.setHorizontallyScrolling(false);
+ mReplyButton.setVisibility(View.VISIBLE);
+ break;
+ }
+
+ case Id.target.file: {
+ if (mDeleteAfter.isChecked()) {
+ setDeleteFile(mInputFilename);
+ showDialog(Id.dialog.delete_file);
+ }
+ break;
+ }
+
+ default: {
+ // shouldn't happen
+ break;
+ }
+ }
+
+ if (data.getBoolean(Apg.EXTRA_SIGNATURE)) {
+ String userId = data.getString(Apg.EXTRA_SIGNATURE_USER_ID);
+ mSignatureKeyId = data.getLong(Apg.EXTRA_SIGNATURE_KEY_ID);
+ mUserIdRest.setText("id: " + Apg.getSmallFingerPrint(mSignatureKeyId));
+ if (userId == null) {
+ userId = getResources().getString(R.string.unknownUserId);
+ }
+ String chunks[] = userId.split(" <", 2);
+ userId = chunks[0];
+ if (chunks.length > 1) {
+ mUserIdRest.setText("<" + chunks[1]);
+ }
+ mUserId.setText(userId);
+
+ if (data.getBoolean(Apg.EXTRA_SIGNATURE_SUCCESS)) {
+ mSignatureStatusImage.setImageResource(R.drawable.overlay_ok);
+ } else if (data.getBoolean(Apg.EXTRA_SIGNATURE_UNKNOWN)) {
+ mSignatureStatusImage.setImageResource(R.drawable.overlay_error);
+ Toast.makeText(this, R.string.unknownSignatureKeyTouchToLookUp, Toast.LENGTH_LONG)
+ .show();
+ } else {
+ mSignatureStatusImage.setImageResource(R.drawable.overlay_error);
+ }
+ mSignatureLayout.setVisibility(View.VISIBLE);
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ switch (requestCode) {
+ case Id.request.filename: {
+ if (resultCode == RESULT_OK && data != null) {
+ String filename = data.getDataString();
+ if (filename != null) {
+ // Get rid of URI prefix:
+ if (filename.startsWith("file://")) {
+ filename = filename.substring(7);
+ }
+ // replace %20 and so on
+ filename = Uri.decode(filename);
+
+ mFilename.setText(filename);
+ }
+ }
+ return;
+ }
+
+ case Id.request.output_filename: {
+ if (resultCode == RESULT_OK && data != null) {
+ String filename = data.getDataString();
+ if (filename != null) {
+ // Get rid of URI prefix:
+ if (filename.startsWith("file://")) {
+ filename = filename.substring(7);
+ }
+ // replace %20 and so on
+ filename = Uri.decode(filename);
+
+ FileDialog.setFilename(filename);
+ }
+ }
+ return;
+ }
+
+ case Id.request.look_up_key_id: {
+ PausableThread thread = getRunningThread();
+ if (thread != null && thread.isPaused()) {
+ thread.unpause();
+ }
+ return;
+ }
+
+ default: {
+ break;
+ }
+ }
+
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+
+ @Override
+ protected Dialog onCreateDialog(int id) {
+ switch (id) {
+ case Id.dialog.output_filename: {
+ return FileDialog.build(this, getString(R.string.title_decryptToFile),
+ getString(R.string.specifyFileToDecryptTo), mOutputFilename,
+ new FileDialog.OnClickListener() {
+ public void onOkClick(String filename, boolean checked) {
+ removeDialog(Id.dialog.output_filename);
+ mOutputFilename = filename;
+ decryptStart();
+ }
+
+ public void onCancelClick() {
+ removeDialog(Id.dialog.output_filename);
+ }
+ }, getString(R.string.filemanager_titleSave),
+ getString(R.string.filemanager_btnSave), null, Id.request.output_filename);
+ }
+
+ case Id.dialog.lookup_unknown_key: {
+ AlertDialog.Builder alert = new AlertDialog.Builder(this);
+
+ alert.setIcon(android.R.drawable.ic_dialog_alert);
+ alert.setTitle(R.string.title_unknownSignatureKey);
+ alert.setMessage(getString(R.string.lookupUnknownKey,
+ Apg.getSmallFingerPrint(mUnknownSignatureKeyId)));
+
+ alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ removeDialog(Id.dialog.lookup_unknown_key);
+ Intent intent = new Intent(DecryptActivity.this, KeyServerQueryActivity.class);
+ intent.setAction(Apg.Intent.LOOK_UP_KEY_ID);
+ intent.putExtra(Apg.EXTRA_KEY_ID, mUnknownSignatureKeyId);
+ startActivityForResult(intent, Id.request.look_up_key_id);
+ }
+ });
+ alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ removeDialog(Id.dialog.lookup_unknown_key);
+ PausableThread thread = getRunningThread();
+ if (thread != null && thread.isPaused()) {
+ thread.unpause();
+ }
+ }
+ });
+ alert.setCancelable(true);
+
+ return alert.create();
+ }
+
+ default: {
+ break;
+ }
+ }
+
+ return super.onCreateDialog(id);
+ }
+
+ protected void fillDataSource() {
+ mDataSource = new DataSource();
+ if (mContentUri != null) {
+ mDataSource.setUri(mContentUri);
+ } else if (mDecryptTarget == Id.target.file) {
+ mDataSource.setUri(mInputFilename);
+ } else {
+ if (mData != null) {
+ mDataSource.setData(mData);
+ } else {
+ mDataSource.setText(mMessage.getText().toString());
+ }
+ }
+ }
+
+ protected void fillDataDestination() {
+ mDataDestination = new DataDestination();
+ if (mContentUri != null) {
+ mDataDestination.setMode(Id.mode.stream);
+ } else if (mDecryptTarget == Id.target.file) {
+ mDataDestination.setFilename(mOutputFilename);
+ mDataDestination.setMode(Id.mode.file);
+ } else {
+ mDataDestination.setMode(Id.mode.byte_array);
+ }
+ }
+}
diff --git a/org_apg/src/org/apg/ui/EditKeyActivity.java b/org_apg/src/org/apg/ui/EditKeyActivity.java
new file mode 100644
index 000000000..c3945d4ed
--- /dev/null
+++ b/org_apg/src/org/apg/ui/EditKeyActivity.java
@@ -0,0 +1,292 @@
+/*
+ * 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.apg.ui;
+
+import org.apg.Apg;
+import org.apg.Constants;
+import org.apg.Id;
+import org.apg.provider.Database;
+import org.apg.ui.widget.KeyEditor;
+import org.apg.ui.widget.SectionView;
+import org.apg.util.IterableIterator;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPSecretKey;
+import org.spongycastle.openpgp.PGPSecretKeyRing;
+import org.apg.R;
+
+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.view.LayoutInflater;
+import android.view.Menu;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.Toast;
+
+import java.io.IOException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.SignatureException;
+import java.util.Vector;
+
+public class EditKeyActivity extends BaseActivity implements OnClickListener {
+
+ private PGPSecretKeyRing mKeyRing = null;
+
+ private SectionView mUserIds;
+ private SectionView mKeys;
+
+ private Button mSaveButton;
+ private Button mDiscardButton;
+
+ private String mCurrentPassPhrase = null;
+ private String mNewPassPhrase = null;
+
+ private Button mChangePassPhrase;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.edit_key);
+
+ Vector<String> userIds = new Vector<String>();
+ Vector<PGPSecretKey> keys = new Vector<PGPSecretKey>();
+
+ Intent intent = getIntent();
+ long keyId = 0;
+ if (intent.getExtras() != null) {
+ keyId = intent.getExtras().getLong(Apg.EXTRA_KEY_ID);
+ }
+
+ if (keyId != 0) {
+ PGPSecretKey masterKey = null;
+ mKeyRing = Apg.getSecretKeyRing(keyId);
+ if (mKeyRing != null) {
+ masterKey = Apg.getMasterKey(mKeyRing);
+ for (PGPSecretKey key : new IterableIterator<PGPSecretKey>(mKeyRing.getSecretKeys())) {
+ keys.add(key);
+ }
+ }
+ if (masterKey != null) {
+ for (String userId : new IterableIterator<String>(masterKey.getUserIDs())) {
+ userIds.add(userId);
+ }
+ }
+ }
+
+ mChangePassPhrase = (Button) findViewById(R.id.btn_change_pass_phrase);
+ mChangePassPhrase.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ showDialog(Id.dialog.new_pass_phrase);
+ }
+ });
+
+ mSaveButton = (Button) findViewById(R.id.btn_save);
+ mDiscardButton = (Button) findViewById(R.id.btn_discard);
+
+ mSaveButton.setOnClickListener(this);
+ mDiscardButton.setOnClickListener(this);
+
+ LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+ LinearLayout container = (LinearLayout) findViewById(R.id.container);
+ mUserIds = (SectionView) inflater.inflate(R.layout.edit_key_section, container, false);
+ mUserIds.setType(Id.type.user_id);
+ mUserIds.setUserIds(userIds);
+ container.addView(mUserIds);
+ mKeys = (SectionView) inflater.inflate(R.layout.edit_key_section, container, false);
+ mKeys.setType(Id.type.key);
+ mKeys.setKeys(keys);
+ container.addView(mKeys);
+
+ mCurrentPassPhrase = Apg.getEditPassPhrase();
+ if (mCurrentPassPhrase == null) {
+ mCurrentPassPhrase = "";
+ }
+
+ updatePassPhraseButtonText();
+
+ Toast.makeText(this,
+ getString(R.string.warningMessage, getString(R.string.keyEditingIsBeta)),
+ Toast.LENGTH_LONG).show();
+ }
+
+ private long getMasterKeyId() {
+ if (mKeys.getEditors().getChildCount() == 0) {
+ return 0;
+ }
+ return ((KeyEditor) mKeys.getEditors().getChildAt(0)).getValue().getKeyID();
+ }
+
+ public boolean havePassPhrase() {
+ return (!mCurrentPassPhrase.equals(""))
+ || (mNewPassPhrase != null && !mNewPassPhrase.equals(""));
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ menu.add(0, Id.menu.option.preferences, 0, R.string.menu_preferences).setIcon(
+ android.R.drawable.ic_menu_preferences);
+ menu.add(0, Id.menu.option.about, 1, R.string.menu_about).setIcon(
+ android.R.drawable.ic_menu_info_details);
+ return true;
+ }
+
+ @Override
+ protected Dialog onCreateDialog(int id) {
+ switch (id) {
+ case Id.dialog.new_pass_phrase: {
+ AlertDialog.Builder alert = new AlertDialog.Builder(this);
+
+ if (havePassPhrase()) {
+ alert.setTitle(R.string.title_changePassPhrase);
+ } else {
+ alert.setTitle(R.string.title_setPassPhrase);
+ }
+ alert.setMessage(R.string.enterPassPhraseTwice);
+
+ LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ View view = inflater.inflate(R.layout.pass_phrase, null);
+ final EditText input1 = (EditText) view.findViewById(R.id.passPhrase);
+ final EditText input2 = (EditText) view.findViewById(R.id.passPhraseAgain);
+
+ alert.setView(view);
+
+ alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ removeDialog(Id.dialog.new_pass_phrase);
+
+ String passPhrase1 = "" + input1.getText();
+ String passPhrase2 = "" + input2.getText();
+ if (!passPhrase1.equals(passPhrase2)) {
+ showDialog(Id.dialog.pass_phrases_do_not_match);
+ return;
+ }
+
+ if (passPhrase1.equals("")) {
+ showDialog(Id.dialog.no_pass_phrase);
+ return;
+ }
+
+ mNewPassPhrase = passPhrase1;
+ updatePassPhraseButtonText();
+ }
+ });
+
+ alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ removeDialog(Id.dialog.new_pass_phrase);
+ }
+ });
+
+ return alert.create();
+ }
+
+ default: {
+ return super.onCreateDialog(id);
+ }
+ }
+ }
+
+ public void onClick(View v) {
+ if (v == mSaveButton) {
+ // TODO: some warning
+ saveClicked();
+ } else if (v == mDiscardButton) {
+ finish();
+ }
+ }
+
+ private void saveClicked() {
+ if (!havePassPhrase()) {
+ Toast.makeText(this, R.string.setAPassPhrase, Toast.LENGTH_SHORT).show();
+ return;
+ }
+ showDialog(Id.dialog.saving);
+ startThread();
+ }
+
+ @Override
+ public void run() {
+ String error = null;
+ Bundle data = new Bundle();
+ Message msg = new Message();
+
+ try {
+ String oldPassPhrase = mCurrentPassPhrase;
+ String newPassPhrase = mNewPassPhrase;
+ if (newPassPhrase == null) {
+ newPassPhrase = oldPassPhrase;
+ }
+ Apg.buildSecretKey(this, mUserIds, mKeys, oldPassPhrase, newPassPhrase, this);
+ Apg.setCachedPassPhrase(getMasterKeyId(), newPassPhrase);
+ } catch (NoSuchProviderException e) {
+ error = "" + e;
+ } catch (NoSuchAlgorithmException e) {
+ error = "" + e;
+ } catch (PGPException e) {
+ error = "" + e;
+ } catch (SignatureException e) {
+ error = "" + e;
+ } catch (Apg.GeneralException e) {
+ error = "" + e;
+ } catch (Database.GeneralException e) {
+ error = "" + e;
+ } catch (IOException e) {
+ error = "" + e;
+ }
+
+ data.putInt(Constants.extras.STATUS, Id.message.done);
+
+ if (error != null) {
+ data.putString(Apg.EXTRA_ERROR, error);
+ }
+
+ msg.setData(data);
+ sendMessage(msg);
+ }
+
+ @Override
+ public void doneCallback(Message msg) {
+ super.doneCallback(msg);
+
+ Bundle data = msg.getData();
+ removeDialog(Id.dialog.saving);
+
+ String error = data.getString(Apg.EXTRA_ERROR);
+ if (error != null) {
+ Toast.makeText(EditKeyActivity.this, getString(R.string.errorMessage, error),
+ Toast.LENGTH_SHORT).show();
+ } else {
+ Toast.makeText(EditKeyActivity.this, R.string.keySaved, Toast.LENGTH_SHORT).show();
+ setResult(RESULT_OK);
+ finish();
+ }
+ }
+
+ private void updatePassPhraseButtonText() {
+ mChangePassPhrase.setText(havePassPhrase() ? R.string.btn_changePassPhrase
+ : R.string.btn_setPassPhrase);
+ }
+}
diff --git a/org_apg/src/org/apg/ui/EncryptActivity.java b/org_apg/src/org/apg/ui/EncryptActivity.java
new file mode 100644
index 000000000..e5892a4d5
--- /dev/null
+++ b/org_apg/src/org/apg/ui/EncryptActivity.java
@@ -0,0 +1,998 @@
+/*
+ * 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.apg.ui;
+
+import org.apg.Apg;
+import org.apg.Constants;
+import org.apg.DataDestination;
+import org.apg.DataSource;
+import org.apg.FileDialog;
+import org.apg.Id;
+import org.apg.InputData;
+import org.apg.provider.DataProvider;
+import org.apg.util.Choice;
+import org.apg.util.Compatibility;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPPublicKey;
+import org.spongycastle.openpgp.PGPPublicKeyRing;
+import org.spongycastle.openpgp.PGPSecretKey;
+import org.spongycastle.openpgp.PGPSecretKeyRing;
+import org.apg.R;
+
+import android.app.Dialog;
+import android.content.ActivityNotFoundException;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Message;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.animation.AnimationUtils;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.EditText;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.Spinner;
+import android.widget.TextView;
+import android.widget.Toast;
+import android.widget.ViewFlipper;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.SignatureException;
+import java.util.Vector;
+
+public class EncryptActivity extends BaseActivity {
+ private Intent mIntent = null;
+ private String mSubject = null;
+ private String mSendTo = null;
+
+ private long mEncryptionKeyIds[] = null;
+
+ private boolean mReturnResult = false;
+ private EditText mMessage = null;
+ private Button mSelectKeysButton = null;
+ private Button mEncryptButton = null;
+ private Button mEncryptToClipboardButton = null;
+ 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 mAsciiArmour = null;
+ private Spinner mFileCompression = null;
+
+ private EditText mFilename = null;
+ private CheckBox mDeleteAfter = null;
+ private ImageButton mBrowse = null;
+
+ private String mInputFilename = null;
+ private String mOutputFilename = null;
+
+ private boolean mAsciiArmourDemand = false;
+ private boolean mOverrideAsciiArmour = false;
+ private Uri mContentUri = null;
+ private byte[] mData = null;
+
+ private DataSource mDataSource = null;
+ private DataDestination mDataDestination = null;
+
+ private boolean mGenerateSignature = false;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.encrypt);
+
+ mGenerateSignature = false;
+
+ 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 = (Button) findViewById(R.id.btn_selectEncryptKeys);
+ mEncryptButton = (Button) findViewById(R.id.btn_encrypt);
+ mEncryptToClipboardButton = (Button) findViewById(R.id.btn_encryptToClipboard);
+ 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 = (ImageButton) findViewById(R.id.btn_browse);
+ mBrowse.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ openFile();
+ }
+ });
+
+ mFileCompression = (Spinner) findViewById(R.id.fileCompression);
+ Choice[] choices = new Choice[] {
+ new Choice(Id.choice.compression.none, getString(R.string.choice_none) + " ("
+ + getString(R.string.fast) + ")"),
+ new Choice(Id.choice.compression.zip, "ZIP (" + getString(R.string.fast) + ")"),
+ new Choice(Id.choice.compression.zlib, "ZLIB (" + getString(R.string.fast) + ")"),
+ new Choice(Id.choice.compression.bzip2, "BZIP2 (" + getString(R.string.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 = mPreferences.getDefaultFileCompression();
+ for (int i = 0; i < choices.length; ++i) {
+ if (choices[i].getId() == defaultFileCompression) {
+ mFileCompression.setSelection(i);
+ break;
+ }
+ }
+
+ mDeleteAfter = (CheckBox) findViewById(R.id.deleteAfterEncryption);
+
+ mAsciiArmour = (CheckBox) findViewById(R.id.asciiArmour);
+ mAsciiArmour.setChecked(mPreferences.getDefaultAsciiArmour());
+ mAsciiArmour.setOnClickListener(new OnClickListener() {
+ public void onClick(View view) {
+ guessOutputFilename();
+ }
+ });
+
+ mEncryptToClipboardButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ encryptToClipboardClicked();
+ }
+ });
+
+ mEncryptButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ encryptClicked();
+ }
+ });
+
+ 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 {
+ setSecretKeyId(Id.key.none);
+ updateView();
+ }
+ }
+ });
+
+ mIntent = getIntent();
+ if (Apg.Intent.ENCRYPT.equals(mIntent.getAction())
+ || Apg.Intent.ENCRYPT_FILE.equals(mIntent.getAction())
+ || Apg.Intent.ENCRYPT_AND_RETURN.equals(mIntent.getAction())
+ || Apg.Intent.GENERATE_SIGNATURE.equals(mIntent.getAction())) {
+ mContentUri = mIntent.getData();
+ Bundle extras = mIntent.getExtras();
+ if (extras == null) {
+ extras = new Bundle();
+ }
+
+ if (Apg.Intent.ENCRYPT_AND_RETURN.equals(mIntent.getAction())
+ || Apg.Intent.GENERATE_SIGNATURE.equals(mIntent.getAction())) {
+ mReturnResult = true;
+ }
+
+ if (Apg.Intent.GENERATE_SIGNATURE.equals(mIntent.getAction())) {
+ mGenerateSignature = true;
+ mOverrideAsciiArmour = true;
+ mAsciiArmourDemand = false;
+ }
+
+ if (extras.containsKey(Apg.EXTRA_ASCII_ARMOUR)) {
+ mAsciiArmourDemand = extras.getBoolean(Apg.EXTRA_ASCII_ARMOUR, true);
+ mOverrideAsciiArmour = true;
+ mAsciiArmour.setChecked(mAsciiArmourDemand);
+ }
+
+ mData = extras.getByteArray(Apg.EXTRA_DATA);
+ String textData = null;
+ if (mData == null) {
+ textData = extras.getString(Apg.EXTRA_TEXT);
+ }
+ mSendTo = extras.getString(Apg.EXTRA_SEND_TO);
+ mSubject = extras.getString(Apg.EXTRA_SUBJECT);
+ long signatureKeyId = extras.getLong(Apg.EXTRA_SIGNATURE_KEY_ID);
+ long encryptionKeyIds[] = extras.getLongArray(Apg.EXTRA_ENCRYPTION_KEY_IDS);
+ if (signatureKeyId != 0) {
+ PGPSecretKeyRing keyRing = Apg.getSecretKeyRing(signatureKeyId);
+ PGPSecretKey masterKey = null;
+ if (keyRing != null) {
+ masterKey = Apg.getMasterKey(keyRing);
+ if (masterKey != null) {
+ Vector<PGPSecretKey> signKeys = Apg.getUsableSigningKeys(keyRing);
+ if (signKeys.size() > 0) {
+ setSecretKeyId(masterKey.getKeyID());
+ }
+ }
+ }
+ }
+
+ if (encryptionKeyIds != null) {
+ Vector<Long> goodIds = new Vector<Long>();
+ for (int i = 0; i < encryptionKeyIds.length; ++i) {
+ PGPPublicKeyRing keyRing = Apg.getPublicKeyRing(encryptionKeyIds[i]);
+ PGPPublicKey masterKey = null;
+ if (keyRing == null) {
+ continue;
+ }
+ masterKey = Apg.getMasterKey(keyRing);
+ if (masterKey == null) {
+ continue;
+ }
+ Vector<PGPPublicKey> encryptKeys = Apg.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);
+ }
+ }
+ }
+
+ if (Apg.Intent.ENCRYPT.equals(mIntent.getAction())
+ || Apg.Intent.ENCRYPT_AND_RETURN.equals(mIntent.getAction())
+ || Apg.Intent.GENERATE_SIGNATURE.equals(mIntent.getAction())) {
+ if (textData != null) {
+ mMessage.setText(textData);
+ }
+ mSource.setInAnimation(null);
+ mSource.setOutAnimation(null);
+ while (mSource.getCurrentView().getId() != R.id.sourceMessage) {
+ mSource.showNext();
+ }
+ } else if (Apg.Intent.ENCRYPT_FILE.equals(mIntent.getAction())) {
+ if ("file".equals(mIntent.getScheme())) {
+ mInputFilename = Uri.decode(mIntent.getDataString().replace("file://", ""));
+ mFilename.setText(mInputFilename);
+ guessOutputFilename();
+ }
+ mSource.setInAnimation(null);
+ mSource.setOutAnimation(null);
+ while (mSource.getCurrentView().getId() != R.id.sourceFile) {
+ mSource.showNext();
+ }
+ }
+ }
+
+ updateView();
+ updateSource();
+ updateMode();
+
+ 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);
+ }
+
+ updateButtons();
+
+ if (mReturnResult
+ && (mMessage.getText().length() > 0 || mData != null || mContentUri != null)
+ && ((mEncryptionKeyIds != null && mEncryptionKeyIds.length > 0) || getSecretKeyId() != 0)) {
+ encryptClicked();
+ }
+ }
+
+ private void openFile() {
+ String filename = mFilename.getText().toString();
+
+ Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+ intent.addCategory(Intent.CATEGORY_OPENABLE);
+
+ intent.setData(Uri.parse("file://" + filename));
+ intent.setType("*/*");
+
+ try {
+ startActivityForResult(intent, Id.request.filename);
+ } catch (ActivityNotFoundException e) {
+ // No compatible file manager was found.
+ Toast.makeText(this, R.string.noFilemanagerInstalled, Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ private void guessOutputFilename() {
+ mInputFilename = mFilename.getText().toString();
+ File file = new File(mInputFilename);
+ String ending = (mAsciiArmour.isChecked() ? ".asc" : ".gpg");
+ mOutputFilename = Constants.path.APP_DIR + "/" + file.getName() + ending;
+ }
+
+ 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;
+ }
+ }
+ updateButtons();
+ }
+
+ private void updateButtons() {
+ switch (mSource.getCurrentView().getId()) {
+ case R.id.sourceFile: {
+ mEncryptToClipboardButton.setVisibility(View.INVISIBLE);
+ mEncryptButton.setText(R.string.btn_encrypt);
+ break;
+ }
+
+ case R.id.sourceMessage: {
+ mSourceLabel.setText(R.string.label_message);
+ if (mReturnResult) {
+ mEncryptToClipboardButton.setVisibility(View.INVISIBLE);
+ } else {
+ mEncryptToClipboardButton.setVisibility(View.VISIBLE);
+ }
+ if (mMode.getCurrentView().getId() == R.id.modeSymmetric) {
+ if (mReturnResult) {
+ mEncryptButton.setText(R.string.btn_encrypt);
+ } else {
+ mEncryptButton.setText(R.string.btn_encryptAndEmail);
+ }
+ mEncryptButton.setEnabled(true);
+ mEncryptToClipboardButton.setText(R.string.btn_encryptToClipboard);
+ mEncryptToClipboardButton.setEnabled(true);
+ } else {
+ if (mEncryptionKeyIds == null || mEncryptionKeyIds.length == 0) {
+ if (getSecretKeyId() == 0) {
+ if (mReturnResult) {
+ mEncryptButton.setText(R.string.btn_encrypt);
+ } else {
+ mEncryptButton.setText(R.string.btn_encryptAndEmail);
+ }
+ mEncryptButton.setEnabled(false);
+ mEncryptToClipboardButton.setText(R.string.btn_encryptToClipboard);
+ mEncryptToClipboardButton.setEnabled(false);
+ } else {
+ if (mReturnResult) {
+ mEncryptButton.setText(R.string.btn_sign);
+ } else {
+ mEncryptButton.setText(R.string.btn_signAndEmail);
+ }
+ mEncryptButton.setEnabled(true);
+ mEncryptToClipboardButton.setText(R.string.btn_signToClipboard);
+ mEncryptToClipboardButton.setEnabled(true);
+ }
+ } else {
+ if (mReturnResult) {
+ mEncryptButton.setText(R.string.btn_encrypt);
+ } else {
+ mEncryptButton.setText(R.string.btn_encryptAndEmail);
+ }
+ mEncryptButton.setEnabled(true);
+ mEncryptToClipboardButton.setText(R.string.btn_encryptToClipboard);
+ mEncryptToClipboardButton.setEnabled(true);
+ }
+ }
+ 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;
+ }
+ }
+ updateButtons();
+ }
+
+ private void encryptToClipboardClicked() {
+ mEncryptTarget = Id.target.clipboard;
+ initiateEncryption();
+ }
+
+ private void encryptClicked() {
+ 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)) {
+ guessOutputFilename();
+ }
+
+ if (mInputFilename.equals("")) {
+ Toast.makeText(this, R.string.noFileSelected, 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.errorMessage, getString(R.string.error_fileNotFound)),
+ 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.passPhrasesDoNotMatch, Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ gotPassPhrase = (passPhrase.length() != 0);
+ if (!gotPassPhrase) {
+ Toast.makeText(this, R.string.passPhraseMustNotBeEmpty, 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.selectEncryptionKey, Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ if (!encryptIt && getSecretKeyId() == 0) {
+ Toast.makeText(this, R.string.selectEncryptionOrSignatureKey, Toast.LENGTH_SHORT)
+ .show();
+ return;
+ }
+
+ if (getSecretKeyId() != 0 && Apg.getCachedPassPhrase(getSecretKeyId()) == null) {
+ showDialog(Id.dialog.pass_phrase);
+ return;
+ }
+ }
+
+ if (mEncryptTarget == Id.target.file) {
+ askForOutputFilename();
+ } else {
+ encryptStart();
+ }
+ }
+
+ private void askForOutputFilename() {
+ showDialog(Id.dialog.output_filename);
+ }
+
+ @Override
+ public void passPhraseCallback(long keyId, String passPhrase) {
+ super.passPhraseCallback(keyId, passPhrase);
+ if (mEncryptTarget == Id.target.file) {
+ askForOutputFilename();
+ } else {
+ encryptStart();
+ }
+ }
+
+ private void encryptStart() {
+ showDialog(Id.dialog.encrypting);
+ startThread();
+ }
+
+ @Override
+ public void run() {
+ String error = null;
+ Bundle data = new Bundle();
+ Message msg = new Message();
+
+ try {
+ InputData in;
+ OutputStream out;
+ boolean useAsciiArmour = true;
+ long encryptionKeyIds[] = null;
+ long signatureKeyId = 0;
+ int compressionId = 0;
+ boolean signOnly = false;
+
+ String passPhrase = null;
+ if (mMode.getCurrentView().getId() == R.id.modeSymmetric) {
+ passPhrase = mPassPhrase.getText().toString();
+ if (passPhrase.length() == 0) {
+ passPhrase = null;
+ }
+ } else {
+ encryptionKeyIds = mEncryptionKeyIds;
+ signatureKeyId = getSecretKeyId();
+ signOnly = (mEncryptionKeyIds == null || mEncryptionKeyIds.length == 0);
+ }
+
+ fillDataSource(signOnly && !mReturnResult);
+ fillDataDestination();
+
+ // streams
+ in = mDataSource.getInputData(this, true);
+ out = mDataDestination.getOutputStream(this);
+
+ if (mEncryptTarget == Id.target.file) {
+ useAsciiArmour = mAsciiArmour.isChecked();
+ compressionId = ((Choice) mFileCompression.getSelectedItem()).getId();
+ } else {
+ useAsciiArmour = true;
+ compressionId = mPreferences.getDefaultMessageCompression();
+ }
+
+ if (mOverrideAsciiArmour) {
+ useAsciiArmour = mAsciiArmourDemand;
+ }
+
+ if (mGenerateSignature) {
+ Apg.generateSignature(this, in, out, useAsciiArmour, mDataSource.isBinary(),
+ getSecretKeyId(), Apg.getCachedPassPhrase(getSecretKeyId()),
+ mPreferences.getDefaultHashAlgorithm(),
+ mPreferences.getForceV3Signatures(), this);
+ } else if (signOnly) {
+ Apg.signText(this, in, out, getSecretKeyId(),
+ Apg.getCachedPassPhrase(getSecretKeyId()),
+ mPreferences.getDefaultHashAlgorithm(),
+ mPreferences.getForceV3Signatures(), this);
+ } else {
+ Apg.encrypt(this, in, out, useAsciiArmour, encryptionKeyIds, signatureKeyId,
+ Apg.getCachedPassPhrase(signatureKeyId), this,
+ mPreferences.getDefaultEncryptionAlgorithm(),
+ mPreferences.getDefaultHashAlgorithm(), compressionId,
+ mPreferences.getForceV3Signatures(), passPhrase);
+ }
+
+ out.close();
+ if (mEncryptTarget != Id.target.file) {
+
+ if (out instanceof ByteArrayOutputStream) {
+ if (useAsciiArmour) {
+ String extraData = new String(((ByteArrayOutputStream) out).toByteArray());
+ if (mGenerateSignature) {
+ data.putString(Apg.EXTRA_SIGNATURE_TEXT, extraData);
+ } else {
+ data.putString(Apg.EXTRA_ENCRYPTED_MESSAGE, extraData);
+ }
+ } else {
+ byte extraData[] = ((ByteArrayOutputStream) out).toByteArray();
+ if (mGenerateSignature) {
+ data.putByteArray(Apg.EXTRA_SIGNATURE_DATA, extraData);
+ } else {
+ data.putByteArray(Apg.EXTRA_ENCRYPTED_DATA, extraData);
+ }
+ }
+ } else if (out instanceof FileOutputStream) {
+ String fileName = mDataDestination.getStreamFilename();
+ String uri = "content://" + DataProvider.AUTHORITY + "/data/" + fileName;
+ data.putString(Apg.EXTRA_RESULT_URI, uri);
+ } else {
+ throw new Apg.GeneralException("No output-data found.");
+ }
+ }
+ } catch (IOException e) {
+ error = "" + e;
+ } catch (PGPException e) {
+ error = "" + e;
+ } catch (NoSuchProviderException e) {
+ error = "" + e;
+ } catch (NoSuchAlgorithmException e) {
+ error = "" + e;
+ } catch (SignatureException e) {
+ error = "" + e;
+ } catch (Apg.GeneralException e) {
+ error = "" + e;
+ }
+
+ data.putInt(Constants.extras.STATUS, Id.message.done);
+
+ if (error != null) {
+ data.putString(Apg.EXTRA_ERROR, error);
+ }
+
+ msg.setData(data);
+ sendMessage(msg);
+ }
+
+ private void updateView() {
+ if (mEncryptionKeyIds == null || mEncryptionKeyIds.length == 0) {
+ mSelectKeysButton.setText(R.string.noKeysSelected);
+ } else if (mEncryptionKeyIds.length == 1) {
+ mSelectKeysButton.setText(R.string.oneKeySelected);
+ } else {
+ mSelectKeysButton.setText("" + mEncryptionKeyIds.length + " "
+ + getResources().getString(R.string.nKeysSelected));
+ }
+
+ if (getSecretKeyId() == 0) {
+ mSign.setChecked(false);
+ mMainUserId.setText("");
+ mMainUserIdRest.setText("");
+ } else {
+ String uid = getResources().getString(R.string.unknownUserId);
+ String uidExtra = "";
+ PGPSecretKeyRing keyRing = Apg.getSecretKeyRing(getSecretKeyId());
+ if (keyRing != null) {
+ PGPSecretKey key = Apg.getMasterKey(keyRing);
+ if (key != null) {
+ String userId = Apg.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);
+ }
+
+ updateButtons();
+ }
+
+ private void selectPublicKeys() {
+ Intent intent = new Intent(this, SelectPublicKeyListActivity.class);
+ Vector<Long> keyIds = new Vector<Long>();
+ if (getSecretKeyId() != 0) {
+ keyIds.add(getSecretKeyId());
+ }
+ 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(Apg.EXTRA_SELECTION, initialKeyIds);
+ startActivityForResult(intent, Id.request.public_keys);
+ }
+
+ private void selectSecretKey() {
+ Intent intent = new Intent(this, SelectSecretKeyListActivity.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) {
+ String filename = data.getDataString();
+ if (filename != null) {
+ // Get rid of URI prefix:
+ if (filename.startsWith("file://")) {
+ filename = filename.substring(7);
+ }
+ // replace %20 and so on
+ filename = Uri.decode(filename);
+
+ mFilename.setText(filename);
+ }
+ }
+ return;
+ }
+
+ case Id.request.output_filename: {
+ if (resultCode == RESULT_OK && data != null) {
+ String filename = data.getDataString();
+ if (filename != null) {
+ // Get rid of URI prefix:
+ if (filename.startsWith("file://")) {
+ filename = filename.substring(7);
+ }
+ // replace %20 and so on
+ filename = Uri.decode(filename);
+
+ FileDialog.setFilename(filename);
+ }
+ }
+ return;
+ }
+
+ case Id.request.secret_keys: {
+ if (resultCode == RESULT_OK) {
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+ updateView();
+ break;
+ }
+
+ case Id.request.public_keys: {
+ if (resultCode == RESULT_OK) {
+ Bundle bundle = data.getExtras();
+ mEncryptionKeyIds = bundle.getLongArray(Apg.EXTRA_SELECTION);
+ }
+ updateView();
+ break;
+ }
+
+ default: {
+ break;
+ }
+ }
+
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+
+ @Override
+ public void doneCallback(Message msg) {
+ super.doneCallback(msg);
+
+ removeDialog(Id.dialog.encrypting);
+
+ Bundle data = msg.getData();
+ String error = data.getString(Apg.EXTRA_ERROR);
+ if (error != null) {
+ Toast.makeText(this, getString(R.string.errorMessage, error), Toast.LENGTH_SHORT)
+ .show();
+ return;
+ }
+ switch (mEncryptTarget) {
+ case Id.target.clipboard: {
+ String message = data.getString(Apg.EXTRA_ENCRYPTED_MESSAGE);
+ Compatibility.copyToClipboard(this, message);
+ Toast.makeText(this, R.string.encryptionToClipboardSuccessful, Toast.LENGTH_SHORT)
+ .show();
+ break;
+ }
+
+ case Id.target.email: {
+ if (mReturnResult) {
+ Intent intent = new Intent();
+ intent.putExtras(data);
+ setResult(RESULT_OK, intent);
+ finish();
+ return;
+ }
+
+ String message = data.getString(Apg.EXTRA_ENCRYPTED_MESSAGE);
+ Intent emailIntent = new Intent(android.content.Intent.ACTION_SEND);
+ emailIntent.setType("text/plain; charset=utf-8");
+ emailIntent.putExtra(android.content.Intent.EXTRA_TEXT, message);
+ if (mSubject != null) {
+ emailIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, mSubject);
+ }
+ if (mSendTo != null) {
+ emailIntent.putExtra(android.content.Intent.EXTRA_EMAIL, new String[] { mSendTo });
+ }
+ EncryptActivity.this.startActivity(Intent.createChooser(emailIntent,
+ getString(R.string.title_sendEmail)));
+ break;
+ }
+
+ case Id.target.file: {
+ Toast.makeText(this, R.string.encryptionSuccessful, Toast.LENGTH_SHORT).show();
+ if (mDeleteAfter.isChecked()) {
+ setDeleteFile(mInputFilename);
+ showDialog(Id.dialog.delete_file);
+ }
+ break;
+ }
+
+ default: {
+ // shouldn't happen
+ break;
+ }
+ }
+ }
+
+ @Override
+ protected Dialog onCreateDialog(int id) {
+ switch (id) {
+ case Id.dialog.output_filename: {
+ return FileDialog.build(this, getString(R.string.title_encryptToFile),
+ getString(R.string.specifyFileToEncryptTo), mOutputFilename,
+ new FileDialog.OnClickListener() {
+ public void onOkClick(String filename, boolean checked) {
+ removeDialog(Id.dialog.output_filename);
+ mOutputFilename = filename;
+ encryptStart();
+ }
+
+ public void onCancelClick() {
+ removeDialog(Id.dialog.output_filename);
+ }
+ }, getString(R.string.filemanager_titleSave),
+ getString(R.string.filemanager_btnSave), null, Id.request.output_filename);
+ }
+
+ default: {
+ break;
+ }
+ }
+
+ return super.onCreateDialog(id);
+ }
+
+ protected void fillDataSource(boolean fixContent) {
+ mDataSource = new DataSource();
+ if (mContentUri != null) {
+ mDataSource.setUri(mContentUri);
+ } else if (mEncryptTarget == Id.target.file) {
+ mDataSource.setUri(mInputFilename);
+ } else {
+ if (mData != null) {
+ mDataSource.setData(mData);
+ } else {
+ String message = mMessage.getText().toString();
+ if (fixContent) {
+ // 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");
+ }
+ mDataSource.setText(message);
+ }
+ }
+ }
+
+ protected void fillDataDestination() {
+ mDataDestination = new DataDestination();
+ if (mContentUri != null) {
+ mDataDestination.setMode(Id.mode.stream);
+ } else if (mEncryptTarget == Id.target.file) {
+ mDataDestination.setFilename(mOutputFilename);
+ mDataDestination.setMode(Id.mode.file);
+ } else {
+ mDataDestination.setMode(Id.mode.byte_array);
+ }
+ }
+}
diff --git a/org_apg/src/org/apg/ui/GeneralActivity.java b/org_apg/src/org/apg/ui/GeneralActivity.java
new file mode 100644
index 000000000..d70694630
--- /dev/null
+++ b/org_apg/src/org/apg/ui/GeneralActivity.java
@@ -0,0 +1,177 @@
+package org.apg.ui;
+
+import java.io.ByteArrayInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Vector;
+
+import org.apg.Apg;
+import org.apg.Id;
+import org.apg.util.Choice;
+import org.apg.R;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.ListView;
+import android.widget.Toast;
+
+public class GeneralActivity extends BaseActivity {
+ private Intent mIntent;
+ private ArrayAdapter<Choice> mAdapter;
+ private ListView mList;
+ private Button mCancelButton;
+ private String mDataString;
+ private Uri mDataUri;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.general);
+
+ mIntent = getIntent();
+
+ InputStream inStream = null;
+ {
+ String data = mIntent.getStringExtra(Intent.EXTRA_TEXT);
+ if (data != null) {
+ mDataString = data;
+ inStream = new ByteArrayInputStream(data.getBytes());
+ }
+ }
+
+ if (inStream == null) {
+ Uri data = mIntent.getData();
+ if (data != null) {
+ mDataUri = data;
+ try {
+ inStream = getContentResolver().openInputStream(data);
+ } catch (FileNotFoundException e) {
+ // didn't work
+ Toast.makeText(this, "failed to open stream", Toast.LENGTH_SHORT).show();
+ }
+ }
+ }
+
+ if (inStream == null) {
+ Toast.makeText(this, "no data found", Toast.LENGTH_SHORT).show();
+ finish();
+ return;
+ }
+
+ int contentType = Id.content.unknown;
+ try {
+ contentType = Apg.getStreamContent(this, inStream);
+ inStream.close();
+ } catch (IOException e) {
+ // just means that there's no PGP data in there
+ }
+
+ mList = (ListView) findViewById(R.id.options);
+ Vector<Choice> choices = new Vector<Choice>();
+
+ if (contentType == Id.content.keys) {
+ choices.add(new Choice(Id.choice.action.import_public,
+ getString(R.string.action_importPublic)));
+ choices.add(new Choice(Id.choice.action.import_secret,
+ getString(R.string.action_importSecret)));
+ }
+
+ if (contentType == Id.content.encrypted_data) {
+ choices.add(new Choice(Id.choice.action.decrypt, getString(R.string.action_decrypt)));
+ }
+
+ if (contentType == Id.content.unknown) {
+ choices.add(new Choice(Id.choice.action.encrypt, getString(R.string.action_encrypt)));
+ }
+
+ mAdapter = new ArrayAdapter<Choice>(this, android.R.layout.simple_list_item_1, choices);
+ mList.setAdapter(mAdapter);
+
+ mList.setOnItemClickListener(new OnItemClickListener() {
+ public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
+ clicked(mAdapter.getItem(arg2).getId());
+ }
+ });
+
+ mCancelButton = (Button) findViewById(R.id.btn_cancel);
+ mCancelButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ GeneralActivity.this.finish();
+ }
+ });
+
+ if (choices.size() == 1) {
+ clicked(choices.get(0).getId());
+ }
+ }
+
+ private void clicked(int id) {
+ Intent intent = new Intent();
+ switch (id) {
+ case Id.choice.action.encrypt: {
+ intent.setClass(this, EncryptActivity.class);
+ if (mDataString != null) {
+ intent.setAction(Apg.Intent.ENCRYPT);
+ intent.putExtra(Apg.EXTRA_TEXT, mDataString);
+ } else if (mDataUri != null) {
+ intent.setAction(Apg.Intent.ENCRYPT_FILE);
+ intent.setDataAndType(mDataUri, mIntent.getType());
+ }
+
+ break;
+ }
+
+ case Id.choice.action.decrypt: {
+ intent.setClass(this, DecryptActivity.class);
+ if (mDataString != null) {
+ intent.setAction(Apg.Intent.DECRYPT);
+ intent.putExtra(Apg.EXTRA_TEXT, mDataString);
+ } else if (mDataUri != null) {
+ intent.setAction(Apg.Intent.DECRYPT_FILE);
+ intent.setDataAndType(mDataUri, mIntent.getType());
+ }
+
+ break;
+ }
+
+ case Id.choice.action.import_public: {
+ intent.setClass(this, PublicKeyListActivity.class);
+ intent.setAction(Apg.Intent.IMPORT);
+ if (mDataString != null) {
+ intent.putExtra(Apg.EXTRA_TEXT, mDataString);
+ } else if (mDataUri != null) {
+ intent.setDataAndType(mDataUri, mIntent.getType());
+ }
+ break;
+ }
+
+ case Id.choice.action.import_secret: {
+ intent.setClass(this, SecretKeyListActivity.class);
+ intent.setAction(Apg.Intent.IMPORT);
+ if (mDataString != null) {
+ intent.putExtra(Apg.EXTRA_TEXT, mDataString);
+ } else if (mDataUri != null) {
+ intent.setDataAndType(mDataUri, mIntent.getType());
+ }
+ break;
+ }
+
+ default: {
+ // shouldn't happen
+ return;
+ }
+ }
+
+ startActivity(intent);
+ finish();
+ }
+}
diff --git a/org_apg/src/org/apg/ui/ImportFromQRCodeActivity.java b/org_apg/src/org/apg/ui/ImportFromQRCodeActivity.java
new file mode 100644
index 000000000..593c841df
--- /dev/null
+++ b/org_apg/src/org/apg/ui/ImportFromQRCodeActivity.java
@@ -0,0 +1,138 @@
+package org.apg.ui;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+
+import org.apg.Apg;
+import org.apg.Constants;
+import org.apg.HkpKeyServer;
+import org.apg.Id;
+import org.apg.KeyServer.QueryException;
+import org.spongycastle.openpgp.PGPKeyRing;
+import org.spongycastle.openpgp.PGPPublicKeyRing;
+import org.apg.R;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Message;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.google.zxing.integration.android.IntentIntegrator;
+import com.google.zxing.integration.android.IntentResult;
+
+public class ImportFromQRCodeActivity extends BaseActivity {
+ private static final String TAG = "ImportFromQRCodeActivity";
+
+ private final Bundle status = new Bundle();
+ private final Message msg = new Message();
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ new IntentIntegrator(this).initiateScan();
+ }
+
+ private void importAndSign(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
+
+ HkpKeyServer server = new HkpKeyServer(mPreferences.getKeyServers()[0]); // TODO: there should be only 1
+ String encodedKey = server.get(keyId);
+
+ PGPKeyRing keyring = Apg.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 = Apg.convertToHex(publicKeyRing.getPublicKey().getFingerprint());
+ if (expectedFingerprint.equals(actualFingerprint)) {
+ // store the signed key in our local cache
+ int retval = Apg.storeKeyRingInCache(publicKeyRing);
+ if (retval != Id.return_value.ok && retval != Id.return_value.updated) {
+ status.putString(Apg.EXTRA_ERROR, "Failed to store signed key in local cache");
+ } else {
+ Intent intent = new Intent(ImportFromQRCodeActivity.this, SignKeyActivity.class);
+ intent.putExtra(Apg.EXTRA_KEY_ID, keyId);
+ startActivityForResult(intent, Id.request.sign_key);
+ }
+ } else {
+ status.putString(Apg.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(Apg.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(Apg.EXTRA_ERROR, "Failed to query KeyServer");
+ status.putInt(Constants.extras.STATUS, Id.message.done);
+ }
+ }
+ };
+
+ t.setName("KeyExchange Download Thread");
+ t.setDaemon(true);
+ t.start();
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ switch (requestCode) {
+ case IntentIntegrator.REQUEST_CODE: {
+ boolean debug = true; // TODO: remove this!!!
+ IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, data);
+ if (debug || (scanResult != null && scanResult.getFormatName() != null)) {
+ String[] bits = debug ? new String[] { "5993515643896327656", "0816 F68A 6816 68FB 01BF 2CA5 532D 3EB9 1E2F EDE8" } : scanResult.getContents().split(",");
+ if (bits.length != 2) {
+ return; // dont know how to handle this. Not a valid code
+ }
+
+ long keyId = Long.parseLong(bits[0]);
+ String expectedFingerprint = bits[1];
+
+ importAndSign(keyId, expectedFingerprint);
+
+ break;
+ }
+ }
+
+ case Id.request.sign_key: {
+ // signals the end of processing. Signature was either applied, or it wasnt
+ status.putInt(Constants.extras.STATUS, Id.message.done);
+
+ msg.setData(status);
+ sendMessage(msg);
+
+ break;
+ }
+
+ default: {
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+ }
+ }
+
+ @Override
+ public void doneCallback(Message msg) {
+ super.doneCallback(msg);
+
+ Bundle data = msg.getData();
+ String error = data.getString(Apg.EXTRA_ERROR);
+ if (error != null) {
+ Toast.makeText(this, getString(R.string.errorMessage, error), Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ Toast.makeText(this, R.string.keySignSuccess, Toast.LENGTH_SHORT).show(); // TODO
+ finish();
+ }
+}
diff --git a/org_apg/src/org/apg/ui/KeyListActivity.java b/org_apg/src/org/apg/ui/KeyListActivity.java
new file mode 100644
index 000000000..6c76f02bc
--- /dev/null
+++ b/org_apg/src/org/apg/ui/KeyListActivity.java
@@ -0,0 +1,768 @@
+/*
+ * 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.apg.ui;
+
+import org.apg.Apg;
+import org.apg.Constants;
+import org.apg.FileDialog;
+import org.apg.Id;
+import org.apg.InputData;
+import org.apg.provider.KeyRings;
+import org.apg.provider.Keys;
+import org.apg.provider.UserIds;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPPublicKeyRing;
+import org.spongycastle.openpgp.PGPSecretKeyRing;
+import org.apg.R;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.SearchManager;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Message;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.BaseExpandableListAdapter;
+import android.widget.Button;
+import android.widget.ExpandableListView;
+import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.io.ByteArrayInputStream;
+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.Vector;
+
+public class KeyListActivity extends BaseActivity {
+ protected ExpandableListView mList;
+ protected KeyListAdapter mListAdapter;
+ protected View mFilterLayout;
+ protected Button mClearFilterButton;
+ protected TextView mFilterInfo;
+
+ protected int mSelectedItem = -1;
+ protected int mTask = 0;
+
+ protected String mImportFilename = Constants.path.APP_DIR + "/";
+ protected String mExportFilename = Constants.path.APP_DIR + "/";
+
+ protected String mImportData;
+ protected boolean mDeleteAfterImport = false;
+
+ protected int mKeyType = Id.type.public_key;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.key_list);
+
+ setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
+
+ mList = (ExpandableListView) findViewById(R.id.list);
+ registerForContextMenu(mList);
+
+ 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());
+ }
+ });
+
+ handleIntent(getIntent());
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ handleIntent(intent);
+ }
+
+ protected void handleIntent(Intent intent) {
+ 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));
+ }
+
+ if (mListAdapter != null) {
+ mListAdapter.cleanup();
+ }
+ mListAdapter = new KeyListAdapter(this, searchString);
+ mList.setAdapter(mListAdapter);
+
+ if (Apg.Intent.IMPORT.equals(intent.getAction())) {
+ if ("file".equals(intent.getScheme()) && intent.getDataString() != null) {
+ mImportFilename = Uri.decode(intent.getDataString().replace("file://", ""));
+ } else {
+ mImportData = intent.getStringExtra(Apg.EXTRA_TEXT);
+ }
+ importKeys();
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case Id.menu.option.import_keys: {
+ showDialog(Id.dialog.import_keys);
+ return true;
+ }
+
+ case Id.menu.option.export_keys: {
+ showDialog(Id.dialog.export_keys);
+ return true;
+ }
+
+ default: {
+ return super.onOptionsItemSelected(item);
+ }
+ }
+ }
+
+ @Override
+ public boolean onContextItemSelected(MenuItem menuItem) {
+ ExpandableListContextMenuInfo info = (ExpandableListContextMenuInfo) menuItem.getMenuInfo();
+ int type = ExpandableListView.getPackedPositionType(info.packedPosition);
+ int groupPosition = ExpandableListView.getPackedPositionGroup(info.packedPosition);
+
+ if (type != ExpandableListView.PACKED_POSITION_TYPE_GROUP) {
+ return super.onContextItemSelected(menuItem);
+ }
+
+ switch (menuItem.getItemId()) {
+ case Id.menu.export: {
+ mSelectedItem = groupPosition;
+ showDialog(Id.dialog.export_key);
+ return true;
+ }
+
+ case Id.menu.delete: {
+ mSelectedItem = groupPosition;
+ showDialog(Id.dialog.delete_key);
+ return true;
+ }
+
+ default: {
+ return super.onContextItemSelected(menuItem);
+ }
+ }
+ }
+
+ @Override
+ protected Dialog onCreateDialog(int id) {
+ boolean singleKeyExport = false;
+
+ switch (id) {
+ case Id.dialog.delete_key: {
+ final int keyRingId = mListAdapter.getKeyRingId(mSelectedItem);
+ mSelectedItem = -1;
+ // TODO: better way to do this?
+ String userId = "<unknown>";
+ Object keyRing = Apg.getKeyRing(keyRingId);
+ if (keyRing != null) {
+ if (keyRing instanceof PGPPublicKeyRing) {
+ userId = Apg.getMainUserIdSafe(this,
+ Apg.getMasterKey((PGPPublicKeyRing) keyRing));
+ } else {
+ userId = Apg.getMainUserIdSafe(this,
+ Apg.getMasterKey((PGPSecretKeyRing) keyRing));
+ }
+ }
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(R.string.warning);
+ builder.setMessage(getString(
+ mKeyType == Id.type.public_key ? R.string.keyDeletionConfirmation
+ : R.string.secretKeyDeletionConfirmation, userId));
+ builder.setIcon(android.R.drawable.ic_dialog_alert);
+ builder.setPositiveButton(R.string.btn_delete, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ deleteKey(keyRingId);
+ removeDialog(Id.dialog.delete_key);
+ }
+ });
+ builder.setNegativeButton(android.R.string.cancel,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ removeDialog(Id.dialog.delete_key);
+ }
+ });
+ return builder.create();
+ }
+
+ case Id.dialog.import_keys: {
+ return FileDialog.build(this, getString(R.string.title_importKeys),
+ getString(R.string.specifyFileToImportFrom), mImportFilename,
+ new FileDialog.OnClickListener() {
+ public void onOkClick(String filename, boolean checked) {
+ removeDialog(Id.dialog.import_keys);
+ mDeleteAfterImport = checked;
+ mImportFilename = filename;
+ importKeys();
+ }
+
+ public void onCancelClick() {
+ removeDialog(Id.dialog.import_keys);
+ }
+ }, getString(R.string.filemanager_titleOpen),
+ getString(R.string.filemanager_btnOpen),
+ getString(R.string.label_deleteAfterImport), Id.request.filename);
+ }
+
+ case Id.dialog.export_key: {
+ singleKeyExport = true;
+ // break intentionally omitted, to use the Id.dialog.export_keys dialog
+ }
+
+ case Id.dialog.export_keys: {
+ String title = (singleKeyExport ? getString(R.string.title_exportKey)
+ : getString(R.string.title_exportKeys));
+
+ final int thisDialogId = (singleKeyExport ? Id.dialog.export_key
+ : Id.dialog.export_keys);
+
+ return FileDialog.build(this, title,
+ getString(mKeyType == Id.type.public_key ? R.string.specifyFileToExportTo
+ : R.string.specifyFileToExportSecretKeysTo), mExportFilename,
+ new FileDialog.OnClickListener() {
+ public void onOkClick(String filename, boolean checked) {
+ removeDialog(thisDialogId);
+ mExportFilename = filename;
+ exportKeys();
+ }
+
+ public void onCancelClick() {
+ removeDialog(thisDialogId);
+ }
+ }, getString(R.string.filemanager_titleSave),
+ getString(R.string.filemanager_btnSave), null, Id.request.filename);
+ }
+
+ default: {
+ return super.onCreateDialog(id);
+ }
+ }
+ }
+
+ public void importKeys() {
+ showDialog(Id.dialog.importing);
+ mTask = Id.task.import_keys;
+ startThread();
+ }
+
+ public void exportKeys() {
+ showDialog(Id.dialog.exporting);
+ mTask = Id.task.export_keys;
+ startThread();
+ }
+
+ @Override
+ public void run() {
+ String error = null;
+ Bundle data = new Bundle();
+ Message msg = new Message();
+
+ try {
+ InputStream importInputStream = null;
+ OutputStream exportOutputStream = null;
+ long size = 0;
+ if (mTask == Id.task.import_keys) {
+ if (mImportData != null) {
+ byte[] bytes = mImportData.getBytes();
+ size = bytes.length;
+ importInputStream = new ByteArrayInputStream(bytes);
+ } else {
+ File file = new File(mImportFilename);
+ size = file.length();
+ importInputStream = new FileInputStream(file);
+ }
+ } else {
+ exportOutputStream = new FileOutputStream(mExportFilename);
+ }
+
+ if (mTask == Id.task.import_keys) {
+ data = Apg.importKeyRings(this, mKeyType, new InputData(importInputStream, size),
+ this);
+ } else {
+ Vector<Integer> keyRingIds = new Vector<Integer>();
+ if (mSelectedItem == -1) {
+ keyRingIds = Apg
+ .getKeyRingIds(mKeyType == Id.type.public_key ? Id.database.type_public
+ : Id.database.type_secret);
+ } else {
+ int keyRingId = mListAdapter.getKeyRingId(mSelectedItem);
+ keyRingIds.add(keyRingId);
+ mSelectedItem = -1;
+ }
+ data = Apg.exportKeyRings(this, keyRingIds, exportOutputStream, this);
+ }
+ } catch (FileNotFoundException e) {
+ error = getString(R.string.error_fileNotFound);
+ } catch (IOException e) {
+ error = "" + e;
+ } catch (PGPException e) {
+ error = "" + e;
+ } catch (Apg.GeneralException e) {
+ error = "" + e;
+ }
+
+ mImportData = null;
+
+ if (mTask == Id.task.import_keys) {
+ data.putInt(Constants.extras.STATUS, Id.message.import_done);
+ } else {
+ data.putInt(Constants.extras.STATUS, Id.message.export_done);
+ }
+
+ if (error != null) {
+ data.putString(Apg.EXTRA_ERROR, error);
+ }
+
+ msg.setData(data);
+ sendMessage(msg);
+ }
+
+ protected void deleteKey(int keyRingId) {
+ Apg.deleteKey(keyRingId);
+ refreshList();
+ }
+
+ protected void refreshList() {
+ mListAdapter.rebuild(true);
+ mListAdapter.notifyDataSetChanged();
+ }
+
+ @Override
+ public void doneCallback(Message msg) {
+ super.doneCallback(msg);
+
+ Bundle data = msg.getData();
+ if (data != null) {
+ int type = data.getInt(Constants.extras.STATUS);
+ switch (type) {
+ case Id.message.import_done: {
+ removeDialog(Id.dialog.importing);
+
+ String error = data.getString(Apg.EXTRA_ERROR);
+ if (error != null) {
+ Toast.makeText(KeyListActivity.this, getString(R.string.errorMessage, error),
+ Toast.LENGTH_SHORT).show();
+ } else {
+ int added = data.getInt("added");
+ int updated = data.getInt("updated");
+ int bad = data.getInt("bad");
+ String message;
+ if (added > 0 && updated > 0) {
+ message = getString(R.string.keysAddedAndUpdated, added, updated);
+ } else if (added > 0) {
+ message = getString(R.string.keysAdded, added);
+ } else if (updated > 0) {
+ message = getString(R.string.keysUpdated, updated);
+ } else {
+ message = getString(R.string.noKeysAddedOrUpdated);
+ }
+ Toast.makeText(KeyListActivity.this, message, Toast.LENGTH_SHORT).show();
+ if (bad > 0) {
+ AlertDialog.Builder alert = new AlertDialog.Builder(this);
+
+ alert.setIcon(android.R.drawable.ic_dialog_alert);
+ alert.setTitle(R.string.warning);
+ alert.setMessage(this.getString(R.string.badKeysEncountered, 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
+ setDeleteFile(mImportFilename);
+ showDialog(Id.dialog.delete_file);
+ }
+ }
+ refreshList();
+ break;
+ }
+
+ case Id.message.export_done: {
+ removeDialog(Id.dialog.exporting);
+
+ String error = data.getString(Apg.EXTRA_ERROR);
+ if (error != null) {
+ Toast.makeText(KeyListActivity.this, getString(R.string.errorMessage, error),
+ Toast.LENGTH_SHORT).show();
+ } else {
+ int exported = data.getInt("exported");
+ String message;
+ if (exported == 1) {
+ message = getString(R.string.keyExported);
+ } else if (exported > 0) {
+ message = getString(R.string.keysExported, exported);
+ } else {
+ message = getString(R.string.noKeysExported);
+ }
+ Toast.makeText(KeyListActivity.this, message, Toast.LENGTH_SHORT).show();
+ }
+ break;
+ }
+
+ default: {
+ break;
+ }
+ }
+ }
+ }
+
+ protected class KeyListAdapter extends BaseExpandableListAdapter {
+ private LayoutInflater mInflater;
+ private Vector<Vector<KeyChild>> mChildren;
+ private SQLiteDatabase mDatabase;
+ private Cursor mCursor;
+ private String mSearchString;
+
+ private class KeyChild {
+ public static final int KEY = 0;
+ public static final int USER_ID = 1;
+ public static final int FINGER_PRINT = 2;
+
+ public int type;
+ public String userId;
+ public long keyId;
+ public boolean isMasterKey;
+ public int algorithm;
+ public int keySize;
+ public boolean canSign;
+ public boolean canEncrypt;
+ public String fingerPrint;
+
+ public KeyChild(long keyId, boolean isMasterKey, int algorithm, int keySize,
+ boolean canSign, boolean canEncrypt) {
+ this.type = KEY;
+ this.keyId = keyId;
+ this.isMasterKey = isMasterKey;
+ this.algorithm = algorithm;
+ this.keySize = keySize;
+ this.canSign = canSign;
+ this.canEncrypt = canEncrypt;
+ }
+
+ public KeyChild(String userId) {
+ type = USER_ID;
+ this.userId = userId;
+ }
+
+ public KeyChild(String fingerPrint, boolean isFingerPrint) {
+ type = FINGER_PRINT;
+ this.fingerPrint = fingerPrint;
+ }
+ }
+
+ public KeyListAdapter(Context context, String searchString) {
+ mSearchString = searchString;
+
+ mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ mDatabase = Apg.getDatabase().db();
+ SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+ qb.setTables(KeyRings.TABLE_NAME + " INNER JOIN " + Keys.TABLE_NAME + " ON " + "("
+ + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " + Keys.TABLE_NAME + "."
+ + Keys.KEY_RING_ID + " AND " + Keys.TABLE_NAME + "." + Keys.IS_MASTER_KEY
+ + " = '1'" + ") " + " INNER JOIN " + UserIds.TABLE_NAME + " ON " + "("
+ + Keys.TABLE_NAME + "." + Keys._ID + " = " + UserIds.TABLE_NAME + "."
+ + UserIds.KEY_ID + " AND " + UserIds.TABLE_NAME + "." + UserIds.RANK
+ + " = '0')");
+
+ 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(")");
+ }
+
+ mCursor = qb.query(mDatabase, new String[] { KeyRings.TABLE_NAME + "." + KeyRings._ID, // 0
+ KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID, // 1
+ UserIds.TABLE_NAME + "." + UserIds.USER_ID, // 2
+ }, KeyRings.TABLE_NAME + "." + KeyRings.TYPE + " = ?", new String[] { ""
+ + (mKeyType == Id.type.public_key ? Id.database.type_public
+ : Id.database.type_secret) }, null, null, UserIds.TABLE_NAME + "."
+ + UserIds.USER_ID + " ASC");
+
+ // content provider way for reference, might have to go back to it sometime:
+ /*
+ * Uri contentUri = null; if (mKeyType == Id.type.secret_key) { contentUri =
+ * Apg.CONTENT_URI_SECRET_KEY_RINGS; } else { contentUri =
+ * Apg.CONTENT_URI_PUBLIC_KEY_RINGS; } mCursor = getContentResolver().query( contentUri,
+ * new String[] { DataProvider._ID, // 0 DataProvider.MASTER_KEY_ID, // 1
+ * DataProvider.USER_ID, // 2 }, null, null, null);
+ */
+
+ startManagingCursor(mCursor);
+ rebuild(false);
+ }
+
+ public void cleanup() {
+ if (mCursor != null) {
+ stopManagingCursor(mCursor);
+ mCursor.close();
+ }
+ }
+
+ public void rebuild(boolean requery) {
+ if (requery) {
+ mCursor.requery();
+ }
+ mChildren = new Vector<Vector<KeyChild>>();
+ for (int i = 0; i < mCursor.getCount(); ++i) {
+ mChildren.add(null);
+ }
+ }
+
+ protected Vector<KeyChild> getChildrenOfGroup(int groupPosition) {
+ Vector<KeyChild> children = mChildren.get(groupPosition);
+ if (children != null) {
+ return children;
+ }
+
+ mCursor.moveToPosition(groupPosition);
+ children = new Vector<KeyChild>();
+ Cursor c = mDatabase.query(Keys.TABLE_NAME, new String[] { Keys._ID, // 0
+ Keys.KEY_ID, // 1
+ Keys.IS_MASTER_KEY, // 2
+ Keys.ALGORITHM, // 3
+ Keys.KEY_SIZE, // 4
+ Keys.CAN_SIGN, // 5
+ Keys.CAN_ENCRYPT, // 6
+ }, Keys.KEY_RING_ID + " = ?", new String[] { mCursor.getString(0) }, null, null,
+ Keys.RANK + " ASC");
+
+ int masterKeyId = -1;
+ long fingerPrintId = -1;
+ for (int i = 0; i < c.getCount(); ++i) {
+ c.moveToPosition(i);
+ children.add(new KeyChild(c.getLong(1), c.getInt(2) == 1, c.getInt(3), c.getInt(4),
+ c.getInt(5) == 1, c.getInt(6) == 1));
+ if (i == 0) {
+ masterKeyId = c.getInt(0);
+ fingerPrintId = c.getLong(1);
+ }
+ }
+ c.close();
+
+ if (masterKeyId != -1) {
+ children.insertElementAt(new KeyChild(Apg.getFingerPrint(fingerPrintId), true), 0);
+ c = mDatabase.query(UserIds.TABLE_NAME, new String[] { UserIds.USER_ID, // 0
+ }, UserIds.KEY_ID + " = ? AND " + UserIds.RANK + " > 0", new String[] { ""
+ + masterKeyId }, null, null, UserIds.RANK + " ASC");
+
+ for (int i = 0; i < c.getCount(); ++i) {
+ c.moveToPosition(i);
+ children.add(new KeyChild(c.getString(0)));
+ }
+ c.close();
+ }
+
+ mChildren.set(groupPosition, children);
+ return children;
+ }
+
+ public boolean hasStableIds() {
+ return true;
+ }
+
+ public boolean isChildSelectable(int groupPosition, int childPosition) {
+ return true;
+ }
+
+ public int getGroupCount() {
+ return mCursor.getCount();
+ }
+
+ public Object getChild(int groupPosition, int childPosition) {
+ return null;
+ }
+
+ public long getChildId(int groupPosition, int childPosition) {
+ return childPosition;
+ }
+
+ public int getChildrenCount(int groupPosition) {
+ return getChildrenOfGroup(groupPosition).size();
+ }
+
+ public Object getGroup(int position) {
+ return position;
+ }
+
+ public long getGroupId(int position) {
+ mCursor.moveToPosition(position);
+ return mCursor.getLong(1); // MASTER_KEY_ID
+ }
+
+ public int getKeyRingId(int position) {
+ mCursor.moveToPosition(position);
+ return mCursor.getInt(0); // _ID
+ }
+
+ public View getGroupView(int groupPosition, boolean isExpanded, View convertView,
+ ViewGroup parent) {
+ mCursor.moveToPosition(groupPosition);
+
+ View view = mInflater.inflate(R.layout.key_list_group_item, null);
+ view.setBackgroundResource(android.R.drawable.list_selector_background);
+
+ TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId);
+ mainUserId.setText("");
+ TextView mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest);
+ mainUserIdRest.setText("");
+
+ String userId = mCursor.getString(2); // USER_ID
+ if (userId != null) {
+ String chunks[] = userId.split(" <", 2);
+ userId = chunks[0];
+ if (chunks.length > 1) {
+ mainUserIdRest.setText("<" + chunks[1]);
+ }
+ mainUserId.setText(userId);
+ }
+
+ if (mainUserId.getText().length() == 0) {
+ mainUserId.setText(R.string.unknownUserId);
+ }
+
+ if (mainUserIdRest.getText().length() == 0) {
+ mainUserIdRest.setVisibility(View.GONE);
+ }
+ return view;
+ }
+
+ public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
+ View convertView, ViewGroup parent) {
+ mCursor.moveToPosition(groupPosition);
+
+ Vector<KeyChild> children = getChildrenOfGroup(groupPosition);
+
+ KeyChild child = children.get(childPosition);
+ View view = null;
+ switch (child.type) {
+ case KeyChild.KEY: {
+ if (child.isMasterKey) {
+ view = mInflater.inflate(R.layout.key_list_child_item_master_key, null);
+ } else {
+ view = mInflater.inflate(R.layout.key_list_child_item_sub_key, null);
+ }
+
+ TextView keyId = (TextView) view.findViewById(R.id.keyId);
+ String keyIdStr = Apg.getSmallFingerPrint(child.keyId);
+ keyId.setText(keyIdStr);
+ TextView keyDetails = (TextView) view.findViewById(R.id.keyDetails);
+ String algorithmStr = Apg.getAlgorithmInfo(child.algorithm, child.keySize);
+ keyDetails.setText("(" + algorithmStr + ")");
+
+ ImageView encryptIcon = (ImageView) view.findViewById(R.id.ic_encryptKey);
+ if (!child.canEncrypt) {
+ encryptIcon.setVisibility(View.GONE);
+ }
+
+ ImageView signIcon = (ImageView) view.findViewById(R.id.ic_signKey);
+ if (!child.canSign) {
+ signIcon.setVisibility(View.GONE);
+ }
+ break;
+ }
+
+ case KeyChild.USER_ID: {
+ view = mInflater.inflate(R.layout.key_list_child_item_user_id, null);
+ TextView userId = (TextView) view.findViewById(R.id.userId);
+ userId.setText(child.userId);
+ break;
+ }
+
+ case KeyChild.FINGER_PRINT: {
+ view = mInflater.inflate(R.layout.key_list_child_item_user_id, null);
+ TextView userId = (TextView) view.findViewById(R.id.userId);
+ userId.setText(getString(R.string.fingerprint) + ":\n"
+ + child.fingerPrint.replace(" ", "\n"));
+ break;
+ }
+ }
+ return view;
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ switch (requestCode) {
+ case Id.request.filename: {
+ if (resultCode == RESULT_OK && data != null) {
+ String filename = data.getDataString();
+ if (filename != null) {
+ // Get rid of URI prefix:
+ if (filename.startsWith("file://")) {
+ filename = filename.substring(7);
+ }
+ // replace %20 and so on
+ filename = Uri.decode(filename);
+
+ FileDialog.setFilename(filename);
+ }
+ }
+ return;
+ }
+
+ default: {
+ break;
+ }
+ }
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+}
diff --git a/org_apg/src/org/apg/ui/KeyServerPreferenceActivity.java b/org_apg/src/org/apg/ui/KeyServerPreferenceActivity.java
new file mode 100644
index 000000000..85d31779a
--- /dev/null
+++ b/org_apg/src/org/apg/ui/KeyServerPreferenceActivity.java
@@ -0,0 +1,125 @@
+/*
+ * 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.apg.ui;
+
+import java.util.Vector;
+
+import org.apg.Apg;
+import org.apg.ui.widget.Editor;
+import org.apg.ui.widget.KeyServerEditor;
+import org.apg.ui.widget.Editor.EditorListener;
+import org.apg.R;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.TextView;
+
+public class KeyServerPreferenceActivity extends BaseActivity
+ implements OnClickListener, EditorListener {
+ private LayoutInflater mInflater;
+ private ViewGroup mEditors;
+ private View mAdd;
+ private TextView mTitle;
+ private TextView mSummary;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ 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_keyServers);
+
+ mEditors = (ViewGroup) findViewById(R.id.editors);
+ mAdd = findViewById(R.id.add);
+ mAdd.setOnClickListener(this);
+
+ Intent intent = getIntent();
+ String servers[] = intent.getStringArrayExtra(Apg.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);
+ }
+ }
+
+ Button okButton = (Button) findViewById(R.id.btn_ok);
+ okButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ okClicked();
+ }
+ });
+
+ Button cancelButton = (Button) findViewById(R.id.btn_cancel);
+ cancelButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ cancelClicked();
+ }
+ });
+ }
+
+ 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(Apg.EXTRA_KEY_SERVERS, servers.toArray(dummy));
+ setResult(RESULT_OK, data);
+ finish();
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ // override this, so no option menu is added (as would be in BaseActivity), since
+ // we're still in preferences
+ return true;
+ }
+}
diff --git a/org_apg/src/org/apg/ui/KeyServerQueryActivity.java b/org_apg/src/org/apg/ui/KeyServerQueryActivity.java
new file mode 100644
index 000000000..606acb575
--- /dev/null
+++ b/org_apg/src/org/apg/ui/KeyServerQueryActivity.java
@@ -0,0 +1,297 @@
+package org.apg.ui;
+
+import java.util.List;
+import java.util.Vector;
+
+import org.apg.Apg;
+import org.apg.Constants;
+import org.apg.HkpKeyServer;
+import org.apg.Id;
+import org.apg.KeyServer.InsufficientQuery;
+import org.apg.KeyServer.KeyInfo;
+import org.apg.KeyServer.QueryException;
+import org.apg.KeyServer.TooManyResponses;
+import org.apg.R;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Message;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+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.Toast;
+
+public class KeyServerQueryActivity extends BaseActivity {
+ private ListView mList;
+ private EditText mQuery;
+ private Button mSearch;
+ private KeyInfoListAdapter mAdapter;
+ private Spinner mKeyServer;
+
+ private int mQueryType;
+ private String mQueryString;
+ private long mQueryId;
+ private volatile List<KeyInfo> mSearchResult;
+ private volatile String mKeyData;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.key_server_query_layout);
+
+ 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.keyServer);
+ ArrayAdapter<String> adapter =
+ new ArrayAdapter<String>(this,
+ android.R.layout.simple_spinner_item,
+ mPreferences.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() {
+ public void onItemClick(AdapterView<?> adapter, View view, int position, long keyId) {
+ get(keyId);
+ }
+ });
+
+ mSearch.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ String query = mQuery.getText().toString();
+ search(query);
+ }
+ });
+
+ Intent intent = getIntent();
+ if (Apg.Intent.LOOK_UP_KEY_ID.equals(intent.getAction()) ||
+ Apg.Intent.LOOK_UP_KEY_ID_AND_RETURN.equals(intent.getAction())) {
+ long keyId = intent.getLongExtra(Apg.EXTRA_KEY_ID, 0);
+ if (keyId != 0) {
+ String query = "0x" + Apg.keyToHex(keyId);
+ mQuery.setText(query);
+ search(query);
+ }
+ }
+ }
+
+ private void search(String query) {
+ showDialog(Id.dialog.querying);
+ mQueryType = Id.keyserver.search;
+ mQueryString = query;
+ mAdapter.setKeys(new Vector<KeyInfo>());
+ startThread();
+ }
+
+ private void get(long keyId) {
+ showDialog(Id.dialog.querying);
+ mQueryType = Id.keyserver.get;
+ mQueryId = keyId;
+ startThread();
+ }
+
+ @Override
+ protected Dialog onCreateDialog(int id) {
+ ProgressDialog progress = (ProgressDialog) super.onCreateDialog(id);
+ progress.setMessage(this.getString(R.string.progress_queryingServer,
+ (String)mKeyServer.getSelectedItem()));
+ return progress;
+ }
+
+ @Override
+ public void run() {
+ String error = null;
+ Bundle data = new Bundle();
+ Message msg = new Message();
+
+ try {
+ HkpKeyServer server = new HkpKeyServer((String)mKeyServer.getSelectedItem());
+ if (mQueryType == Id.keyserver.search) {
+ mSearchResult = server.search(mQueryString);
+ } else if (mQueryType == Id.keyserver.get) {
+ mKeyData = server.get(mQueryId);
+ }
+ } catch (QueryException e) {
+ error = "" + e;
+ } catch (InsufficientQuery e) {
+ error = "Insufficient query.";
+ } catch (TooManyResponses e) {
+ error = "Too many responses.";
+ }
+
+ data.putInt(Constants.extras.STATUS, Id.message.done);
+
+ if (error != null) {
+ data.putString(Apg.EXTRA_ERROR, error);
+ }
+
+ msg.setData(data);
+ sendMessage(msg);
+ }
+
+ @Override
+ public void doneCallback(Message msg) {
+ super.doneCallback(msg);
+
+ removeDialog(Id.dialog.querying);
+
+ Bundle data = msg.getData();
+ String error = data.getString(Apg.EXTRA_ERROR);
+ if (error != null) {
+ Toast.makeText(this, getString(R.string.errorMessage, error), Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ if (mQueryType == Id.keyserver.search) {
+ if (mSearchResult != null) {
+ Toast.makeText(this, getString(R.string.keysFound, mSearchResult.size()), Toast.LENGTH_SHORT).show();
+ mAdapter.setKeys(mSearchResult);
+ }
+ } else if (mQueryType == Id.keyserver.get) {
+ Intent orgIntent = getIntent();
+ if (Apg.Intent.LOOK_UP_KEY_ID_AND_RETURN.equals(orgIntent.getAction())) {
+ if (mKeyData != null) {
+ Intent intent = new Intent();
+ intent.putExtra(Apg.EXTRA_TEXT, mKeyData);
+ setResult(RESULT_OK, intent);
+ } else {
+ setResult(RESULT_CANCELED);
+ }
+ finish();
+ } else {
+ if (mKeyData != null) {
+ Intent intent = new Intent(this, PublicKeyListActivity.class);
+ intent.setAction(Apg.Intent.IMPORT);
+ intent.putExtra(Apg.EXTRA_TEXT, mKeyData);
+ startActivity(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 Vector<KeyInfo>();
+ }
+
+ public void setKeys(List<KeyInfo> keys) {
+ mKeys = keys;
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return true;
+ }
+
+ public int getCount() {
+ return mKeys.size();
+ }
+
+ public Object getItem(int position) {
+ return mKeys.get(position);
+ }
+
+ public long getItemId(int position) {
+ return mKeys.get(position).keyId;
+ }
+
+ 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.unknownUserId);
+ TextView mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest);
+ mainUserIdRest.setText("");
+ TextView keyId = (TextView) view.findViewById(R.id.keyId);
+ keyId.setText(R.string.noKey);
+ 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(Apg.getSmallFingerPrint(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.FILL_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/org_apg/src/org/apg/ui/MailListActivity.java b/org_apg/src/org/apg/ui/MailListActivity.java
new file mode 100644
index 000000000..ad1d08068
--- /dev/null
+++ b/org_apg/src/org/apg/ui/MailListActivity.java
@@ -0,0 +1,222 @@
+/*
+ * 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.apg.ui;
+
+import java.util.Vector;
+import java.util.regex.Matcher;
+
+import org.apg.Apg;
+import org.apg.Preferences;
+import org.apg.R;
+
+import android.app.ListActivity;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.text.Html;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.ListAdapter;
+import android.widget.TextView;
+
+public class MailListActivity extends ListActivity {
+ LayoutInflater mInflater = null;
+
+ public static final String EXTRA_ACCOUNT = "account";
+
+ private static class Conversation {
+ public long id;
+ public String subject;
+ public Vector<Message> messages;
+
+ public Conversation(long id, String subject) {
+ this.id = id;
+ this.subject = subject;
+ }
+ }
+
+ private static class Message {
+ public Conversation parent;
+ public long id;
+ public String subject;
+ public String fromAddress;
+ public String data;
+ public String replyTo;
+ public boolean signedOnly;
+
+ public Message(Conversation parent, long id, String subject,
+ String fromAddress, String replyTo,
+ String data, boolean signedOnly) {
+ this.parent = parent;
+ this.id = id;
+ this.subject = subject;
+ this.fromAddress = fromAddress;
+ this.replyTo = replyTo;
+ this.data = data;
+ if (this.replyTo == null || this.replyTo.equals("")) {
+ this.replyTo = this.fromAddress;
+ }
+ this.signedOnly = signedOnly;
+ }
+ }
+
+ private Vector<Conversation> mConversations;
+ private Vector<Message> mMessages;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ Preferences prefs = Preferences.getPreferences(this);
+ BaseActivity.setLanguage(this, prefs.getLanguage());
+
+ super.onCreate(savedInstanceState);
+
+ mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+ mConversations = new Vector<Conversation>();
+ mMessages = new Vector<Message>();
+
+ String account = getIntent().getExtras().getString(EXTRA_ACCOUNT);
+ // TODO: what if account is null?
+ Uri uri = Uri.parse("content://gmail-ls/conversations/" + account);
+ Cursor cursor =
+ managedQuery(uri, new String[] { "conversation_id", "subject" }, null, null, null);
+ for (int i = 0; i < cursor.getCount(); ++i) {
+ cursor.moveToPosition(i);
+
+ int idIndex = cursor.getColumnIndex("conversation_id");
+ int subjectIndex = cursor.getColumnIndex("subject");
+ long conversationId = cursor.getLong(idIndex);
+ Conversation conversation =
+ new Conversation(conversationId, cursor.getString(subjectIndex));
+ Uri messageUri = Uri.withAppendedPath(uri, "" + conversationId + "/messages");
+ Cursor messageCursor =
+ managedQuery(messageUri, new String[] {
+ "messageId",
+ "subject",
+ "fromAddress",
+ "replyToAddresses",
+ "body" }, null, null, null);
+ Vector<Message> messages = new Vector<Message>();
+ for (int j = 0; j < messageCursor.getCount(); ++j) {
+ messageCursor.moveToPosition(j);
+ idIndex = messageCursor.getColumnIndex("messageId");
+ subjectIndex = messageCursor.getColumnIndex("subject");
+ int fromAddressIndex = messageCursor.getColumnIndex("fromAddress");
+ int replyToIndex = messageCursor.getColumnIndex("replyToAddresses");
+ int bodyIndex = messageCursor.getColumnIndex("body");
+ String data = messageCursor.getString(bodyIndex);
+ data = Html.fromHtml(data).toString();
+ boolean signedOnly = false;
+ Matcher matcher = Apg.PGP_MESSAGE.matcher(data);
+ if (matcher.matches()) {
+ data = matcher.group(1);
+ } else {
+ matcher = Apg.PGP_SIGNED_MESSAGE.matcher(data);
+ if (matcher.matches()) {
+ data = matcher.group(1);
+ signedOnly = true;
+ } else {
+ data = null;
+ }
+ }
+ Message message =
+ new Message(conversation,
+ messageCursor.getLong(idIndex),
+ messageCursor.getString(subjectIndex),
+ messageCursor.getString(fromAddressIndex),
+ messageCursor.getString(replyToIndex),
+ data, signedOnly);
+
+ messages.add(message);
+ mMessages.add(message);
+ }
+ conversation.messages = messages;
+ mConversations.add(conversation);
+ }
+
+ setListAdapter(new MailboxAdapter());
+ getListView().setOnItemClickListener(new OnItemClickListener() {
+ public void onItemClick(AdapterView<?> arg0, View v, int position, long id) {
+ Intent intent = new Intent(MailListActivity.this, DecryptActivity.class);
+ intent.setAction(Apg.Intent.DECRYPT);
+ Message message = (Message) ((MailboxAdapter) getListAdapter()).getItem(position);
+ intent.putExtra(Apg.EXTRA_TEXT, message.data);
+ intent.putExtra(Apg.EXTRA_SUBJECT, message.subject);
+ intent.putExtra(Apg.EXTRA_REPLY_TO, message.replyTo);
+ startActivity(intent);
+ }
+ });
+ }
+
+ private class MailboxAdapter extends BaseAdapter implements ListAdapter {
+
+ @Override
+ public boolean isEnabled(int position) {
+ Message message = (Message) getItem(position);
+ return message.data != null;
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return true;
+ }
+
+ public int getCount() {
+ return mMessages.size();
+ }
+
+ public Object getItem(int position) {
+ return mMessages.get(position);
+ }
+
+ public long getItemId(int position) {
+ return mMessages.get(position).id;
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View view = mInflater.inflate(R.layout.mailbox_message_item, null);
+
+ Message message = (Message) getItem(position);
+
+ TextView subject = (TextView) view.findViewById(R.id.subject);
+ TextView email = (TextView) view.findViewById(R.id.emailAddress);
+ ImageView status = (ImageView) view.findViewById(R.id.ic_status);
+
+ subject.setText(message.subject);
+ email.setText(message.fromAddress);
+ if (message.data != null) {
+ if (message.signedOnly) {
+ status.setImageResource(R.drawable.signed);
+ } else {
+ status.setImageResource(R.drawable.encrypted);
+ }
+ status.setVisibility(View.VISIBLE);
+ } else {
+ status.setVisibility(View.INVISIBLE);
+ }
+
+ return view;
+ }
+ }
+}
diff --git a/org_apg/src/org/apg/ui/MainActivity.java b/org_apg/src/org/apg/ui/MainActivity.java
new file mode 100644
index 000000000..8c985c2ac
--- /dev/null
+++ b/org_apg/src/org/apg/ui/MainActivity.java
@@ -0,0 +1,419 @@
+/*
+ * 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.apg.ui;
+
+import java.security.Security;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apg.Apg;
+import org.apg.Id;
+import org.apg.Id.dialog;
+import org.apg.Id.menu;
+import org.apg.Id.menu.option;
+import org.apg.provider.Accounts;
+import org.spongycastle.jce.provider.BouncyCastleProvider;
+import org.apg.R;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.net.Uri;
+import android.os.Bundle;
+import android.text.util.Linkify;
+import android.text.util.Linkify.TransformFilter;
+import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
+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.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.Button;
+import android.widget.CursorAdapter;
+import android.widget.EditText;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+public class MainActivity extends BaseActivity {
+ static {
+ Security.addProvider(new BouncyCastleProvider());
+ }
+
+ private ListView mAccounts = null;
+ private AccountListAdapter mListAdapter = null;
+ private Cursor mAccountCursor;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+
+ Button encryptMessageButton = (Button) findViewById(R.id.btn_encryptMessage);
+ Button decryptMessageButton = (Button) findViewById(R.id.btn_decryptMessage);
+ Button encryptFileButton = (Button) findViewById(R.id.btn_encryptFile);
+ Button decryptFileButton = (Button) findViewById(R.id.btn_decryptFile);
+ mAccounts = (ListView) findViewById(R.id.accounts);
+
+ encryptMessageButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ Intent intent = new Intent(MainActivity.this, EncryptActivity.class);
+ intent.setAction(Apg.Intent.ENCRYPT);
+ startActivity(intent);
+ }
+ });
+
+ decryptMessageButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ Intent intent = new Intent(MainActivity.this, DecryptActivity.class);
+ intent.setAction(Apg.Intent.DECRYPT);
+ startActivity(intent);
+ }
+ });
+
+ encryptFileButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ Intent intent = new Intent(MainActivity.this, EncryptActivity.class);
+ intent.setAction(Apg.Intent.ENCRYPT_FILE);
+ startActivity(intent);
+ }
+ });
+
+ decryptFileButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ Intent intent = new Intent(MainActivity.this, DecryptActivity.class);
+ intent.setAction(Apg.Intent.DECRYPT_FILE);
+ startActivity(intent);
+ }
+ });
+
+ mAccountCursor =
+ Apg.getDatabase().db().query(Accounts.TABLE_NAME,
+ new String[] {
+ Accounts._ID,
+ Accounts.NAME,
+ }, null, null, null, null, Accounts.NAME + " ASC");
+ startManagingCursor(mAccountCursor);
+
+ mListAdapter = new AccountListAdapter(this, mAccountCursor);
+ mAccounts.setAdapter(mListAdapter);
+ mAccounts.setOnItemClickListener(new OnItemClickListener() {
+ public void onItemClick(AdapterView<?> arg0, View view, int index, long id) {
+ String accountName = (String) mAccounts.getItemAtPosition(index);
+ startActivity(new Intent(MainActivity.this, MailListActivity.class)
+ .putExtra(MailListActivity.EXTRA_ACCOUNT, accountName));
+ }
+ });
+ registerForContextMenu(mAccounts);
+
+ if (!mPreferences.hasSeenHelp()) {
+ showDialog(Id.dialog.help);
+ }
+
+ if (Apg.isReleaseVersion(this) && !mPreferences.hasSeenChangeLog(Apg.getVersion(this))) {
+ showDialog(Id.dialog.change_log);
+ }
+ }
+
+ @Override
+ protected Dialog onCreateDialog(int id) {
+ switch (id) {
+ case Id.dialog.new_account: {
+ AlertDialog.Builder alert = new AlertDialog.Builder(this);
+
+ alert.setTitle(R.string.title_addAccount);
+ alert.setMessage(R.string.specifyGoogleMailAccount);
+
+ LayoutInflater inflater =
+ (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ View view = inflater.inflate(R.layout.add_account_dialog, null);
+
+ final EditText input = (EditText) view.findViewById(R.id.input);
+ alert.setView(view);
+
+ alert.setPositiveButton(android.R.string.ok,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ MainActivity.this.removeDialog(Id.dialog.new_account);
+ String accountName = "" + input.getText();
+
+ try {
+ Cursor testCursor =
+ managedQuery(Uri.parse("content://gmail-ls/conversations/" +
+ accountName),
+ null, null, null, null);
+ if (testCursor == null) {
+ Toast.makeText(MainActivity.this,
+ getString(R.string.errorMessage,
+ getString(R.string.error_accountNotFound,
+ accountName)),
+ Toast.LENGTH_SHORT).show();
+ return;
+ }
+ } catch (SecurityException e) {
+ Toast.makeText(MainActivity.this,
+ getString(R.string.errorMessage,
+ getString(R.string.error_accountReadingNotAllowed)),
+ Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ ContentValues values = new ContentValues();
+ values.put(Accounts.NAME, accountName);
+ try {
+ Apg.getDatabase().db().insert(Accounts.TABLE_NAME,
+ Accounts.NAME, values);
+ mAccountCursor.requery();
+ mListAdapter.notifyDataSetChanged();
+ } catch (SQLException e) {
+ Toast.makeText(MainActivity.this,
+ getString(R.string.errorMessage,
+ getString(R.string.error_addingAccountFailed,
+ accountName)),
+ Toast.LENGTH_SHORT).show();
+ }
+ }
+ });
+
+ alert.setNegativeButton(android.R.string.cancel,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ MainActivity.this.removeDialog(Id.dialog.new_account);
+ }
+ });
+
+ return alert.create();
+ }
+
+ case Id.dialog.change_log: {
+ AlertDialog.Builder alert = new AlertDialog.Builder(this);
+
+ alert.setTitle("Changes " + Apg.getFullVersion(this));
+ LayoutInflater inflater =
+ (LayoutInflater) this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ View layout = inflater.inflate(R.layout.info, null);
+ TextView message = (TextView) layout.findViewById(R.id.message);
+
+ message.setText("Changes:\n" +
+ "* \n" +
+ "\n" +
+ "WARNING: be careful editing your existing keys, as they " +
+ "WILL be stripped of certificates right now.\n" +
+ "\n" +
+ "Also: key cross-certification is NOT supported, so signing " +
+ "with those keys will get a warning when the signature is " +
+ "checked.\n" +
+ "\n" +
+ "I hope APG continues to be useful to you, please send " +
+ "bug reports, feature wishes, feedback.");
+ alert.setView(layout);
+
+ alert.setCancelable(false);
+ alert.setPositiveButton(android.R.string.ok,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ MainActivity.this.removeDialog(Id.dialog.change_log);
+ mPreferences.setHasSeenChangeLog(
+ Apg.getVersion(MainActivity.this), true);
+ }
+ });
+
+ return alert.create();
+ }
+
+ case Id.dialog.help: {
+ AlertDialog.Builder alert = new AlertDialog.Builder(this);
+
+ alert.setTitle(R.string.title_help);
+
+ LayoutInflater inflater =
+ (LayoutInflater) this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ View layout = inflater.inflate(R.layout.info, null);
+ TextView message = (TextView) layout.findViewById(R.id.message);
+ message.setText(R.string.text_help);
+
+ TransformFilter packageNames = new TransformFilter() {
+ public final String transformUrl(final Matcher match, String url) {
+ String name = match.group(1).toLowerCase();
+ if (name.equals("astro")) {
+ return "com.metago.astro";
+ } else if (name.equals("k-9 mail")) {
+ return "com.fsck.k9";
+ } else {
+ return "org.openintents.filemanager";
+ }
+ }
+ };
+
+ Pattern pattern = Pattern.compile("(OI File Manager|ASTRO|K-9 Mail)");
+ String scheme = "market://search?q=pname:";
+ message.setAutoLinkMask(0);
+ Linkify.addLinks(message, pattern, scheme, null, packageNames);
+
+ alert.setView(layout);
+
+ alert.setPositiveButton(android.R.string.ok,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ MainActivity.this.removeDialog(Id.dialog.help);
+ mPreferences.setHasSeenHelp(true);
+ }
+ });
+
+ return alert.create();
+ }
+
+ default: {
+ return super.onCreateDialog(id);
+ }
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ menu.add(0, Id.menu.option.manage_public_keys, 0, R.string.menu_managePublicKeys)
+ .setIcon(android.R.drawable.ic_menu_manage);
+ menu.add(0, Id.menu.option.manage_secret_keys, 1, R.string.menu_manageSecretKeys)
+ .setIcon(android.R.drawable.ic_menu_manage);
+ menu.add(1, Id.menu.option.create, 2, R.string.menu_addAccount)
+ .setIcon(android.R.drawable.ic_menu_add);
+ menu.add(2, Id.menu.option.preferences, 3, R.string.menu_preferences)
+ .setIcon(android.R.drawable.ic_menu_preferences);
+ menu.add(2, Id.menu.option.key_server, 4, R.string.menu_keyServer)
+ .setIcon(android.R.drawable.ic_menu_search);
+ menu.add(3, Id.menu.option.about, 5, R.string.menu_about)
+ .setIcon(android.R.drawable.ic_menu_info_details);
+ menu.add(3, Id.menu.option.help, 6, R.string.menu_help)
+ .setIcon(android.R.drawable.ic_menu_help);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case Id.menu.option.create: {
+ showDialog(Id.dialog.new_account);
+ return true;
+ }
+
+ case Id.menu.option.manage_public_keys: {
+ startActivity(new Intent(this, PublicKeyListActivity.class));
+ return true;
+ }
+
+ case Id.menu.option.manage_secret_keys: {
+ startActivity(new Intent(this, SecretKeyListActivity.class));
+ return true;
+ }
+
+ case Id.menu.option.help: {
+ showDialog(Id.dialog.help);
+ return true;
+ }
+
+ case Id.menu.option.key_server: {
+ startActivity(new Intent(this, KeyServerQueryActivity.class));
+ return true;
+ }
+
+ default: {
+ return super.onOptionsItemSelected(item);
+ }
+ }
+ }
+
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
+ super.onCreateContextMenu(menu, v, menuInfo);
+
+ TextView nameTextView = (TextView) v.findViewById(R.id.accountName);
+ if (nameTextView != null) {
+ menu.setHeaderTitle(nameTextView.getText());
+ menu.add(0, Id.menu.delete, 0, R.string.menu_deleteAccount);
+ }
+ }
+
+ @Override
+ public boolean onContextItemSelected(MenuItem menuItem) {
+ AdapterView.AdapterContextMenuInfo info =
+ (AdapterView.AdapterContextMenuInfo) menuItem.getMenuInfo();
+
+ switch (menuItem.getItemId()) {
+ case Id.menu.delete: {
+ Apg.getDatabase().db().delete(Accounts.TABLE_NAME,
+ Accounts._ID + " = ?",
+ new String[] { "" + info.id });
+ mAccountCursor.requery();
+ mListAdapter.notifyDataSetChanged();
+ return true;
+ }
+
+ default: {
+ return super.onContextItemSelected(menuItem);
+ }
+ }
+ }
+
+
+ private static class AccountListAdapter extends CursorAdapter {
+ private LayoutInflater mInflater;
+
+ public AccountListAdapter(Context context, Cursor cursor) {
+ super(context, cursor);
+ mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ }
+
+ @Override
+ public Object getItem(int position) {
+ Cursor c = getCursor();
+ c.moveToPosition(position);
+ return c.getString(c.getColumnIndex(Accounts.NAME));
+ }
+
+ @Override
+ public int getCount() {
+ return super.getCount();
+ }
+
+ @Override
+ public View newView(Context context, Cursor cursor, ViewGroup parent) {
+ return mInflater.inflate(R.layout.account_item, null);
+ }
+
+ @Override
+ public void bindView(View view, Context context, Cursor cursor) {
+ TextView nameTextView = (TextView) view.findViewById(R.id.accountName);
+ int nameIndex = cursor.getColumnIndex(Accounts.NAME);
+ final String account = cursor.getString(nameIndex);
+ nameTextView.setText(account);
+ }
+
+ @Override
+ public boolean isEnabled(int position) {
+ return true;
+ }
+ }
+} \ No newline at end of file
diff --git a/org_apg/src/org/apg/ui/PreferencesActivity.java b/org_apg/src/org/apg/ui/PreferencesActivity.java
new file mode 100644
index 000000000..421c9cc39
--- /dev/null
+++ b/org_apg/src/org/apg/ui/PreferencesActivity.java
@@ -0,0 +1,274 @@
+/*
+ * 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.apg.ui;
+
+import org.apg.Apg;
+import org.apg.Constants;
+import org.apg.Id;
+import org.apg.Preferences;
+import org.apg.Constants.pref;
+import org.apg.Id.choice;
+import org.apg.Id.request;
+import org.apg.Id.choice.compression;
+import org.apg.ui.widget.IntegerListPreference;
+import org.spongycastle.bcpg.HashAlgorithmTags;
+import org.spongycastle.openpgp.PGPEncryptedData;
+import org.apg.R;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.preference.CheckBoxPreference;
+import android.preference.ListPreference;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceScreen;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Vector;
+
+public class PreferencesActivity extends PreferenceActivity {
+ private ListPreference mLanguage = null;
+ 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);
+ BaseActivity.setLanguage(this, mPreferences.getLanguage());
+ super.onCreate(savedInstanceState);
+
+ addPreferencesFromResource(R.xml.apg_preferences);
+
+ mLanguage = (ListPreference) findPreference(Constants.pref.LANGUAGE);
+ Vector<CharSequence> entryVector = new Vector<CharSequence>(Arrays.asList(mLanguage.getEntries()));
+ Vector<CharSequence> entryValueVector = new Vector<CharSequence>(Arrays.asList(mLanguage.getEntryValues()));
+ String supportedLanguages[] = getResources().getStringArray(R.array.supported_languages);
+ HashSet<String> supportedLanguageSet = new HashSet<String>(Arrays.asList(supportedLanguages));
+ for (int i = entryVector.size() - 1; i > -1; --i)
+ {
+ if (!supportedLanguageSet.contains(entryValueVector.get(i)))
+ {
+ entryVector.remove(i);
+ entryValueVector.remove(i);
+ }
+ }
+ CharSequence dummy[] = new CharSequence[0];
+ mLanguage.setEntries(entryVector.toArray(dummy));
+ mLanguage.setEntryValues(entryValueVector.toArray(dummy));
+ mLanguage.setValue(mPreferences.getLanguage());
+ mLanguage.setSummary(mLanguage.getEntry());
+ mLanguage.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener()
+ {
+ public boolean onPreferenceChange(Preference preference, Object newValue)
+ {
+ mLanguage.setValue(newValue.toString());
+ mLanguage.setSummary(mLanguage.getEntry());
+ mPreferences.setLanguage(newValue.toString());
+ return false;
+ }
+ });
+
+ 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()));
+ BaseActivity.startCacheService(PreferencesActivity.this, mPreferences);
+ 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.fast) + ")",
+ "ZIP (" + getString(R.string.fast) + ")",
+ "ZLIB (" + getString(R.string.fast) + ")",
+ "BZIP2 (" + getString(R.string.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().getString(R.string.nKeyServers, servers.length));
+ mKeyServerPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
+ public boolean onPreferenceClick(Preference preference) {
+ Intent intent = new Intent(PreferencesActivity.this,
+ KeyServerPreferenceActivity.class);
+ intent.putExtra(Apg.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(Apg.EXTRA_KEY_SERVERS);
+ mPreferences.setKeyServers(servers);
+ mKeyServerPreference.setSummary(getResources().getString(R.string.nKeyServers, servers.length));
+ break;
+ }
+
+ default: {
+ super.onActivityResult(requestCode, resultCode, data);
+ break;
+ }
+ }
+ }
+}
+
diff --git a/org_apg/src/org/apg/ui/PublicKeyListActivity.java b/org_apg/src/org/apg/ui/PublicKeyListActivity.java
new file mode 100644
index 000000000..81a79ce33
--- /dev/null
+++ b/org_apg/src/org/apg/ui/PublicKeyListActivity.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apg.ui;
+
+import org.apg.Apg;
+import org.apg.Constants;
+import org.apg.Id;
+import org.apg.Constants.path;
+import org.apg.Id.menu;
+import org.apg.Id.request;
+import org.apg.Id.type;
+import org.apg.Id.menu.option;
+import org.spongycastle.openpgp.PGPPublicKeyRing;
+import org.apg.R;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.ExpandableListView;
+import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
+
+public class PublicKeyListActivity extends KeyListActivity {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ mExportFilename = Constants.path.APP_DIR + "/pubexport.asc";
+ mKeyType = Id.type.public_key;
+ super.onCreate(savedInstanceState);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ menu.add(0, Id.menu.option.import_keys, 0, R.string.menu_importKeys).setIcon(
+ android.R.drawable.ic_menu_add);
+ menu.add(0, Id.menu.option.export_keys, 1, R.string.menu_exportKeys).setIcon(
+ android.R.drawable.ic_menu_save);
+ menu.add(1, Id.menu.option.search, 2, R.string.menu_search).setIcon(
+ android.R.drawable.ic_menu_search);
+ menu.add(1, Id.menu.option.preferences, 3, R.string.menu_preferences).setIcon(
+ android.R.drawable.ic_menu_preferences);
+ menu.add(1, Id.menu.option.about, 4, R.string.menu_about).setIcon(
+ android.R.drawable.ic_menu_info_details);
+ menu.add(1, Id.menu.option.scanQRCode, 5, R.string.menu_scanQRCode).setIcon(
+ android.R.drawable.ic_menu_add);
+ return true;
+ }
+
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
+ super.onCreateContextMenu(menu, v, menuInfo);
+ ExpandableListView.ExpandableListContextMenuInfo info = (ExpandableListView.ExpandableListContextMenuInfo) menuInfo;
+ int type = ExpandableListView.getPackedPositionType(info.packedPosition);
+
+ if (type == ExpandableListView.PACKED_POSITION_TYPE_GROUP) {
+ // TODO: user id? menu.setHeaderTitle("Key");
+ menu.add(0, Id.menu.export, 0, R.string.menu_exportKey);
+ menu.add(0, Id.menu.delete, 1, R.string.menu_deleteKey);
+ menu.add(0, Id.menu.update, 1, R.string.menu_updateKey);
+ menu.add(0, Id.menu.exportToServer, 1, R.string.menu_exportKeyToServer);
+ menu.add(0, Id.menu.signKey, 1, R.string.menu_signKey);
+ }
+ }
+
+ @Override
+ public boolean onContextItemSelected(MenuItem menuItem) {
+ ExpandableListContextMenuInfo info = (ExpandableListContextMenuInfo) menuItem.getMenuInfo();
+ int type = ExpandableListView.getPackedPositionType(info.packedPosition);
+ int groupPosition = ExpandableListView.getPackedPositionGroup(info.packedPosition);
+
+ if (type != ExpandableListView.PACKED_POSITION_TYPE_GROUP) {
+ return super.onContextItemSelected(menuItem);
+ }
+
+ switch (menuItem.getItemId()) {
+ case Id.menu.update: {
+ mSelectedItem = groupPosition;
+ final int keyRingId = mListAdapter.getKeyRingId(groupPosition);
+ long keyId = 0;
+ Object keyRing = Apg.getKeyRing(keyRingId);
+ if (keyRing != null && keyRing instanceof PGPPublicKeyRing) {
+ keyId = Apg.getMasterKey((PGPPublicKeyRing) keyRing).getKeyID();
+ }
+ if (keyId == 0) {
+ // this shouldn't happen
+ return true;
+ }
+
+ Intent intent = new Intent(this, KeyServerQueryActivity.class);
+ intent.setAction(Apg.Intent.LOOK_UP_KEY_ID_AND_RETURN);
+ intent.putExtra(Apg.EXTRA_KEY_ID, keyId);
+ startActivityForResult(intent, Id.request.look_up_key_id);
+
+ return true;
+ }
+
+ case Id.menu.exportToServer: {
+ mSelectedItem = groupPosition;
+ final int keyRingId = mListAdapter.getKeyRingId(groupPosition);
+
+ Intent intent = new Intent(this, SendKeyActivity.class);
+ intent.setAction(Apg.Intent.EXPORT_KEY_TO_SERVER);
+ intent.putExtra(Apg.EXTRA_KEY_ID, keyRingId);
+ startActivityForResult(intent, Id.request.export_to_server);
+
+ return true;
+ }
+
+ case Id.menu.signKey: {
+ mSelectedItem = groupPosition;
+ final int keyRingId = mListAdapter.getKeyRingId(groupPosition);
+ long keyId = 0;
+ Object keyRing = Apg.getKeyRing(keyRingId);
+ if (keyRing != null && keyRing instanceof PGPPublicKeyRing) {
+ keyId = Apg.getMasterKey((PGPPublicKeyRing) keyRing).getKeyID();
+ }
+
+ if (keyId == 0) {
+ // this shouldn't happen
+ return true;
+ }
+
+ Intent intent = new Intent(this, SignKeyActivity.class);
+ intent.putExtra(Apg.EXTRA_KEY_ID, keyId);
+ startActivity(intent);
+
+ return true;
+ }
+
+ default: {
+ return super.onContextItemSelected(menuItem);
+ }
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case Id.menu.option.scanQRCode: {
+ Intent intent = new Intent(this, ImportFromQRCodeActivity.class);
+ intent.setAction(Apg.Intent.IMPORT_FROM_QR_CODE);
+ startActivityForResult(intent, Id.request.import_from_qr_code);
+
+ return true;
+ }
+
+ default: {
+ return super.onOptionsItemSelected(item);
+ }
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ switch (requestCode) {
+ case Id.request.look_up_key_id: {
+ if (resultCode == RESULT_CANCELED || data == null
+ || data.getStringExtra(Apg.EXTRA_TEXT) == null) {
+ return;
+ }
+
+ Intent intent = new Intent(this, PublicKeyListActivity.class);
+ intent.setAction(Apg.Intent.IMPORT);
+ intent.putExtra(Apg.EXTRA_TEXT, data.getStringExtra(Apg.EXTRA_TEXT));
+ handleIntent(intent);
+ break;
+ }
+
+ default: {
+ super.onActivityResult(requestCode, resultCode, data);
+ break;
+ }
+ }
+ }
+}
diff --git a/org_apg/src/org/apg/ui/SecretKeyListActivity.java b/org_apg/src/org/apg/ui/SecretKeyListActivity.java
new file mode 100644
index 000000000..a5d351bc6
--- /dev/null
+++ b/org_apg/src/org/apg/ui/SecretKeyListActivity.java
@@ -0,0 +1,203 @@
+/*
+ * 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.apg.ui;
+
+import org.apg.Apg;
+import org.apg.AskForSecretKeyPassPhrase;
+import org.apg.Constants;
+import org.apg.Id;
+import org.apg.Constants.path;
+import org.apg.Id.dialog;
+import org.apg.Id.menu;
+import org.apg.Id.message;
+import org.apg.Id.type;
+import org.apg.Id.menu.option;
+import org.apg.R;
+
+import android.app.Dialog;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.ExpandableListView;
+import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
+import android.widget.ExpandableListView.OnChildClickListener;
+
+import com.google.zxing.integration.android.IntentIntegrator;
+
+public class SecretKeyListActivity extends KeyListActivity implements OnChildClickListener {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ mExportFilename = Constants.path.APP_DIR + "/secexport.asc";
+ mKeyType = Id.type.secret_key;
+ super.onCreate(savedInstanceState);
+ mList.setOnChildClickListener(this);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ menu.add(0, Id.menu.option.import_keys, 0, R.string.menu_importKeys)
+ .setIcon(android.R.drawable.ic_menu_add);
+ menu.add(0, Id.menu.option.export_keys, 1, R.string.menu_exportKeys)
+ .setIcon(android.R.drawable.ic_menu_save);
+ menu.add(1, Id.menu.option.create, 2, R.string.menu_createKey)
+ .setIcon(android.R.drawable.ic_menu_add);
+ menu.add(3, Id.menu.option.search, 3, R.string.menu_search)
+ .setIcon(android.R.drawable.ic_menu_search);
+ menu.add(3, Id.menu.option.preferences, 4, R.string.menu_preferences)
+ .setIcon(android.R.drawable.ic_menu_preferences);
+ menu.add(3, Id.menu.option.about, 5, R.string.menu_about)
+ .setIcon(android.R.drawable.ic_menu_info_details);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case Id.menu.option.create: {
+ createKey();
+ return true;
+ }
+
+ default: {
+ return super.onOptionsItemSelected(item);
+ }
+ }
+ }
+
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
+ super.onCreateContextMenu(menu, v, menuInfo);
+ ExpandableListView.ExpandableListContextMenuInfo info =
+ (ExpandableListView.ExpandableListContextMenuInfo) menuInfo;
+ int type = ExpandableListView.getPackedPositionType(info.packedPosition);
+
+ if (type == ExpandableListView.PACKED_POSITION_TYPE_GROUP) {
+ // TODO: user id? menu.setHeaderTitle("Key");
+ menu.add(0, Id.menu.edit, 0, R.string.menu_editKey);
+ menu.add(0, Id.menu.export, 1, R.string.menu_exportKey);
+ menu.add(0, Id.menu.delete, 2, R.string.menu_deleteKey);
+ menu.add(0, Id.menu.share, 2, R.string.menu_share);
+ }
+ }
+
+ @Override
+ public boolean onContextItemSelected(MenuItem menuItem) {
+ ExpandableListContextMenuInfo info = (ExpandableListContextMenuInfo) menuItem.getMenuInfo();
+ int type = ExpandableListView.getPackedPositionType(info.packedPosition);
+ int groupPosition = ExpandableListView.getPackedPositionGroup(info.packedPosition);
+
+ if (type != ExpandableListView.PACKED_POSITION_TYPE_GROUP) {
+ return super.onContextItemSelected(menuItem);
+ }
+
+ switch (menuItem.getItemId()) {
+ case Id.menu.edit: {
+ mSelectedItem = groupPosition;
+ checkPassPhraseAndEdit();
+ return true;
+ }
+
+ case Id.menu.share: {
+ mSelectedItem = groupPosition;
+
+ long keyId = ((KeyListAdapter) mList.getExpandableListAdapter()).getGroupId(mSelectedItem);
+ String msg = keyId + "," + Apg.getFingerPrint(keyId);;
+
+ new IntentIntegrator(this).shareText(msg);
+ }
+
+ default: {
+ return super.onContextItemSelected(menuItem);
+ }
+ }
+ }
+
+ public boolean onChildClick(ExpandableListView parent, View v, int groupPosition,
+ int childPosition, long id) {
+ mSelectedItem = groupPosition;
+ checkPassPhraseAndEdit();
+ return true;
+ }
+
+ @Override
+ protected Dialog onCreateDialog(int id) {
+ switch (id) {
+ case Id.dialog.pass_phrase: {
+ long keyId = ((KeyListAdapter) mList.getExpandableListAdapter()).getGroupId(mSelectedItem);
+ return AskForSecretKeyPassPhrase.createDialog(this, keyId, this);
+ }
+
+ default: {
+ return super.onCreateDialog(id);
+ }
+ }
+ }
+
+ public void checkPassPhraseAndEdit() {
+ long keyId = ((KeyListAdapter) mList.getExpandableListAdapter()).getGroupId(mSelectedItem);
+ String passPhrase = Apg.getCachedPassPhrase(keyId);
+ if (passPhrase == null) {
+ showDialog(Id.dialog.pass_phrase);
+ } else {
+ Apg.setEditPassPhrase(passPhrase);
+ editKey();
+ }
+ }
+
+ @Override
+ public void passPhraseCallback(long keyId, String passPhrase) {
+ super.passPhraseCallback(keyId, passPhrase);
+ Apg.setEditPassPhrase(passPhrase);
+ editKey();
+ }
+
+ private void createKey() {
+ Apg.setEditPassPhrase("");
+ Intent intent = new Intent(this, EditKeyActivity.class);
+ startActivityForResult(intent, Id.message.create_key);
+ }
+
+ private void editKey() {
+ long keyId = ((KeyListAdapter) mList.getExpandableListAdapter()).getGroupId(mSelectedItem);
+ Intent intent = new Intent(this, EditKeyActivity.class);
+ intent.putExtra(Apg.EXTRA_KEY_ID, keyId);
+ startActivityForResult(intent, Id.message.edit_key);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ switch (requestCode) {
+ case Id.message.create_key: // intentionally no break
+ case Id.message.edit_key: {
+ if (resultCode == RESULT_OK) {
+ refreshList();
+ }
+ break;
+ }
+
+ default: {
+ break;
+ }
+ }
+
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+}
diff --git a/org_apg/src/org/apg/ui/SelectPublicKeyListActivity.java b/org_apg/src/org/apg/ui/SelectPublicKeyListActivity.java
new file mode 100644
index 000000000..5216e7a3d
--- /dev/null
+++ b/org_apg/src/org/apg/ui/SelectPublicKeyListActivity.java
@@ -0,0 +1,172 @@
+/*
+ * 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.apg.ui;
+
+import java.util.Vector;
+
+import org.apg.Apg;
+import org.apg.Id;
+import org.apg.Id.menu;
+import org.apg.Id.menu.option;
+import org.apg.R;
+
+import android.app.SearchManager;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.ListView;
+import android.widget.TextView;
+
+public class SelectPublicKeyListActivity extends BaseActivity {
+ protected ListView mList;
+ protected SelectPublicKeyListAdapter mListAdapter;
+ protected View mFilterLayout;
+ protected Button mClearFilterButton;
+ protected TextView mFilterInfo;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.select_public_key);
+
+ setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
+
+ mList = (ListView) findViewById(R.id.list);
+ // needed in Android 1.5, where the XML attribute gets ignored
+ mList.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
+
+ Button okButton = (Button) findViewById(R.id.btn_ok);
+ okButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ okClicked();
+ }
+ });
+
+ Button cancelButton = (Button) findViewById(R.id.btn_cancel);
+ cancelButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ cancelClicked();
+ }
+ });
+
+ 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());
+ }
+ });
+
+ handleIntent(getIntent());
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ handleIntent(intent);
+ }
+
+ private void handleIntent(Intent intent) {
+ String searchString = null;
+ if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
+ searchString = intent.getStringExtra(SearchManager.QUERY);
+ if (searchString != null && searchString.trim().length() == 0) {
+ searchString = null;
+ }
+ }
+
+ long selectedKeyIds[] = null;
+ selectedKeyIds = intent.getLongArrayExtra(Apg.EXTRA_SELECTION);
+
+ if (selectedKeyIds == null) {
+ Vector<Long> vector = new Vector<Long>();
+ for (int i = 0; i < mList.getCount(); ++i) {
+ if (mList.isItemChecked(i)) {
+ vector.add(mList.getItemIdAtPosition(i));
+ }
+ }
+ selectedKeyIds = new long[vector.size()];
+ for (int i = 0; i < vector.size(); ++i) {
+ selectedKeyIds[i] = vector.get(i);
+ }
+ }
+
+ if (searchString == null) {
+ mFilterLayout.setVisibility(View.GONE);
+ } else {
+ mFilterLayout.setVisibility(View.VISIBLE);
+ mFilterInfo.setText(getString(R.string.filterInfo, searchString));
+ }
+
+ if (mListAdapter != null) {
+ mListAdapter.cleanup();
+ }
+
+ mListAdapter = new SelectPublicKeyListAdapter(this, mList, searchString, selectedKeyIds);
+ mList.setAdapter(mListAdapter);
+
+ if (selectedKeyIds != null) {
+ for (int i = 0; i < mListAdapter.getCount(); ++i) {
+ long keyId = mListAdapter.getItemId(i);
+ for (int j = 0; j < selectedKeyIds.length; ++j) {
+ if (keyId == selectedKeyIds[j]) {
+ mList.setItemChecked(i, true);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ private void cancelClicked() {
+ setResult(RESULT_CANCELED, null);
+ finish();
+ }
+
+ private void okClicked() {
+ Intent data = new Intent();
+ Vector<Long> keys = new Vector<Long>();
+ Vector<String> userIds = new Vector<String>();
+ for (int i = 0; i < mList.getCount(); ++i) {
+ if (mList.isItemChecked(i)) {
+ keys.add(mList.getItemIdAtPosition(i));
+ userIds.add((String) mList.getItemAtPosition(i));
+ }
+ }
+ long selectedKeyIds[] = new long[keys.size()];
+ for (int i = 0; i < keys.size(); ++i) {
+ selectedKeyIds[i] = keys.get(i);
+ }
+ String userIdArray[] = new String[0];
+ data.putExtra(Apg.EXTRA_SELECTION, selectedKeyIds);
+ data.putExtra(Apg.EXTRA_USER_IDS, userIds.toArray(userIdArray));
+ setResult(RESULT_OK, data);
+ finish();
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ menu.add(0, Id.menu.option.search, 0, R.string.menu_search)
+ .setIcon(android.R.drawable.ic_menu_search);
+ return true;
+ }
+}
diff --git a/org_apg/src/org/apg/ui/SelectPublicKeyListAdapter.java b/org_apg/src/org/apg/ui/SelectPublicKeyListAdapter.java
new file mode 100644
index 000000000..b2f49f74a
--- /dev/null
+++ b/org_apg/src/org/apg/ui/SelectPublicKeyListAdapter.java
@@ -0,0 +1,226 @@
+/*
+ * 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.apg.ui;
+
+import java.util.Date;
+
+import org.apg.Apg;
+import org.apg.Id;
+import org.apg.Id.database;
+import org.apg.provider.KeyRings;
+import org.apg.provider.Keys;
+import org.apg.provider.UserIds;
+import org.apg.R;
+
+import android.app.Activity;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.CheckBox;
+import android.widget.ListView;
+import android.widget.TextView;
+
+public class SelectPublicKeyListAdapter extends BaseAdapter {
+ protected LayoutInflater mInflater;
+ protected ListView mParent;
+ protected SQLiteDatabase mDatabase;
+ protected Cursor mCursor;
+ protected String mSearchString;
+ protected Activity mActivity;
+
+ public SelectPublicKeyListAdapter(Activity activity, ListView parent,
+ String searchString, long selectedKeyIds[]) {
+ mSearchString = searchString;
+
+ mActivity = activity;
+ mParent = parent;
+ mDatabase = Apg.getDatabase().db();
+ mInflater = (LayoutInflater) parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ long now = new Date().getTime() / 1000;
+ SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+ qb.setTables(KeyRings.TABLE_NAME + " INNER JOIN " + Keys.TABLE_NAME + " ON " +
+ "(" + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " +
+ Keys.TABLE_NAME + "." + Keys.KEY_RING_ID + " AND " +
+ Keys.TABLE_NAME + "." + Keys.IS_MASTER_KEY + " = '1'" +
+ ") " +
+ " INNER JOIN " + UserIds.TABLE_NAME + " ON " +
+ "(" + Keys.TABLE_NAME + "." + Keys._ID + " = " +
+ UserIds.TABLE_NAME + "." + UserIds.KEY_ID + " AND " +
+ UserIds.TABLE_NAME + "." + UserIds.RANK + " = '0') ");
+
+ String inIdList = null;
+
+ if (selectedKeyIds != null && selectedKeyIds.length > 0) {
+ inIdList = KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID + " IN (";
+ for (int i = 0; i < selectedKeyIds.length; ++i) {
+ if (i != 0) {
+ inIdList += ", ";
+ }
+ inIdList += DatabaseUtils.sqlEscapeString("" + selectedKeyIds[i]);
+ }
+ inIdList += ")";
+ }
+
+ 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.TABLE_NAME + "." + UserIds.USER_ID + " ASC";
+ if (inIdList != null) {
+ orderBy = inIdList + " DESC, " + orderBy;
+ }
+
+ mCursor = qb.query(mDatabase,
+ new String[] {
+ KeyRings.TABLE_NAME + "." + KeyRings._ID, // 0
+ KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID, // 1
+ UserIds.TABLE_NAME + "." + UserIds.USER_ID, // 2
+ "(SELECT COUNT(tmp." + Keys._ID + ") FROM " + Keys.TABLE_NAME + " AS tmp WHERE " +
+ "tmp." + Keys.KEY_RING_ID + " = " +
+ KeyRings.TABLE_NAME + "." + KeyRings._ID + " AND " +
+ "tmp." + Keys.IS_REVOKED + " = '0' AND " +
+ "tmp." + Keys.CAN_ENCRYPT + " = '1')", // 3
+ "(SELECT COUNT(tmp." + Keys._ID + ") FROM " + Keys.TABLE_NAME + " AS tmp WHERE " +
+ "tmp." + Keys.KEY_RING_ID + " = " +
+ KeyRings.TABLE_NAME + "." + KeyRings._ID + " AND " +
+ "tmp." + Keys.IS_REVOKED + " = '0' AND " +
+ "tmp." + Keys.CAN_ENCRYPT + " = '1' AND " +
+ "tmp." + Keys.CREATION + " <= '" + now + "' AND " +
+ "(tmp." + Keys.EXPIRY + " IS NULL OR " +
+ "tmp." + Keys.EXPIRY + " >= '" + now + "'))", // 4
+ },
+ KeyRings.TABLE_NAME + "." + KeyRings.TYPE + " = ?",
+ new String[] { "" + Id.database.type_public },
+ null, null, orderBy);
+
+ activity.startManagingCursor(mCursor);
+ }
+
+ public void cleanup() {
+ if (mCursor != null) {
+ mActivity.stopManagingCursor(mCursor);
+ mCursor.close();
+ }
+ }
+
+ @Override
+ public boolean isEnabled(int position) {
+ mCursor.moveToPosition(position);
+ return mCursor.getInt(4) > 0; // valid CAN_ENCRYPT
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return true;
+ }
+
+ public int getCount() {
+ return mCursor.getCount();
+ }
+
+ public Object getItem(int position) {
+ mCursor.moveToPosition(position);
+ return mCursor.getString(2); // USER_ID
+ }
+
+ public long getItemId(int position) {
+ mCursor.moveToPosition(position);
+ return mCursor.getLong(1); // MASTER_KEY_ID
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ mCursor.moveToPosition(position);
+
+ View view = mInflater.inflate(R.layout.select_public_key_item, null);
+ boolean enabled = isEnabled(position);
+
+ TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId);
+ mainUserId.setText(R.string.unknownUserId);
+ TextView mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest);
+ mainUserIdRest.setText("");
+ TextView keyId = (TextView) view.findViewById(R.id.keyId);
+ keyId.setText(R.string.noKey);
+ TextView status = (TextView) view.findViewById(R.id.status);
+ status.setText(R.string.unknownStatus);
+
+ String userId = mCursor.getString(2); // USER_ID
+ if (userId != null) {
+ String chunks[] = userId.split(" <", 2);
+ userId = chunks[0];
+ if (chunks.length > 1) {
+ mainUserIdRest.setText("<" + chunks[1]);
+ }
+ mainUserId.setText(userId);
+ }
+
+ long masterKeyId = mCursor.getLong(1); // MASTER_KEY_ID
+ keyId.setText(Apg.getSmallFingerPrint(masterKeyId));
+
+ if (mainUserIdRest.getText().length() == 0) {
+ mainUserIdRest.setVisibility(View.GONE);
+ }
+
+ if (enabled) {
+ status.setText(R.string.canEncrypt);
+ } else {
+ if (mCursor.getInt(3) > 0) {
+ // has some CAN_ENCRYPT keys, but col(4) = 0, so must be revoked or expired
+ status.setText(R.string.expired);
+ } else {
+ status.setText(R.string.noKey);
+ }
+ }
+
+ status.setText(status.getText() + " ");
+
+ CheckBox selected = (CheckBox) view.findViewById(R.id.selected);
+
+ if (!enabled) {
+ mParent.setItemChecked(position, false);
+ }
+
+ selected.setChecked(mParent.isItemChecked(position));
+
+ view.setEnabled(enabled);
+ mainUserId.setEnabled(enabled);
+ mainUserIdRest.setEnabled(enabled);
+ keyId.setEnabled(enabled);
+ selected.setEnabled(enabled);
+ status.setEnabled(enabled);
+
+ return view;
+ }
+}
diff --git a/org_apg/src/org/apg/ui/SelectSecretKeyListActivity.java b/org_apg/src/org/apg/ui/SelectSecretKeyListActivity.java
new file mode 100644
index 000000000..191a0ecc7
--- /dev/null
+++ b/org_apg/src/org/apg/ui/SelectSecretKeyListActivity.java
@@ -0,0 +1,115 @@
+/*
+ * 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.apg.ui;
+
+import org.apg.Apg;
+import org.apg.Id;
+import org.apg.Id.menu;
+import org.apg.Id.menu.option;
+import org.apg.R;
+
+import android.app.SearchManager;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.Button;
+import android.widget.ListView;
+import android.widget.TextView;
+
+public class SelectSecretKeyListActivity extends BaseActivity {
+ protected ListView mList;
+ protected SelectSecretKeyListAdapter mListAdapter;
+ protected View mFilterLayout;
+ protected Button mClearFilterButton;
+ protected TextView mFilterInfo;
+
+ protected long mSelectedKeyId = 0;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
+
+ setContentView(R.layout.select_secret_key);
+
+ mList = (ListView) findViewById(R.id.list);
+
+ mList.setOnItemClickListener(new OnItemClickListener() {
+ public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
+ Intent data = new Intent();
+ data.putExtra(Apg.EXTRA_KEY_ID, id);
+ data.putExtra(Apg.EXTRA_USER_ID, (String)mList.getItemAtPosition(position));
+ setResult(RESULT_OK, data);
+ finish();
+ }
+ });
+
+ 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());
+ }
+ });
+
+ handleIntent(getIntent());
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ handleIntent(intent);
+ }
+
+ private void handleIntent(Intent intent) {
+ 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));
+ }
+
+ if (mListAdapter != null) {
+ mListAdapter.cleanup();
+ }
+
+ mListAdapter = new SelectSecretKeyListAdapter(this, mList, searchString);
+ mList.setAdapter(mListAdapter);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ menu.add(0, Id.menu.option.search, 0, R.string.menu_search)
+ .setIcon(android.R.drawable.ic_menu_search);
+ return true;
+ }
+}
diff --git a/org_apg/src/org/apg/ui/SelectSecretKeyListAdapter.java b/org_apg/src/org/apg/ui/SelectSecretKeyListAdapter.java
new file mode 100644
index 000000000..1a7734245
--- /dev/null
+++ b/org_apg/src/org/apg/ui/SelectSecretKeyListAdapter.java
@@ -0,0 +1,176 @@
+package org.apg.ui;
+
+import java.util.Date;
+
+import org.apg.Apg;
+import org.apg.Id;
+import org.apg.Id.database;
+import org.apg.provider.KeyRings;
+import org.apg.provider.Keys;
+import org.apg.provider.UserIds;
+import org.apg.R;
+
+import android.app.Activity;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+
+public class SelectSecretKeyListAdapter extends BaseAdapter {
+ protected LayoutInflater mInflater;
+ protected ListView mParent;
+ protected SQLiteDatabase mDatabase;
+ protected Cursor mCursor;
+ protected String mSearchString;
+ protected Activity mActivity;
+
+ public SelectSecretKeyListAdapter(Activity activity, ListView parent, String searchString) {
+ mSearchString = searchString;
+
+ mActivity = activity;
+ mParent = parent;
+ mDatabase = Apg.getDatabase().db();
+ mInflater = (LayoutInflater) parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ long now = new Date().getTime() / 1000;
+ SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+ qb.setTables(KeyRings.TABLE_NAME + " INNER JOIN " + Keys.TABLE_NAME + " ON " +
+ "(" + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " +
+ Keys.TABLE_NAME + "." + Keys.KEY_RING_ID + " AND " +
+ Keys.TABLE_NAME + "." + Keys.IS_MASTER_KEY + " = '1'" +
+ ") " +
+ " INNER JOIN " + UserIds.TABLE_NAME + " ON " +
+ "(" + Keys.TABLE_NAME + "." + Keys._ID + " = " +
+ UserIds.TABLE_NAME + "." + UserIds.KEY_ID + " AND " +
+ UserIds.TABLE_NAME + "." + UserIds.RANK + " = '0') ");
+
+ 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(")");
+ }
+
+ mCursor = qb.query(mDatabase,
+ new String[] {
+ KeyRings.TABLE_NAME + "." + KeyRings._ID, // 0
+ KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID, // 1
+ UserIds.TABLE_NAME + "." + UserIds.USER_ID, // 2
+ "(SELECT COUNT(tmp." + Keys._ID + ") FROM " + Keys.TABLE_NAME + " AS tmp WHERE " +
+ "tmp." + Keys.KEY_RING_ID + " = " +
+ KeyRings.TABLE_NAME + "." + KeyRings._ID + " AND " +
+ "tmp." + Keys.IS_REVOKED + " = '0' AND " +
+ "tmp." + Keys.CAN_SIGN + " = '1')", // 3,
+ "(SELECT COUNT(tmp." + Keys._ID + ") FROM " + Keys.TABLE_NAME + " AS tmp WHERE " +
+ "tmp." + Keys.KEY_RING_ID + " = " +
+ KeyRings.TABLE_NAME + "." + KeyRings._ID + " AND " +
+ "tmp." + Keys.IS_REVOKED + " = '0' AND " +
+ "tmp." + Keys.CAN_SIGN + " = '1' AND " +
+ "tmp." + Keys.CREATION + " <= '" + now + "' AND " +
+ "(tmp." + Keys.EXPIRY + " IS NULL OR " +
+ "tmp." + Keys.EXPIRY + " >= '" + now + "'))", // 4
+ },
+ KeyRings.TABLE_NAME + "." + KeyRings.TYPE + " = ?",
+ new String[] { "" + Id.database.type_secret },
+ null, null, UserIds.TABLE_NAME + "." + UserIds.USER_ID + " ASC");
+
+ activity.startManagingCursor(mCursor);
+ }
+
+ public void cleanup() {
+ if (mCursor != null) {
+ mActivity.stopManagingCursor(mCursor);
+ mCursor.close();
+ }
+ }
+
+ @Override
+ public boolean isEnabled(int position) {
+ mCursor.moveToPosition(position);
+ return mCursor.getInt(4) > 0; // valid CAN_SIGN
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return true;
+ }
+
+ public int getCount() {
+ return mCursor.getCount();
+ }
+
+ public Object getItem(int position) {
+ mCursor.moveToPosition(position);
+ return mCursor.getString(2); // USER_ID
+ }
+
+ public long getItemId(int position) {
+ mCursor.moveToPosition(position);
+ return mCursor.getLong(1); // MASTER_KEY_ID
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ mCursor.moveToPosition(position);
+
+ View view = mInflater.inflate(R.layout.select_secret_key_item, null);
+ boolean enabled = isEnabled(position);
+
+ TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId);
+ mainUserId.setText(R.string.unknownUserId);
+ TextView mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest);
+ mainUserIdRest.setText("");
+ TextView keyId = (TextView) view.findViewById(R.id.keyId);
+ keyId.setText(R.string.noKey);
+ TextView status = (TextView) view.findViewById(R.id.status);
+ status.setText(R.string.unknownStatus);
+
+ String userId = mCursor.getString(2); // USER_ID
+ if (userId != null) {
+ String chunks[] = userId.split(" <", 2);
+ userId = chunks[0];
+ if (chunks.length > 1) {
+ mainUserIdRest.setText("<" + chunks[1]);
+ }
+ mainUserId.setText(userId);
+ }
+
+ long masterKeyId = mCursor.getLong(1); // MASTER_KEY_ID
+ keyId.setText(Apg.getSmallFingerPrint(masterKeyId));
+
+ if (mainUserIdRest.getText().length() == 0) {
+ mainUserIdRest.setVisibility(View.GONE);
+ }
+
+ if (enabled) {
+ status.setText(R.string.canSign);
+ } else {
+ if (mCursor.getInt(3) > 0) {
+ // has some CAN_SIGN keys, but col(4) = 0, so must be revoked or expired
+ status.setText(R.string.expired);
+ } else {
+ status.setText(R.string.noKey);
+ }
+ }
+
+ status.setText(status.getText() + " ");
+
+ view.setEnabled(enabled);
+ mainUserId.setEnabled(enabled);
+ mainUserIdRest.setEnabled(enabled);
+ keyId.setEnabled(enabled);
+ status.setEnabled(enabled);
+
+ return view;
+ }
+} \ No newline at end of file
diff --git a/org_apg/src/org/apg/ui/SendKeyActivity.java b/org_apg/src/org/apg/ui/SendKeyActivity.java
new file mode 100644
index 000000000..c44e87469
--- /dev/null
+++ b/org_apg/src/org/apg/ui/SendKeyActivity.java
@@ -0,0 +1,100 @@
+package org.apg.ui;
+
+import org.apg.Apg;
+import org.apg.Constants;
+import org.apg.HkpKeyServer;
+import org.apg.Id;
+import org.apg.Constants.extras;
+import org.apg.Id.message;
+import org.spongycastle.openpgp.PGPKeyRing;
+import org.spongycastle.openpgp.PGPPublicKeyRing;
+import org.apg.R;
+
+import android.os.Bundle;
+import android.os.Message;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.Spinner;
+import android.widget.Toast;
+
+/**
+ * gpg --send-key activity
+ *
+ * Sends the selected public key to a key server
+ */
+public class SendKeyActivity extends BaseActivity {
+
+ private Button export;
+ private Spinner keyServer;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.key_server_export_layout);
+
+ export = (Button) findViewById(R.id.btn_export_to_server);
+ keyServer = (Spinner) findViewById(R.id.keyServer);
+
+ ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, mPreferences.getKeyServers());
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ keyServer.setAdapter(adapter);
+ if (adapter.getCount() > 0) {
+ keyServer.setSelection(0);
+ } else {
+ export.setEnabled(false);
+ }
+
+ export.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ startThread();
+ }
+ });
+ }
+
+ @Override
+ public void run() {
+ String error = null;
+ Bundle data = new Bundle();
+ Message msg = new Message();
+
+ HkpKeyServer server = new HkpKeyServer((String) keyServer.getSelectedItem());
+
+ int keyRingId = getIntent().getIntExtra(Apg.EXTRA_KEY_ID, -1);
+
+ PGPKeyRing keyring = Apg.getKeyRing(keyRingId);
+ if (keyring != null && keyring instanceof PGPPublicKeyRing) {
+ boolean uploaded = Apg.uploadKeyRingToServer(server, (PGPPublicKeyRing) keyring);
+ if (!uploaded) {
+ error = "Unable to export key to selected server";
+ }
+ }
+
+ data.putInt(Constants.extras.STATUS, Id.message.export_done);
+
+ if (error != null) {
+ data.putString(Apg.EXTRA_ERROR, error);
+ }
+
+ msg.setData(data);
+ sendMessage(msg);
+ }
+
+ @Override
+ public void doneCallback(Message msg) {
+ super.doneCallback(msg);
+
+ Bundle data = msg.getData();
+ String error = data.getString(Apg.EXTRA_ERROR);
+ if (error != null) {
+ Toast.makeText(this, getString(R.string.errorMessage, error), Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ Toast.makeText(this, R.string.keySendSuccess, Toast.LENGTH_SHORT).show();
+ finish();
+ }
+}
diff --git a/org_apg/src/org/apg/ui/SignKeyActivity.java b/org_apg/src/org/apg/ui/SignKeyActivity.java
new file mode 100644
index 000000000..ab145c921
--- /dev/null
+++ b/org_apg/src/org/apg/ui/SignKeyActivity.java
@@ -0,0 +1,294 @@
+package org.apg.ui;
+
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.SignatureException;
+import java.util.Iterator;
+
+import org.apg.Apg;
+import org.apg.Constants;
+import org.apg.HkpKeyServer;
+import org.apg.Id;
+import org.apg.Constants.extras;
+import org.apg.Id.dialog;
+import org.apg.Id.message;
+import org.apg.Id.request;
+import org.apg.Id.return_value;
+import org.spongycastle.jce.provider.BouncyCastleProvider;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPPrivateKey;
+import org.spongycastle.openpgp.PGPPublicKey;
+import org.spongycastle.openpgp.PGPPublicKeyRing;
+import org.spongycastle.openpgp.PGPSecretKey;
+import org.spongycastle.openpgp.PGPSignature;
+import org.spongycastle.openpgp.PGPSignatureGenerator;
+import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator;
+import org.spongycastle.openpgp.PGPSignatureSubpacketVector;
+import org.spongycastle.openpgp.PGPUtil;
+import org.apg.R;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Message;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+import android.widget.Spinner;
+import android.widget.Toast;
+
+/**
+ * gpg --sign-key
+ *
+ * signs the specified public key with the specified secret master key
+ */
+public class SignKeyActivity extends BaseActivity {
+ private static final String TAG = "SignKeyActivity";
+
+ private long pubKeyId = 0;
+ private long masterKeyId = 0;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // check we havent already signed it
+ setContentView(R.layout.sign_key_layout);
+
+ final Spinner keyServer = (Spinner) findViewById(R.id.keyServer);
+ ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, mPreferences.getKeyServers());
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ keyServer.setAdapter(adapter);
+
+ final CheckBox sendKey = (CheckBox) findViewById(R.id.sendKey);
+ if (!sendKey.isChecked()) {
+ keyServer.setEnabled(false);
+ } else {
+ keyServer.setEnabled(true);
+ }
+
+ sendKey.setOnCheckedChangeListener(new OnCheckedChangeListener() {
+
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ if (!isChecked) {
+ keyServer.setEnabled(false);
+ } else {
+ keyServer.setEnabled(true);
+ }
+ }
+ });
+
+ Button sign = (Button) findViewById(R.id.sign);
+ sign.setEnabled(false); // disabled until the user selects a key to sign with
+ sign.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ if (pubKeyId != 0) {
+ initiateSigning();
+ }
+ }
+ });
+
+ pubKeyId = getIntent().getLongExtra(Apg.EXTRA_KEY_ID, 0);
+ if (pubKeyId == 0) {
+ finish(); // nothing to do if we dont know what key to sign
+ } else {
+ // kick off the SecretKey selection activity so the user chooses which key to sign with first
+ Intent intent = new Intent(this, SelectSecretKeyListActivity.class);
+ startActivityForResult(intent, Id.request.secret_keys);
+ }
+ }
+
+ /**
+ * handles the UI bits of the signing process on the UI thread
+ */
+ private void initiateSigning() {
+ PGPPublicKeyRing pubring = Apg.getPublicKeyRing(pubKeyId);
+ 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(pubKeyId).getSignatures();
+ while (itr.hasNext()) {
+ PGPSignature sig = itr.next();
+ if (sig.getKeyID() == masterKeyId) {
+ alreadySigned = true;
+ break;
+ }
+ }
+
+ if (!alreadySigned) {
+ /*
+ * get the user's passphrase for this key (if required)
+ */
+ String passphrase = Apg.getCachedPassPhrase(masterKeyId);
+ if (passphrase == null) {
+ showDialog(Id.dialog.pass_phrase);
+ return; // bail out; need to wait until the user has entered the passphrase before trying again
+ } else {
+ startSigning();
+ }
+ } else {
+ final Bundle status = new Bundle();
+ Message msg = new Message();
+
+ status.putString(Apg.EXTRA_ERROR, "Key has already been signed");
+
+ status.putInt(Constants.extras.STATUS, Id.message.done);
+
+ msg.setData(status);
+ sendMessage(msg);
+
+ setResult(Id.return_value.error);
+ finish();
+ }
+ }
+ }
+
+ @Override
+ public long getSecretKeyId() {
+ return masterKeyId;
+ }
+
+ @Override
+ public void passPhraseCallback(long keyId, String passPhrase) {
+ super.passPhraseCallback(keyId, passPhrase);
+ startSigning();
+ }
+
+ /**
+ * kicks off the actual signing process on a background thread
+ */
+ private void startSigning() {
+ showDialog(Id.dialog.signing);
+ startThread();
+ }
+
+ @Override
+ public void run() {
+ final Bundle status = new Bundle();
+ Message msg = new Message();
+
+ try {
+ String passphrase = Apg.getCachedPassPhrase(masterKeyId);
+ if (passphrase == null || passphrase.length() <= 0) {
+ status.putString(Apg.EXTRA_ERROR, "Unable to obtain passphrase");
+ } else {
+ PGPPublicKeyRing pubring = Apg.getPublicKeyRing(pubKeyId);
+
+ /*
+ * sign the incoming key
+ */
+ PGPSecretKey secretKey = Apg.getSecretKey(masterKeyId);
+ PGPPrivateKey signingKey = secretKey.extractPrivateKey(passphrase.toCharArray(), BouncyCastleProvider.PROVIDER_NAME);
+ PGPSignatureGenerator sGen = new PGPSignatureGenerator(secretKey.getPublicKey().getAlgorithm(), PGPUtil.SHA256, BouncyCastleProvider.PROVIDER_NAME);
+ sGen.initSign(PGPSignature.DIRECT_KEY, signingKey);
+
+ PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
+
+ PGPSignatureSubpacketVector packetVector = spGen.generate();
+ sGen.setHashedSubpackets(packetVector);
+
+ PGPPublicKey signedKey = PGPPublicKey.addCertification(pubring.getPublicKey(pubKeyId), sGen.generate());
+ pubring = PGPPublicKeyRing.insertPublicKey(pubring, signedKey);
+
+ // check if we need to send the key to the server or not
+ CheckBox sendKey = (CheckBox) findViewById(R.id.sendKey);
+ if (sendKey.isChecked()) {
+ Spinner keyServer = (Spinner) findViewById(R.id.keyServer);
+ HkpKeyServer server = new HkpKeyServer((String) keyServer.getSelectedItem());
+
+ /*
+ * upload the newly signed key to the key server
+ */
+
+ Apg.uploadKeyRingToServer(server, pubring);
+ }
+
+ // store the signed key in our local cache
+ int retval = Apg.storeKeyRingInCache(pubring);
+ if (retval != Id.return_value.ok && retval != Id.return_value.updated) {
+ status.putString(Apg.EXTRA_ERROR, "Failed to store signed key in local cache");
+ }
+ }
+ } catch (PGPException e) {
+ Log.e(TAG, "Failed to sign key", e);
+ status.putString(Apg.EXTRA_ERROR, "Failed to sign key");
+ status.putInt(Constants.extras.STATUS, Id.message.done);
+ return;
+ } catch (NoSuchAlgorithmException e) {
+ Log.e(TAG, "Failed to sign key", e);
+ status.putString(Apg.EXTRA_ERROR, "Failed to sign key");
+ status.putInt(Constants.extras.STATUS, Id.message.done);
+ return;
+ } catch (NoSuchProviderException e) {
+ Log.e(TAG, "Failed to sign key", e);
+ status.putString(Apg.EXTRA_ERROR, "Failed to sign key");
+ status.putInt(Constants.extras.STATUS, Id.message.done);
+ return;
+ } catch (SignatureException e) {
+ Log.e(TAG, "Failed to sign key", e);
+ status.putString(Apg.EXTRA_ERROR, "Failed to sign key");
+ status.putInt(Constants.extras.STATUS, Id.message.done);
+ return;
+ }
+
+ status.putInt(Constants.extras.STATUS, Id.message.done);
+
+ msg.setData(status);
+ sendMessage(msg);
+
+ if (status.containsKey(Apg.EXTRA_ERROR)) {
+ setResult(Id.return_value.error);
+ } else {
+ setResult(Id.return_value.ok);
+ }
+
+ finish();
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ switch (requestCode) {
+ case Id.request.secret_keys: {
+ if (resultCode == RESULT_OK) {
+ masterKeyId = data.getLongExtra(Apg.EXTRA_KEY_ID, 0);
+
+ // re-enable the sign button so the user can initiate the sign process
+ Button sign = (Button) findViewById(R.id.sign);
+ sign.setEnabled(true);
+ }
+
+ break;
+ }
+
+ default: {
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+ }
+ }
+
+ @Override
+ public void doneCallback(Message msg) {
+ super.doneCallback(msg);
+
+ removeDialog(Id.dialog.signing);
+
+ Bundle data = msg.getData();
+ String error = data.getString(Apg.EXTRA_ERROR);
+ if (error != null) {
+ Toast.makeText(this, getString(R.string.errorMessage, error), Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ Toast.makeText(this, R.string.keySignSuccess, Toast.LENGTH_SHORT).show();
+ finish();
+ }
+}
diff --git a/org_apg/src/org/apg/ui/widget/Editor.java b/org_apg/src/org/apg/ui/widget/Editor.java
new file mode 100644
index 000000000..be95ad656
--- /dev/null
+++ b/org_apg/src/org/apg/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.apg.ui.widget;
+
+public interface Editor {
+ public interface EditorListener {
+ public void onDeleted(Editor editor);
+ }
+
+ public void setEditorListener(EditorListener listener);
+}
diff --git a/org_apg/src/org/apg/ui/widget/IntegerListPreference.java b/org_apg/src/org/apg/ui/widget/IntegerListPreference.java
new file mode 100644
index 000000000..fa411a786
--- /dev/null
+++ b/org_apg/src/org/apg/ui/widget/IntegerListPreference.java
@@ -0,0 +1,95 @@
+/*
+ * 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.apg.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/org_apg/src/org/apg/ui/widget/KeyEditor.java b/org_apg/src/org/apg/ui/widget/KeyEditor.java
new file mode 100644
index 000000000..ef98f794a
--- /dev/null
+++ b/org_apg/src/org/apg/ui/widget/KeyEditor.java
@@ -0,0 +1,233 @@
+/*
+ * 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.apg.ui.widget;
+
+import org.apg.Apg;
+import org.apg.Id;
+import org.apg.util.Choice;
+import org.spongycastle.openpgp.PGPPublicKey;
+import org.spongycastle.openpgp.PGPSecretKey;
+import org.apg.R;
+
+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.Button;
+import android.widget.DatePicker;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+import java.text.DateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Vector;
+
+public class KeyEditor extends LinearLayout implements Editor, OnClickListener {
+ private PGPSecretKey mKey;
+
+ private EditorListener mEditorListener = null;
+
+ private boolean mIsMasterKey;
+ ImageButton mDeleteButton;
+ TextView mAlgorithm;
+ TextView mKeyId;
+ Spinner mUsage;
+ TextView mCreationDate;
+ Button mExpiryDateButton;
+ GregorianCalendar mExpiryDate;
+
+ private DatePickerDialog.OnDateSetListener mExpiryDateSetListener =
+ new DatePickerDialog.OnDateSetListener() {
+ public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
+ 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 = (Button) findViewById(R.id.expiry);
+ mUsage = (Spinner) findViewById(R.id.usage);
+ Choice choices[] = {
+ new Choice(Id.choice.usage.sign_only,
+ getResources().getString(R.string.choice_signOnly)),
+ new Choice(Id.choice.usage.encrypt_only,
+ getResources().getString(R.string.choice_encryptOnly)),
+ new Choice(Id.choice.usage.sign_and_encrypt,
+ getResources().getString(R.string.choice_signAndEncrypt)),
+ };
+ 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 = (ImageButton) 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));
+ dialog.setCancelable(true);
+ dialog.setButton(Dialog.BUTTON_NEGATIVE,
+ getContext().getString(R.string.btn_noDate),
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ setExpiryDate(null);
+ }
+ });
+ dialog.show();
+ }
+ });
+
+ super.onFinishInflate();
+ }
+
+ public void setValue(PGPSecretKey key, boolean isMasterKey) {
+ mKey = key;
+
+ mIsMasterKey = isMasterKey;
+ if (mIsMasterKey) {
+ mDeleteButton.setVisibility(View.INVISIBLE);
+ }
+
+ mAlgorithm.setText(Apg.getAlgorithmInfo(key));
+ String keyId1Str = Apg.getSmallFingerPrint(key.getKeyID());
+ String keyId2Str = Apg.getSmallFingerPrint(key.getKeyID() >> 32);
+ mKeyId.setText(keyId1Str + " " + keyId2Str);
+
+ Vector<Choice> choices = new Vector<Choice>();
+ boolean isElGamalKey = (key.getPublicKey().getAlgorithm() == PGPPublicKey.ELGAMAL_ENCRYPT);
+ if (!isElGamalKey) {
+ choices.add(new Choice(Id.choice.usage.sign_only,
+ getResources().getString(R.string.choice_signOnly)));
+ }
+ if (!mIsMasterKey) {
+ choices.add(new Choice(Id.choice.usage.encrypt_only,
+ getResources().getString(R.string.choice_encryptOnly)));
+ }
+ if (!isElGamalKey) {
+ choices.add(new Choice(Id.choice.usage.sign_and_encrypt,
+ getResources().getString(R.string.choice_signAndEncrypt)));
+ }
+
+ 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);
+
+ int selectId = 0;
+ if (Apg.isEncryptionKey(key)) {
+ if (Apg.isSigningKey(key)) {
+ selectId = Id.choice.usage.sign_and_encrypt;
+ } else {
+ selectId = Id.choice.usage.encrypt_only;
+ }
+ } 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(Apg.getCreationDate(key));
+ mCreationDate.setText(DateFormat.getDateInstance().format(cal.getTime()));
+ cal = new GregorianCalendar();
+ Date date = Apg.getExpiryDate(key);
+ if (date == null) {
+ setExpiryDate(null);
+ } else {
+ cal.setTime(Apg.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(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/org_apg/src/org/apg/ui/widget/KeyServerEditor.java b/org_apg/src/org/apg/ui/widget/KeyServerEditor.java
new file mode 100644
index 000000000..3d8634c76
--- /dev/null
+++ b/org_apg/src/org/apg/ui/widget/KeyServerEditor.java
@@ -0,0 +1,78 @@
+/*
+ * 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.apg.ui.widget;
+
+import org.apg.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.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+public class KeyServerEditor extends LinearLayout implements Editor, OnClickListener {
+ private EditorListener mEditorListener = null;
+
+ ImageButton mDeleteButton;
+ TextView mServer;
+
+ public KeyServerEditor(Context context) {
+ super(context);
+ }
+
+ public KeyServerEditor(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ setDrawingCacheEnabled(true);
+ setAlwaysDrawnWithCacheEnabled(true);
+
+ mServer = (TextView) findViewById(R.id.server);
+
+ mDeleteButton = (ImageButton) findViewById(R.id.delete);
+ mDeleteButton.setOnClickListener(this);
+
+ super.onFinishInflate();
+ }
+
+ public void setValue(String value) {
+ mServer.setText(value);
+ }
+
+ public String getValue() {
+ return mServer.getText().toString().trim();
+ }
+
+ public void onClick(View v) {
+ final ViewGroup parent = (ViewGroup)getParent();
+ if (v == mDeleteButton) {
+ parent.removeView(this);
+ if (mEditorListener != null) {
+ mEditorListener.onDeleted(this);
+ }
+ }
+ }
+
+ public void setEditorListener(EditorListener listener) {
+ mEditorListener = listener;
+ }
+}
diff --git a/org_apg/src/org/apg/ui/widget/SectionView.java b/org_apg/src/org/apg/ui/widget/SectionView.java
new file mode 100644
index 000000000..220699124
--- /dev/null
+++ b/org_apg/src/org/apg/ui/widget/SectionView.java
@@ -0,0 +1,335 @@
+/*
+ * 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.apg.ui.widget;
+
+import org.apg.Apg;
+import org.apg.Id;
+import org.apg.ui.widget.Editor.EditorListener;
+import org.apg.util.Choice;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPSecretKey;
+import org.apg.R;
+
+import android.app.AlertDialog;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+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.EditText;
+import android.widget.LinearLayout;
+import android.widget.Spinner;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidParameterException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.util.Vector;
+
+public class SectionView extends LinearLayout implements OnClickListener, EditorListener, Runnable {
+ private LayoutInflater mInflater;
+ private View mAdd;
+ private ViewGroup mEditors;
+ private TextView mTitle;
+ private int mType = 0;
+
+ private Choice mNewKeyAlgorithmChoice;
+ private int mNewKeySize;
+
+ volatile private PGPSecretKey mNewKey;
+ private ProgressDialog mProgressDialog;
+ private Thread mRunningThread = null;
+
+ private Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ Bundle data = msg.getData();
+ if (data != null) {
+ boolean closeProgressDialog = data.getBoolean("closeProgressDialog");
+ if (closeProgressDialog) {
+ if (mProgressDialog != null) {
+ mProgressDialog.dismiss();
+ mProgressDialog = null;
+ }
+ }
+
+ String error = data.getString(Apg.EXTRA_ERROR);
+ if (error != null) {
+ Toast.makeText(getContext(),
+ getContext().getString(R.string.errorMessage, error),
+ Toast.LENGTH_SHORT).show();
+ }
+
+ boolean gotNewKey = data.getBoolean("gotNewKey");
+ if (gotNewKey) {
+ KeyEditor view =
+ (KeyEditor) mInflater.inflate(R.layout.edit_key_key_item,
+ mEditors, false);
+ view.setEditorListener(SectionView.this);
+ boolean isMasterKey = (mEditors.getChildCount() == 0);
+ view.setValue(mNewKey, isMasterKey);
+ mEditors.addView(view);
+ SectionView.this.updateEditorsVisible();
+ }
+ }
+ }
+ };
+
+ public SectionView(Context context) {
+ super(context);
+ }
+
+ public SectionView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public ViewGroup getEditors() {
+ return mEditors;
+ }
+
+ public void setType(int type) {
+ mType = type;
+ switch (type) {
+ case Id.type.user_id: {
+ mTitle.setText(R.string.section_userIds);
+ break;
+ }
+
+ case Id.type.key: {
+ mTitle.setText(R.string.section_keys);
+ break;
+ }
+
+ default: {
+ break;
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void onFinishInflate() {
+ mInflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+ setDrawingCacheEnabled(true);
+ setAlwaysDrawnWithCacheEnabled(true);
+
+ mAdd = findViewById(R.id.header);
+ mAdd.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) {
+ 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, null);
+ dialog.setView(view);
+ dialog.setTitle(R.string.title_createKey);
+ dialog.setMessage(R.string.keyCreationElGamalInfo);
+
+ boolean wouldBeMasterKey = (mEditors.getChildCount() == 0);
+
+ final Spinner algorithm = (Spinner) view.findViewById(R.id.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 EditText keySize = (EditText) view.findViewById(R.id.size);
+
+ dialog.setPositiveButton(android.R.string.ok,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface di, int id) {
+ di.dismiss();
+ try {
+ mNewKeySize = Integer.parseInt("" + keySize.getText());
+ } 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);
+ }
+ mEditors.addView(view);
+ }
+
+ this.updateEditorsVisible();
+ }
+
+ public void setKeys(Vector<PGPSecretKey> list) {
+ if (mType != Id.type.key) {
+ return;
+ }
+
+ mEditors.removeAllViews();
+ for (PGPSecretKey key : list) {
+ KeyEditor view =
+ (KeyEditor) mInflater.inflate(R.layout.edit_key_key_item, mEditors, false);
+ view.setEditorListener(this);
+ boolean isMasterKey = (mEditors.getChildCount() == 0);
+ view.setValue(key, isMasterKey);
+ mEditors.addView(view);
+ }
+
+ this.updateEditorsVisible();
+ }
+
+ private void createKey() {
+ mProgressDialog = new ProgressDialog(getContext());
+ mProgressDialog.setMessage(getContext().getString(R.string.progress_generating));
+ mProgressDialog.setCancelable(false);
+ mProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
+ mProgressDialog.show();
+ mRunningThread = new Thread(this);
+ mRunningThread.start();
+ }
+
+ public void run() {
+ String error = null;
+ try {
+ PGPSecretKey masterKey = null;
+ String passPhrase;
+ if (mEditors.getChildCount() > 0) {
+ masterKey = ((KeyEditor) mEditors.getChildAt(0)).getValue();
+ passPhrase = Apg.getCachedPassPhrase(masterKey.getKeyID());
+ } else {
+ passPhrase = "";
+ }
+ mNewKey = Apg.createKey(getContext(),
+ mNewKeyAlgorithmChoice.getId(),
+ mNewKeySize, passPhrase,
+ masterKey);
+ } catch (NoSuchProviderException e) {
+ error = "" + e;
+ } catch (NoSuchAlgorithmException e) {
+ error = "" + e;
+ } catch (PGPException e) {
+ error = "" + e;
+ } catch (InvalidParameterException e) {
+ error = "" + e;
+ } catch (InvalidAlgorithmParameterException e) {
+ error = "" + e;
+ } catch (Apg.GeneralException e) {
+ error = "" + e;
+ }
+
+ Message message = new Message();
+ Bundle data = new Bundle();
+ data.putBoolean("closeProgressDialog", true);
+ if (error != null) {
+ data.putString(Apg.EXTRA_ERROR, error);
+ } else {
+ data.putBoolean("gotNewKey", true);
+ }
+ message.setData(data);
+ mHandler.sendMessage(message);
+ }
+}
diff --git a/org_apg/src/org/apg/ui/widget/UserIdEditor.java b/org_apg/src/org/apg/ui/widget/UserIdEditor.java
new file mode 100644
index 000000000..b154803cf
--- /dev/null
+++ b/org_apg/src/org/apg/ui/widget/UserIdEditor.java
@@ -0,0 +1,192 @@
+/*
+ * 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.apg.ui.widget;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apg.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.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.RadioButton;
+
+public class UserIdEditor extends LinearLayout implements Editor, OnClickListener {
+ private EditorListener mEditorListener = null;
+
+ private ImageButton mDeleteButton;
+ private RadioButton mIsMainUserId;
+ private EditText mName;
+ private EditText mEmail;
+ private EditText mComment;
+
+ private static final Pattern EMAIL_PATTERN =
+ Pattern.compile("^([a-zA-Z0-9_.-])+@([a-zA-Z0-9_.-])+[.]([a-zA-Z])+([a-zA-Z])+",
+ Pattern.CASE_INSENSITIVE);
+
+ public static class NoNameException extends Exception {
+ static final long serialVersionUID = 0xf812773343L;
+
+ public NoNameException(String message) {
+ super(message);
+ }
+ }
+
+ 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 = (ImageButton) 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_invalidEmail, 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/org_apg/src/org/apg/util/ApgCon.java b/org_apg/src/org/apg/util/ApgCon.java
new file mode 100644
index 000000000..73ee66ad9
--- /dev/null
+++ b/org_apg/src/org/apg/util/ApgCon.java
@@ -0,0 +1,836 @@
+/*
+ * Copyright (C) 2011 Markus Doits <markus.doits@googlemail.com>
+ *
+ * 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.apg.util;
+
+import org.apg.util.ApgConInterface.OnCallFinishListener;
+import org.apg.IApgService;
+
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.util.Log;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+
+/**
+ * A APG-AIDL-Wrapper
+ *
+ * <p>
+ * This class can be used by other projects to simplify connecting to the
+ * APG-AIDL-Service. Kind of wrapper of for AIDL.
+ * </p>
+ *
+ * <p>
+ * It is not used in this project.
+ * </p>
+ *
+ * @author Markus Doits <markus.doits@googlemail.com>
+ * @version 1.1rc1
+ *
+ */
+public class ApgCon {
+ private static final boolean LOCAL_LOGV = true;
+ private static final boolean LOCAL_LOGD = true;
+
+ private final static String TAG = "ApgCon";
+ private final static int API_VERSION = 2; // aidl api-version it expects
+ private final static String BLOB_URI = "content://org.thialfihar.android.apg.provider.apgserviceblobprovider";
+
+ /**
+ * How many seconds to wait for a connection to AGP when connecting.
+ * Being unsuccessful for this number of seconds, a connection
+ * is assumed to be failed.
+ */
+ public int secondsToWaitForConnection = 15;
+
+ private class CallAsync extends AsyncTask<String, Void, Void> {
+
+ @Override
+ protected Void doInBackground(String... arg) {
+ if( LOCAL_LOGD ) Log.d(TAG, "Async execution starting");
+ call(arg[0]);
+ return null;
+ }
+
+ protected void onPostExecute(Void res) {
+ if( LOCAL_LOGD ) Log.d(TAG, "Async execution finished");
+ mAsyncRunning = false;
+
+ }
+
+ }
+
+ private final Context mContext;
+ private final error mConnectionStatus;
+ private boolean mAsyncRunning = false;
+ private OnCallFinishListener mOnCallFinishListener;
+
+ private final Bundle mResult = new Bundle();
+ private final Bundle mArgs = new Bundle();
+ private final ArrayList<String> mErrorList = new ArrayList<String>();
+ private final ArrayList<String> mWarningList = new ArrayList<String>();
+
+ /** Remote service for decrypting and encrypting data */
+ private IApgService mApgService = null;
+
+ /** Set apgService accordingly to connection status */
+ private ServiceConnection mApgConnection = new ServiceConnection() {
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ if( LOCAL_LOGD ) Log.d(TAG, "IApgService bound to apgService");
+ mApgService = IApgService.Stub.asInterface(service);
+ }
+
+ public void onServiceDisconnected(ComponentName className) {
+ if( LOCAL_LOGD ) Log.d(TAG, "IApgService disconnected");
+ mApgService = null;
+ }
+ };
+
+ /**
+ * Different types of local errors
+ */
+ public static enum error {
+ /**
+ * no error
+ */
+ NO_ERROR,
+ /**
+ * generic error
+ */
+ GENERIC,
+ /**
+ * connection to apg service not possible
+ */
+ CANNOT_BIND_TO_APG,
+ /**
+ * function to call not provided
+ */
+ CALL_MISSING,
+ /**
+ * apg service does not know what to do
+ */
+ CALL_NOT_KNOWN,
+ /**
+ * could not find APG being installed
+ */
+ APG_NOT_FOUND,
+ /**
+ * found APG but without AIDL interface
+ */
+ APG_AIDL_MISSING,
+ /**
+ * found APG but with wrong API
+ */
+ APG_API_MISSMATCH
+ }
+
+ private static enum ret {
+ ERROR, // returned from AIDL
+ RESULT, // returned from AIDL
+ WARNINGS, // mixed AIDL and LOCAL
+ ERRORS, // mixed AIDL and LOCAL
+ }
+
+ /**
+ * Constructor
+ *
+ * <p>
+ * Creates a new ApgCon object and searches for the right APG version on
+ * initialization. If not found, errors are printed to the error log.
+ * </p>
+ *
+ * @param ctx
+ * the running context
+ */
+ public ApgCon(Context ctx) {
+ if( LOCAL_LOGV ) Log.v(TAG, "EncryptionService created");
+ mContext = ctx;
+
+ error tmpError = null;
+ try {
+ if( LOCAL_LOGV ) Log.v(TAG, "Searching for the right APG version");
+ ServiceInfo apgServices[] = ctx.getPackageManager().getPackageInfo("org.thialfihar.android.apg",
+ PackageManager.GET_SERVICES | PackageManager.GET_META_DATA).services;
+ if (apgServices == null) {
+ Log.e(TAG, "Could not fetch services");
+ tmpError = error.GENERIC;
+ } else {
+ boolean apgServiceFound = false;
+ for (ServiceInfo inf : apgServices) {
+ if( LOCAL_LOGV ) Log.v(TAG, "Found service of APG: " + inf.name);
+ if (inf.name.equals("org.thialfihar.android.apg.ApgService")) {
+ apgServiceFound = true;
+ if (inf.metaData == null) {
+ Log.w(TAG, "Could not determine ApgService API");
+ Log.w(TAG, "This probably won't work!");
+ mWarningList.add("(LOCAL) Could not determine ApgService API");
+ tmpError = error.APG_API_MISSMATCH;
+ } else if (inf.metaData.getInt("api_version") != API_VERSION) {
+ Log.w(TAG, "Found ApgService API version " + inf.metaData.getInt("api_version") + " but exspected " + API_VERSION);
+ Log.w(TAG, "This probably won't work!");
+ mWarningList.add("(LOCAL) Found ApgService API version " + inf.metaData.getInt("api_version") + " but exspected " + API_VERSION);
+ tmpError = error.APG_API_MISSMATCH;
+ } else {
+ if( LOCAL_LOGV ) Log.v(TAG, "Found api_version " + API_VERSION + ", everything should work");
+ tmpError = error.NO_ERROR;
+ }
+ }
+ }
+
+ if (!apgServiceFound) {
+ Log.e(TAG, "Could not find APG with AIDL interface, this probably won't work");
+ mErrorList.add("(LOCAL) Could not find APG with AIDL interface, this probably won't work");
+ mResult.putInt(ret.ERROR.name(), error.APG_AIDL_MISSING.ordinal());
+ tmpError = error.APG_NOT_FOUND;
+ }
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Could not find APG, is it installed?", e);
+ mErrorList.add("(LOCAL) Could not find APG, is it installed?");
+ mResult.putInt(ret.ERROR.name(), error.APG_NOT_FOUND.ordinal());
+ tmpError = error.APG_NOT_FOUND;
+ }
+
+ mConnectionStatus = tmpError;
+
+ }
+
+ /** try to connect to the apg service */
+ private boolean connect() {
+ if( LOCAL_LOGV ) Log.v(TAG, "trying to bind the apgService to context");
+
+ if (mApgService != null) {
+ if( LOCAL_LOGV ) Log.v(TAG, "allready connected");
+ return true;
+ }
+
+ try {
+ mContext.bindService(new Intent(IApgService.class.getName()), mApgConnection, Context.BIND_AUTO_CREATE);
+ } catch (Exception e) {
+ Log.e(TAG, "could not bind APG service", e);
+ return false;
+ }
+
+ int waitCount = 0;
+ while (mApgService == null && waitCount++ < secondsToWaitForConnection) {
+ if( LOCAL_LOGV ) Log.v(TAG, "sleeping 1 second to wait for apg");
+ android.os.SystemClock.sleep(1000);
+ }
+
+ if (waitCount >= secondsToWaitForConnection) {
+ if( LOCAL_LOGV ) Log.v(TAG, "slept waiting for nothing!");
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Disconnects ApgCon from Apg
+ *
+ * <p>
+ * This should be called whenever all work with APG is done (e.g. everything
+ * you wanted to encrypt is encrypted), since connections with AIDL should
+ * not be upheld indefinitely.
+ * <p>
+ *
+ * <p>
+ * Also, if you destroy you end using your ApgCon-instance, this must be
+ * called or else the connection to APG is leaked
+ * </p>
+ */
+ public void disconnect() {
+ if( LOCAL_LOGV ) Log.v(TAG, "disconnecting apgService");
+ if (mApgService != null) {
+ mContext.unbindService(mApgConnection);
+ mApgService = null;
+ }
+ }
+
+ private boolean initialize() {
+ if (mApgService == null) {
+ if (!connect()) {
+ if( LOCAL_LOGV ) Log.v(TAG, "connection to apg service failed");
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Calls a function from APG's AIDL-interface
+ *
+ * <p>
+ * After you have set up everything with {@link #setArg(String, String)}
+ * (and variants), you can call a function of the AIDL-interface. This
+ * will:
+ * <ul>
+ * <li>start connection to the remote interface (if not already connected)</li>
+ * <li>call the function passed with all parameters synchronously</li>
+ * <li>set up everything to retrieve the result and/or warnings/errors</li>
+ * <li>call the callback if provided
+ * </ul>
+ * </p>
+ *
+ * <p>
+ * Note your thread will be blocked during execution - if you want to call
+ * the function asynchronously, see {@link #callAsync(String)}.
+ * </p>
+ *
+ * @param function
+ * a remote function to call
+ * @return true, if call successful (= no errors), else false
+ *
+ * @see #callAsync(String)
+ * @see #setArg(String, String)
+ * @see #setOnCallFinishListener(OnCallFinishListener)
+ */
+ public boolean call(String function) {
+ boolean success = this.call(function, mArgs, mResult);
+ if (mOnCallFinishListener != null) {
+ try {
+ if( LOCAL_LOGD ) Log.d(TAG, "About to execute callback");
+ mOnCallFinishListener.onCallFinish(mResult);
+ if( LOCAL_LOGD ) Log.d(TAG, "Callback executed");
+ } catch (Exception e) {
+ Log.w(TAG, "Exception on callback: (" + e.getClass() + ") " + e.getMessage(), e);
+ mWarningList.add("(LOCAL) Could not execute callback (" + e.getClass() + "): " + e.getMessage());
+ }
+ }
+ return success;
+ }
+
+ /**
+ * Calls a function of remote interface asynchronously
+ *
+ * <p>
+ * This does exactly the same as {@link #call(String)}, but asynchronously.
+ * While connection to APG and work are done in background, your thread can
+ * go on executing.
+ * <p>
+ *
+ * <p>
+ * To see whether the task is finished, you have two possibilities:
+ * <ul>
+ * <li>In your thread, poll {@link #isRunning()}</li>
+ * <li>Supply a callback with {@link #setOnCallFinishListener(OnCallFinishListener)}</li>
+ * </ul>
+ * </p>
+ *
+ * @param function
+ * a remote function to call
+ *
+ * @see #call(String)
+ * @see #isRunning()
+ * @see #setOnCallFinishListener(OnCallFinishListener)
+ */
+ public void callAsync(String function) {
+ mAsyncRunning = true;
+ new CallAsync().execute(function);
+ }
+
+ private boolean call(String function, Bundle pArgs, Bundle pReturn) {
+
+ if (!initialize()) {
+ mErrorList.add("(LOCAL) Cannot bind to ApgService");
+ mResult.putInt(ret.ERROR.name(), error.CANNOT_BIND_TO_APG.ordinal());
+ return false;
+ }
+
+ if (function == null || function.length() == 0) {
+ mErrorList.add("(LOCAL) Function to call missing");
+ mResult.putInt(ret.ERROR.name(), error.CALL_MISSING.ordinal());
+ return false;
+ }
+
+ try {
+ Boolean success = (Boolean) IApgService.class.getMethod(function, Bundle.class, Bundle.class).invoke(mApgService, pArgs, pReturn);
+ mErrorList.addAll(pReturn.getStringArrayList(ret.ERRORS.name()));
+ mWarningList.addAll(pReturn.getStringArrayList(ret.WARNINGS.name()));
+ return success;
+ } catch (NoSuchMethodException e) {
+ Log.e(TAG, "Remote call not known (" + function + "): " + e.getMessage(), e);
+ mErrorList.add("(LOCAL) Remote call not known (" + function + "): " + e.getMessage());
+ mResult.putInt(ret.ERROR.name(), error.CALL_NOT_KNOWN.ordinal());
+ return false;
+ } catch (InvocationTargetException e) {
+ Throwable orig = e.getTargetException();
+ Log.w(TAG, "Exception of type '" + orig.getClass() + "' on AIDL call '" + function + "': " + orig.getMessage(), orig);
+ mErrorList.add("(LOCAL) Exception of type '" + orig.getClass() + "' on AIDL call '" + function + "': " + orig.getMessage());
+ return false;
+ } catch (Exception e) {
+ Log.e(TAG, "Generic error (" + e.getClass() + "): " + e.getMessage(), e);
+ mErrorList.add("(LOCAL) Generic error (" + e.getClass() + "): " + e.getMessage());
+ mResult.putInt(ret.ERROR.name(), error.GENERIC.ordinal());
+ return false;
+ }
+
+ }
+
+ /**
+ * Set a string argument for APG
+ *
+ * <p>
+ * This defines a string argument for APG's AIDL-interface.
+ * </p>
+ *
+ * <p>
+ * To know what key-value-pairs are possible (or required), take a look into
+ * the IApgService.aidl
+ * </p>
+ *
+ * <p>
+ * Note that parameters are not reseted after a call, so you have to
+ * reset ({@link #clearArgs()}) them manually if you want to.
+ * </p>
+ *
+ *
+ * @param key
+ * the key
+ * @param val
+ * the value
+ *
+ * @see #clearArgs()
+ */
+ public void setArg(String key, String val) {
+ mArgs.putString(key, val);
+ }
+
+ /**
+ * Set a string-array argument for APG
+ *
+ * <p>
+ * If the AIDL-parameter is an {@literal ArrayList<String>}, you have to use
+ * this function.
+ * </p>
+ *
+ * <code>
+ * <pre>
+ * setArg("a key", new String[]{ "entry 1", "entry 2" });
+ * </pre>
+ * </code>
+ *
+ * @param key
+ * the key
+ * @param vals
+ * the value
+ *
+ * @see #setArg(String, String)
+ */
+ public void setArg(String key, String vals[]) {
+ ArrayList<String> list = new ArrayList<String>();
+ for (String val : vals) {
+ list.add(val);
+ }
+ mArgs.putStringArrayList(key, list);
+ }
+
+ /**
+ * Set up a boolean argument for APG
+ *
+ * @param key
+ * the key
+ * @param vals
+ * the value
+ *
+ * @see #setArg(String, String)
+ */
+ public void setArg(String key, boolean val) {
+ mArgs.putBoolean(key, val);
+ }
+
+ /**
+ * Set up a int argument for APG
+ *
+ * @param key
+ * the key
+ * @param vals
+ * the value
+ *
+ * @see #setArg(String, String)
+ */
+ public void setArg(String key, int val) {
+ mArgs.putInt(key, val);
+ }
+
+ /**
+ * Set up a int-array argument for APG
+ * <p>
+ * If the AIDL-parameter is an {@literal ArrayList<Integer>}, you have to
+ * use this function.
+ * </p>
+ *
+ * @param key
+ * the key
+ * @param vals
+ * the value
+ *
+ * @see #setArg(String, String)
+ */
+ public void setArg(String key, int vals[]) {
+ ArrayList<Integer> list = new ArrayList<Integer>();
+ for (int val : vals) {
+ list.add(val);
+ }
+ mArgs.putIntegerArrayList(key, list);
+ }
+
+ /**
+ * Set up binary data to en/decrypt
+ *
+ * @param is
+ * InputStream to get the data from
+ */
+ public void setBlob(InputStream is) {
+ if( LOCAL_LOGD ) Log.d(TAG, "setBlob() called");
+ // 1. get the new contentUri
+ ContentResolver cr = mContext.getContentResolver();
+ Uri contentUri = cr.insert(Uri.parse(BLOB_URI), new ContentValues());
+
+ // 2. insert binary data
+ OutputStream os = null;
+ try {
+ os = cr.openOutputStream(contentUri, "w");
+ } catch( Exception e ) {
+ Log.e(TAG, "... exception on setBlob", e);
+ }
+
+ byte[] buffer = new byte[8];
+ int len = 0;
+ try {
+ while( (len = is.read(buffer)) != -1) {
+ os.write(buffer, 0, len);
+ }
+ if(LOCAL_LOGD) Log.d(TAG, "... write finished, now closing");
+ os.close();
+ } catch (Exception e) {
+ Log.e(TAG, "... error on writing buffer", e);
+ }
+
+ mArgs.putString("BLOB", contentUri.toString() );
+ }
+
+ /**
+ * Clears all arguments
+ *
+ * <p>
+ * Anything the has been set up with the various
+ * {@link #setArg(String, String)} functions is cleared.
+ * </p>
+ *
+ * <p>
+ * Note that any warning, error, callback, result, etc. is NOT cleared with
+ * this.
+ * </p>
+ *
+ * @see #reset()
+ */
+ public void clearArgs() {
+ mArgs.clear();
+ }
+
+ /**
+ * Return the object associated with the key
+ *
+ * @param key
+ * the object's key you want to return
+ * @return an object at position key, or null if not set
+ */
+ public Object getArg(String key) {
+ return mArgs.get(key);
+ }
+
+ /**
+ * Iterates through the errors
+ *
+ * <p>
+ * With this method you can iterate through all errors. The errors are only
+ * returned once and deleted immediately afterwards, so you can only return
+ * each error once.
+ * </p>
+ *
+ * @return a human readable description of a error that happened, or null if
+ * no more errors
+ *
+ * @see #hasNextError()
+ * @see #clearErrors()
+ */
+ public String getNextError() {
+ if (mErrorList.size() != 0)
+ return mErrorList.remove(0);
+ else
+ return null;
+ }
+
+ /**
+ * Check if there are any new errors
+ *
+ * @return true, if there are unreturned errors, false otherwise
+ *
+ * @see #getNextError()
+ */
+ public boolean hasNextError() {
+ return mErrorList.size() != 0;
+ }
+
+ /**
+ * Get the numeric representation of the last error
+ *
+ * <p>
+ * Values <100 mean the error happened locally, values >=100 mean the error
+ * happened at the remote side (APG). See the IApgService.aidl (or get the
+ * human readable description with {@link #getNextError()}) for what
+ * errors >=100 mean.
+ * </p>
+ *
+ * @return the id of the error that happened
+ */
+ public int getError() {
+ if (mResult.containsKey(ret.ERROR.name()))
+ return mResult.getInt(ret.ERROR.name());
+ else
+ return -1;
+ }
+
+ /**
+ * Iterates through the warnings
+ *
+ * <p>
+ * With this method you can iterate through all warnings. Warnings are
+ * only returned once and deleted immediately afterwards, so you can only
+ * return each warning once.
+ * </p>
+ *
+ * @return a human readable description of a warning that happened, or null
+ * if no more warnings
+ *
+ * @see #hasNextWarning()
+ * @see #clearWarnings()
+ */
+ public String getNextWarning() {
+ if (mWarningList.size() != 0)
+ return mWarningList.remove(0);
+ else
+ return null;
+ }
+
+ /**
+ * Check if there are any new warnings
+ *
+ * @return true, if there are unreturned warnings, false otherwise
+ *
+ * @see #getNextWarning()
+ */
+ public boolean hasNextWarning() {
+ return mWarningList.size() != 0;
+ }
+
+ /**
+ * Get the result
+ *
+ * <p>
+ * This gets your result. After doing an encryption or decryption with APG,
+ * you get the output with this function.
+ * </p>
+ *
+ * <p>
+ * Note when your last remote call is unsuccessful, the result will
+ * still have the same value like the last successful call (or null, if no
+ * call was successful). To ensure you do not work with old call's results,
+ * either be sure to {@link #reset()} (or at least {@link #clearResult()})
+ * your instance before each new call or always check that
+ * {@link #hasNextError()} is false.
+ * </p>
+ *
+ * <p>
+ * Note: When handling binary data with {@link #setBlob(InputStream)}, you
+ * get your result with {@link #getBlobResult()}.
+ * </p>
+ *
+ * @return the mResult of the last {@link #call(String)} or
+ * {@link #callAsync(String)}.
+ *
+ * @see #reset()
+ * @see #clearResult()
+ * @see #getResultBundle()
+ * @see #getBlobResult()
+ */
+ public String getResult() {
+ return mResult.getString(ret.RESULT.name());
+ }
+
+ /**
+ * Get the binary result
+ *
+ * <p>
+ * This gets your binary result. It only works if you called {@link #setBlob(InputStream)} before.
+ *
+ * If you did not call encrypt nor decrypt, this will be the same data as you inputed.
+ * </p>
+ *
+ * @return InputStream of the binary data which was en/decrypted
+ *
+ * @see #setBlob(InputStream)
+ * @see #getResult()
+ */
+ public InputStream getBlobResult() {
+ if(mArgs.containsKey("BLOB")) {
+ ContentResolver cr = mContext.getContentResolver();
+ InputStream in = null;
+ try {
+ in = cr.openInputStream(Uri.parse(mArgs.getString("BLOB")));
+ } catch( Exception e ) {
+ Log.e(TAG, "Could not return blob in result", e);
+ }
+ return in;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Get the result bundle
+ *
+ * <p>
+ * Unlike {@link #getResult()}, which only returns any en-/decrypted
+ * message, this function returns the complete information that was returned
+ * by Apg. This also includes the "RESULT", but additionally the warnings,
+ * errors and any other information.
+ * </p>
+ * <p>
+ * For warnings and errors it is suggested to use the functions that are
+ * provided here, namely {@link #getError()}, {@link #getNextError()},
+ * {@link #get_next_Warning()} etc.), but if any call returns something non
+ * standard, you have access to the complete result bundle to extract the
+ * information.
+ * </p>
+ *
+ * @return the complete result bundle of the last call to apg
+ */
+ public Bundle getResultBundle() {
+ return mResult;
+ }
+
+ public error getConnectionStatus() {
+ return mConnectionStatus;
+ }
+
+ /**
+ * Clears all unfetched errors
+ *
+ * @see #getNextError()
+ * @see #hasNextError()
+ */
+ public void clearErrors() {
+ mErrorList.clear();
+ mResult.remove(ret.ERROR.name());
+ }
+
+ /**
+ * Clears all unfetched warnings
+ *
+ * @see #getNextWarning()
+ * @see #hasNextWarning()
+ */
+ public void clearWarnings() {
+ mWarningList.clear();
+ }
+
+ /**
+ * Clears the last mResult
+ *
+ * @see #getResult()
+ */
+ public void clearResult() {
+ mResult.remove(ret.RESULT.name());
+ }
+
+ /**
+ * Set a callback listener when call to AIDL finishes
+ *
+ * @param obj
+ * a object to call back after async execution
+ * @see ApgConInterface
+ */
+ public void setOnCallFinishListener(OnCallFinishListener lis) {
+ mOnCallFinishListener = lis;
+ }
+
+ /**
+ * Clears any callback object
+ *
+ * @see #setOnCallFinishListener(OnCallFinishListener)
+ */
+ public void clearOnCallFinishListener() {
+ mOnCallFinishListener = null;
+ }
+
+ /**
+ * Checks if an async execution is running
+ *
+ * <p>
+ * If you started something with {@link #callAsync(String)}, this will
+ * return true if the task is still running
+ * </p>
+ *
+ * @return true, if an async task is still running, false otherwise
+ *
+ * @see #callAsync(String)
+ *
+ */
+ public boolean isRunning() {
+ return mAsyncRunning;
+ }
+
+ /**
+ * Completely resets your instance
+ *
+ * <p>
+ * This currently resets everything in this instance. Errors, warnings,
+ * results, callbacks, ... are removed. Any connection to the remote
+ * interface is upheld, though.
+ * </p>
+ *
+ * <p>
+ * Note when an async execution ({@link #callAsync(String)}) is
+ * running, it's result, warnings etc. will still be evaluated (which might
+ * be not what you want). Also mind that any callback you set is also
+ * reseted, so when finishing the execution any before defined callback will
+ * NOT BE TRIGGERED.
+ * </p>
+ */
+ public void reset() {
+ clearErrors();
+ clearWarnings();
+ clearArgs();
+ clearOnCallFinishListener();
+ mResult.clear();
+ }
+
+}
diff --git a/org_apg/src/org/apg/util/ApgConInterface.java b/org_apg/src/org/apg/util/ApgConInterface.java
new file mode 100644
index 000000000..2b66164f1
--- /dev/null
+++ b/org_apg/src/org/apg/util/ApgConInterface.java
@@ -0,0 +1,7 @@
+package org.apg.util;
+
+public interface ApgConInterface {
+ public static interface OnCallFinishListener {
+ public abstract void onCallFinish(android.os.Bundle result);
+ }
+}
diff --git a/org_apg/src/org/apg/util/Choice.java b/org_apg/src/org/apg/util/Choice.java
new file mode 100644
index 000000000..1dbd9b215
--- /dev/null
+++ b/org_apg/src/org/apg/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.apg.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/org_apg/src/org/apg/util/Compatibility.java b/org_apg/src/org/apg/util/Compatibility.java
new file mode 100644
index 000000000..10b3c7f6b
--- /dev/null
+++ b/org_apg/src/org/apg/util/Compatibility.java
@@ -0,0 +1,79 @@
+package org.apg.util;
+
+import java.lang.reflect.Method;
+
+import android.content.Context;
+import android.util.Log;
+
+public class Compatibility {
+
+ private static final String clipboardLabel = "APG";
+
+ /**
+ * Wrapper around ClipboardManager based on Android version using Reflection API, from
+ * http://www.projectsexception.com/blog/?p=87
+ *
+ * @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 method = clipboard.getClass().getMethod("setText", CharSequence.class);
+ method.invoke(clipboard, text);
+ } else if ("android.content.ClipboardManager".equals(clipboard.getClass().getName())) {
+ Class<?> clazz = Class.forName("android.content.ClipData");
+ Method method = clazz.getMethod("newPlainText", CharSequence.class,
+ CharSequence.class);
+ Object clip = method.invoke(null, clipboardLabel, text);
+ method = clipboard.getClass().getMethod("setPrimaryClip", clazz);
+ method.invoke(clipboard, clip);
+ }
+ } catch (Exception e) {
+ Log.e("ProjectsException", "There was and 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 method = clipboard.getClass().getMethod("getText");
+ Object text = method.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", Integer.TYPE);
+ 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 and error getting the text from the clipboard: "
+ + e.getMessage());
+
+ return null;
+ }
+ }
+}
diff --git a/org_apg/src/org/apg/util/IterableIterator.java b/org_apg/src/org/apg/util/IterableIterator.java
new file mode 100644
index 000000000..be6632ac0
--- /dev/null
+++ b/org_apg/src/org/apg/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.apg.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;
+ }
+}