branching trunk out of latest 1.0.x to get a clean start for it

This commit is contained in:
Thialfihar 2010-06-05 00:33:33 +00:00
commit a85ae5e009
41 changed files with 2837 additions and 2592 deletions

View File

@ -17,7 +17,7 @@
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="org.thialfihar.android.apg"
android:versionName="0.9.5" android:versionCode="10">
android:versionCode="15" android:versionName="1.0.1">
<application
android:icon="@drawable/icon"
@ -53,12 +53,28 @@
<activity
android:name=".SelectPublicKeyListActivity"
android:label="@string/title_selectRecipients"
android:configChanges="keyboardHidden|orientation|keyboard"/>
android:configChanges="keyboardHidden|orientation|keyboard">
<intent-filter>
<action android:name="org.thialfihar.android.apg.intent.SELECT_PUBLIC_KEYS" />
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="text/*"/>
</intent-filter>
</activity>
<activity
android:name=".SelectSecretKeyListActivity"
android:label="@string/title_selectSignature"
android:configChanges="keyboardHidden|orientation|keyboard"/>
android:configChanges="keyboardHidden|orientation|keyboard">
<intent-filter>
<action android:name="org.thialfihar.android.apg.intent.SELECT_SECRET_KEY" />
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="text/*"/>
</intent-filter>
</activity>
<activity
android:name=".EncryptActivity"
@ -68,6 +84,9 @@
<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" />
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="text/*"/>
</intent-filter>
</activity>
@ -92,6 +111,9 @@
<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="text/*"/>
</intent-filter>
</activity>
@ -106,13 +128,22 @@
android:label="@string/title_preferences"
android:configChanges="keyboardHidden|orientation|keyboard"/>
<service android:name=".Service" />
<provider
android:readPermission="org.thialfihar.android.apg.permission.READ_KEY_DETAILS"
android:name="org.thialfihar.android.apg.provider.DataProvider"
android:authorities="org.thialfihar.android.apg.provider" />
android:authorities="org.thialfihar.android.apg.provider"/>
</application>
<uses-sdk android:minSdkVersion="3" android:targetSdkVersion="5" />
<uses-permission android:name="com.google.android.providers.gmail.permission.READ_GMAIL" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
<permission android:name="org.thialfihar.android.apg.permission.READ_KEY_DETAILS"
android:protectionLevel="dangerous"
android:label="@string/permission_read_key_details_label"
android:description="@string/permission_read_key_details_description"/>
<uses-permission android:name="com.google.android.providers.gmail.permission.READ_GMAIL" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
</manifest>

View File

@ -77,6 +77,7 @@
<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"/>

View File

@ -69,20 +69,6 @@
android:layout_width="wrap_content"
android:layout_height="fill_parent"/>
<TextView
android:id="@+id/creation"
android:textAppearance="?android:attr/textAppearanceSmall"
android:text="31.12.2009"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/expiry"
android:textAppearance="?android:attr/textAppearanceSmall"
android:text="31.12.2010"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/status"
android:textAppearance="?android:attr/textAppearanceSmall"

View File

@ -62,20 +62,6 @@
android:layout_width="wrap_content"
android:layout_height="fill_parent"/>
<TextView
android:id="@+id/creation"
android:textAppearance="?android:attr/textAppearanceSmall"
android:text="31.12.2009"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/expiry"
android:textAppearance="?android:attr/textAppearanceSmall"
android:text="31.12.2010"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/status"
android:textAppearance="?android:attr/textAppearanceSmall"

View File

@ -38,10 +38,12 @@
<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>
<!-- 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>
<!-- btn_lowerCase: capitalized words, no punctuation -->
@ -92,6 +94,9 @@
<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="noKeysSelected">Select</string>
<string name="oneKeySelected">1 Selected</string>
@ -108,9 +113,16 @@
<string name="notValid">not valid</string>
<!-- choice_lowerCase: capitalized firwst 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_untilQuit">until quit</string>
<string name="dsa">DSA</string>
<string name="elgamal">ElGamal</string>
@ -133,7 +145,7 @@
<string name="usingClipboardContent">Using clipboard content.</string>
<string name="keySaved">Key saved.</string>
<string name="setAPassPhrase">Set a pass phrase via the option menu first.</string>
<string name="oiFilemanagerNotInstalled">OI File Manager not installed.</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">Pass phrase for symmetric encryption:</string>
@ -150,19 +162,20 @@
<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 from.</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">Succssfully added %s keys and updated %s keys."</string>
<string name="keysAdded">Succssfully added %s keys.</string>
<string name="keysUpdated">Succssfully updated %s keys.</string>
<string name="keysAddedAndUpdated">Succssfully added %s key(s) and updated %s key(s)."</string>
<string name="keysAdded">Succssfully added %s key(s).</string>
<string name="keysUpdated">Succssfully updated %s key(s).</string>
<string name="noKeysAddedOrUpdated">No keys added or updated.</string>
<string name="keyExported">Succssfully exported 1 key.</string>
<string name="keysExported">Succssfully 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>
<!-- error_lowerCase: phrases, no punctuation, all lowercase,
they will be put after "errorMessage", e.g. "Error: file not found" -->
@ -227,4 +240,3 @@
<string name="progress_verifyingIntegrity">verifying integrity...</string>
</resources>

View File

@ -38,10 +38,12 @@
<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>
<!-- 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>
<!-- btn_lowerCase: capitalized words, no punctuation -->
@ -92,6 +94,9 @@
<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="noKeysSelected">Select</string>
<string name="oneKeySelected">1 Selected</string>
@ -108,9 +113,16 @@
<string name="notValid">not valid</string>
<!-- choice_lowerCase: capitalized firwst 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_untilQuit">until quit</string>
<string name="dsa">DSA</string>
<string name="elgamal">ElGamal</string>
@ -133,7 +145,7 @@
<string name="usingClipboardContent">Using clipboard content.</string>
<string name="keySaved">Key saved.</string>
<string name="setAPassPhrase">Set a pass phrase via the option menu first.</string>
<string name="oiFilemanagerNotInstalled">OI File Manager not installed.</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">Pass phrase for symmetric encryption:</string>
@ -150,19 +162,20 @@
<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 from.</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">Succssfully added %s keys and updated %s keys."</string>
<string name="keysAdded">Succssfully added %s keys.</string>
<string name="keysUpdated">Succssfully updated %s keys.</string>
<string name="keysAddedAndUpdated">Succssfully added %s key(s) and updated %s key(s)."</string>
<string name="keysAdded">Succssfully added %s key(s).</string>
<string name="keysUpdated">Succssfully updated %s key(s).</string>
<string name="noKeysAddedOrUpdated">No keys added or updated.</string>
<string name="keyExported">Succssfully exported 1 key.</string>
<string name="keysExported">Succssfully 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>
<!-- error_lowerCase: phrases, no punctuation, all lowercase,
they will be put after "errorMessage", e.g. "Error: file not found" -->
@ -227,4 +240,3 @@
<string name="progress_verifyingIntegrity">verifying integrity...</string>
</resources>

View File

@ -38,10 +38,12 @@
<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>
<!-- 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>
<!-- btn_lowerCase: capitalized words, no punctuation -->
@ -92,6 +94,9 @@
<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="noKeysSelected">Select</string>
<string name="oneKeySelected">1 Selected</string>
@ -108,9 +113,16 @@
<string name="notValid">not valid</string>
<!-- choice_lowerCase: capitalized firwst 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_untilQuit">until quit</string>
<string name="dsa">DSA</string>
<string name="elgamal">ElGamal</string>
@ -133,7 +145,7 @@
<string name="usingClipboardContent">Using clipboard content.</string>
<string name="keySaved">Key saved.</string>
<string name="setAPassPhrase">Set a pass phrase via the option menu first.</string>
<string name="oiFilemanagerNotInstalled">OI File Manager not installed.</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">Pass phrase for symmetric encryption:</string>
@ -150,19 +162,20 @@
<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 from.</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">Succssfully added %s keys and updated %s keys."</string>
<string name="keysAdded">Succssfully added %s keys.</string>
<string name="keysUpdated">Succssfully updated %s keys.</string>
<string name="keysAddedAndUpdated">Succssfully added %s key(s) and updated %s key(s)."</string>
<string name="keysAdded">Succssfully added %s key(s).</string>
<string name="keysUpdated">Succssfully updated %s key(s).</string>
<string name="noKeysAddedOrUpdated">No keys added or updated.</string>
<string name="keyExported">Succssfully exported 1 key.</string>
<string name="keysExported">Succssfully 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>
<!-- error_lowerCase: phrases, no punctuation, all lowercase,
they will be put after "errorMessage", e.g. "Error: file not found" -->
@ -227,4 +240,3 @@
<string name="progress_verifyingIntegrity">verifying integrity...</string>
</resources>

242
res/values-sl/strings.xml Normal file
View File

@ -0,0 +1,242 @@
<?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 name="app_name">APG</string>
<!-- 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_authentification">Avtentikacija</string>
<string name="title_createKey">Ustvari ključ</string>
<string name="title_editKey">Uredi ključ</string>
<string name="title_preferences">Nastavitve</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>
<!-- 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>
<!-- btn_lowerCase: capitalized words, no punctuation -->
<string name="btn_encryptToClipboard">Šifriraj v odložišče</string>
<string name="btn_send">Šifriraj in pošlji</string>
<string name="btn_encrypt">Šifriraj</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>
<!-- 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_changePassPhrase">Spremeni geslo</string>
<string name="menu_setPassPhrase">Določi geslo</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>
<!-- 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_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="noKeysSelected">Izberi</string>
<string name="oneKeySelected">1 izbran</string>
<string name="nKeysSelected">Izbrani</string>
<string name="unknownUserId">&lt;nepoznan&gt;</string>
<string name="none">&lt;brez&gt;</string>
<string name="noKey">&lt;brez ključa&gt;</string>
<string name="noDate">-</string>
<string name="noExpiry">&lt;nikoli&gt;</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>
<!-- choice_lowerCase: capitalized firwst 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_untilQuit">do izhoda</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 preko menija možnosti 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">Geslo za simetrično enkripcijo:</string>
<string name="passPhraseFor">Geslo za %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: %s. Uspešno posodobljeni ključi: %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>
<!-- 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_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>
</resources>

View File

@ -38,6 +38,7 @@
<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>
<!-- section_lowerCase: capitalized words, no punctuation -->
<string name="section_userIds">User IDs</string>
@ -144,7 +145,7 @@
<string name="usingClipboardContent">Using clipboard content.</string>
<string name="keySaved">Key saved.</string>
<string name="setAPassPhrase">Set a pass phrase via the option menu first.</string>
<string name="oiFilemanagerNotInstalled">OI File Manager not installed.</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">Pass phrase for symmetric encryption:</string>
@ -161,19 +162,20 @@
<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 from.</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">Succssfully added %s keys and updated %s keys."</string>
<string name="keysAdded">Succssfully added %s keys.</string>
<string name="keysUpdated">Succssfully updated %s keys.</string>
<string name="keysAddedAndUpdated">Succssfully added %s key(s) and updated %s key(s)."</string>
<string name="keysAdded">Succssfully added %s key(s).</string>
<string name="keysUpdated">Succssfully updated %s key(s).</string>
<string name="noKeysAddedOrUpdated">No keys added or updated.</string>
<string name="keyExported">Succssfully exported 1 key.</string>
<string name="keysExported">Succssfully 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>
<!-- error_lowerCase: phrases, no punctuation, all lowercase,
they will be put after "errorMessage", e.g. "Error: file not found" -->
@ -237,5 +239,9 @@
<string name="progress_decompressingData">decompressing data...</string>
<string name="progress_verifyingIntegrity">verifying integrity...</string>
<!-- permission strings -->
<string name="permission_read_key_details_label">Read key details from APG.</string>
<string name="permission_read_key_details_description">Read of public and secret keys stored in APG, such as key ID and user IDs. The keys themselves can NOT be read.</string>
</resources>

View File

@ -1,78 +0,0 @@
/*
* Copyright (C) 2008 OpenIntents.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.openintents.intents;
// Version Dec 9, 2008
/**
* Provides OpenIntents actions, extras, and categories used by providers.
* <p>
* These specifiers extend the standard Android specifiers.
* </p>
*/
public final class FileManager {
/**
* Activity Action: Pick a file through the file manager, or let user
* specify a custom file name. Data is the current file name or file name
* suggestion. Returns a new file name as file URI in data.
*
* <p>
* Constant Value: "org.openintents.action.PICK_FILE"
* </p>
*/
public static final String ACTION_PICK_FILE = "org.openintents.action.PICK_FILE";
/**
* Activity Action: Pick a directory through the file manager, or let user
* specify a custom file name. Data is the current directory name or
* directory name suggestion. Returns a new directory name as file URI in
* data.
*
* <p>
* Constant Value: "org.openintents.action.PICK_DIRECTORY"
* </p>
*/
public static final String ACTION_PICK_DIRECTORY = "org.openintents.action.PICK_DIRECTORY";
/**
* The title to display.
*
* <p>
* This is shown in the title bar of the file manager.
* </p>
*
* <p>
* Constant Value: "org.openintents.extra.TITLE"
* </p>
*/
public static final String EXTRA_TITLE = "org.openintents.extra.TITLE";
/**
* The text on the button to display.
*
* <p>
* Depending on the use, it makes sense to set this to "Open" or "Save".
* </p>
*
* <p>
* Constant Value: "org.openintents.extra.BUTTON_TEXT"
* </p>
*/
public static final String EXTRA_BUTTON_TEXT = "org.openintents.extra.BUTTON_TEXT";
}

File diff suppressed because it is too large Load Diff

View File

@ -25,6 +25,7 @@ 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;
@ -42,14 +43,24 @@ public class AskForSecretKeyPassPhrase {
alert.setTitle(R.string.title_authentification);
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.findSecretKeyRing(secretKeyId));
secretKey = Apg.getMasterKey(Apg.getSecretKeyRing(secretKeyId));
if (secretKey == null) {
return null;
alert.setTitle(R.string.title_keyNotFound);
alert.setMessage(context.getString(R.string.keyNotFound, secretKeyId));
alert.setPositiveButton(android.R.string.ok, new OnClickListener() {
@Override
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));
@ -65,30 +76,29 @@ public class AskForSecretKeyPassPhrase {
alert.setView(view);
final PassPhraseCallbackInterface cb = callback;
final Activity activity = context;
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 {
secretKey.extractPrivateKey(passPhrase.toCharArray(),
new BouncyCastleProvider());
} 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);
}
});
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 {
secretKey.extractPrivateKey(passPhrase.toCharArray(),
new BouncyCastleProvider());
} 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() {

View File

@ -17,10 +17,7 @@
package org.thialfihar.android.apg;
import java.io.File;
import java.util.Timer;
import java.util.TimerTask;
import org.bouncycastle2.bcpg.CompressionAlgorithmTags;
import org.bouncycastle2.bcpg.HashAlgorithmTags;
import org.bouncycastle2.openpgp.PGPEncryptedData;
@ -33,6 +30,7 @@ import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.view.LayoutInflater;
@ -53,8 +51,6 @@ public class BaseActivity extends Activity
private String mDeleteFile = null;
protected static SharedPreferences mPreferences = null;
private static Timer mCacheTimer = new Timer();
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
@ -66,33 +62,23 @@ public class BaseActivity extends Activity
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Apg.initialize(this);
if (mPreferences == null) {
mPreferences = getPreferences(MODE_PRIVATE);
}
Apg.initialize(this);
if (mCacheTimer == null) {
setPassPhraseCacheTimer();
}
}
private void setPassPhraseCacheTimer() {
if (mCacheTimer != null) {
mCacheTimer.cancel();
mCacheTimer = null;
}
int ttl = getPassPhraseCacheTtl();
if (ttl == 0) {
// no timer needed
return;
}
// 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 int the cache is 1.5 * ttl
mCacheTimer = new Timer();
mCacheTimer.scheduleAtFixedRate(new TimerTask() {
public void run() {
Apg.cleanUpCache(BaseActivity.this.getPassPhraseCacheTtl());
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
}
}, 0, ttl * 1000 / 2);
}
Intent intent = new Intent(this, Service.class);
intent.putExtra(Service.EXTRA_TTL, getPassPhraseCacheTtl());
startService(intent);
}
@Override
@ -282,7 +268,7 @@ public class BaseActivity extends Activity
case Id.request.secret_keys: {
if (resultCode == RESULT_OK) {
Bundle bundle = data.getExtras();
setSecretKeyId(bundle.getLong("selectedKeyId"));
setSecretKeyId(bundle.getLong(Apg.EXTRA_KEY_ID));
} else {
setSecretKeyId(Id.key.none);
}
@ -304,9 +290,9 @@ public class BaseActivity extends Activity
public void setProgress(int progress, int max) {
Message msg = new Message();
Bundle data = new Bundle();
data.putInt("type", Id.message.progress_update);
data.putInt("progress", progress);
data.putInt("max", max);
data.putInt(Apg.EXTRA_STATUS, Id.message.progress_update);
data.putInt(Apg.EXTRA_PROGRESS, progress);
data.putInt(Apg.EXTRA_MAX, max);
msg.setData(data);
mHandler.sendMessage(msg);
}
@ -314,10 +300,10 @@ public class BaseActivity extends Activity
public void setProgress(String message, int progress, int max) {
Message msg = new Message();
Bundle data = new Bundle();
data.putInt("type", Id.message.progress_update);
data.putString("message", message);
data.putInt("progress", progress);
data.putInt("max", max);
data.putInt(Apg.EXTRA_STATUS, Id.message.progress_update);
data.putString(Apg.EXTRA_MESSAGE, message);
data.putInt(Apg.EXTRA_PROGRESS, progress);
data.putInt(Apg.EXTRA_MAX, max);
msg.setData(data);
mHandler.sendMessage(msg);
}
@ -328,16 +314,16 @@ public class BaseActivity extends Activity
return;
}
int type = data.getInt("type");
int type = data.getInt(Apg.EXTRA_STATUS);
switch (type) {
case Id.message.progress_update: {
String message = data.getString("message");
String message = data.getString(Apg.EXTRA_MESSAGE);
if (mProgressDialog != null) {
if (message != null) {
mProgressDialog.setMessage(message);
}
mProgressDialog.setMax(data.getInt("max"));
mProgressDialog.setProgress(data.getInt("progress"));
mProgressDialog.setMax(data.getInt(Apg.EXTRA_MAX));
mProgressDialog.setProgress(data.getInt(Apg.EXTRA_PROGRESS));
}
break;
}
@ -382,7 +368,14 @@ public class BaseActivity extends Activity
}
public int getPassPhraseCacheTtl() {
return mPreferences.getInt(Constants.pref.pass_phrase_cache_ttl, 300);
int ttl = mPreferences.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;
setPassPhraseCacheTtl(ttl);
}
return ttl;
}
public void setPassPhraseCacheTtl(int value) {
@ -390,7 +383,9 @@ public class BaseActivity extends Activity
editor.putInt(Constants.pref.pass_phrase_cache_ttl, value);
editor.commit();
setPassPhraseCacheTimer();
Intent intent = new Intent(this, Service.class);
intent.putExtra(Service.EXTRA_TTL, value);
startService(intent);
}
public int getDefaultEncryptionAlgorithm() {
@ -417,7 +412,7 @@ public class BaseActivity extends Activity
public int getDefaultMessageCompression() {
return mPreferences.getInt(Constants.pref.default_message_compression,
CompressionAlgorithmTags.ZLIB);
Id.choice.compression.zlib);
}
public void setDefaultMessageCompression(int value) {
@ -428,7 +423,7 @@ public class BaseActivity extends Activity
public int getDefaultFileCompression() {
return mPreferences.getInt(Constants.pref.default_file_compression,
CompressionAlgorithmTags.ZLIB);
Id.choice.compression.none);
}
public void setDefaultFileCompression(int value) {

View File

@ -10,6 +10,12 @@ public class CachedPassPhrase {
this.passPhrase = passPhrase;
}
public int hashCode() {
int hc1 = (int)(this.timestamp & 0xffffffff);
int hc2 = (this.passPhrase == null ? 0 : this.passPhrase.hashCode());
return (hc1 + hc2) * hc2 + hc1;
}
public boolean equals(Object other) {
if (!(other instanceof CachedPassPhrase)) {
return false;

View File

@ -31,8 +31,6 @@ import java.util.regex.Matcher;
import org.bouncycastle2.jce.provider.BouncyCastleProvider;
import org.bouncycastle2.openpgp.PGPException;
import org.bouncycastle2.util.Strings;
import org.openintents.intents.FileManager;
import android.app.Dialog;
import android.content.ActivityNotFoundException;
@ -57,6 +55,9 @@ import android.widget.ViewFlipper;
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;
@ -158,9 +159,9 @@ public class DecryptActivity extends BaseActivity {
mSource.showNext();
}
Intent intent = getIntent();
if (intent.getAction() != null && intent.getAction().equals(Intent.ACTION_VIEW)) {
Uri uri = intent.getData();
mIntent = getIntent();
if (Intent.ACTION_VIEW.equals(mIntent.getAction())) {
Uri uri = mIntent.getData();
try {
InputStream attachment = getContentResolver().openInputStream(uri);
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
@ -170,15 +171,15 @@ public class DecryptActivity extends BaseActivity {
byteOut.write(bytes, 0, length);
}
byteOut.close();
String data = Strings.fromUTF8ByteArray(byteOut.toByteArray());
String data = new String(byteOut.toByteArray());
mMessage.setText(data);
} catch (FileNotFoundException e) {
// ignore, then
} catch (IOException e) {
// ignore, then
}
} else if (intent.getAction() != null && intent.getAction().equals(Intent.ACTION_SEND)) {
Bundle extras = intent.getExtras();
} else if (Intent.ACTION_SEND.equals(mIntent.getAction())) {
Bundle extras = mIntent.getExtras();
if (extras == null) {
extras = new Bundle();
}
@ -187,15 +188,15 @@ public class DecryptActivity extends BaseActivity {
mMessage.setText(data);
}
mSubject = extras.getString(Intent.EXTRA_SUBJECT);
if (mSubject.startsWith("Fwd: ")) {
if (mSubject != null && mSubject.startsWith("Fwd: ")) {
mSubject = mSubject.substring(5);
}
} else if (intent.getAction() != null && intent.getAction().equals(Apg.Intent.DECRYPT)) {
Bundle extras = intent.getExtras();
} else if (Apg.Intent.DECRYPT.equals(mIntent.getAction())) {
Bundle extras = mIntent.getExtras();
if (extras == null) {
extras = new Bundle();
}
String data = extras.getString("data");
String data = extras.getString(Apg.EXTRA_DATA);
if (data != null) {
Matcher matcher = Apg.PGP_MESSAGE.matcher(data);
if (matcher.matches()) {
@ -214,14 +215,39 @@ public class DecryptActivity extends BaseActivity {
}
}
}
mReplyTo = extras.getString("replyTo");
mSubject = extras.getString("subject");
} else if (intent.getAction() != null && intent.getAction().equals(Apg.Intent.DECRYPT_FILE)) {
mReplyTo = extras.getString(Apg.EXTRA_REPLY_TO);
mSubject = extras.getString(Apg.EXTRA_SUBJECT);
} else if (Apg.Intent.DECRYPT_FILE.equals(mIntent.getAction())) {
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())) {
Bundle extras = mIntent.getExtras();
if (extras == null) {
extras = new Bundle();
}
String data = extras.getString(Apg.EXTRA_DATA);
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 &&
@ -256,29 +282,41 @@ public class DecryptActivity extends BaseActivity {
});
mReplyButton.setVisibility(View.INVISIBLE);
if (mSource.getCurrentView().getId() == R.id.sourceMessage &&
mMessage.getText().length() > 0) {
mDecryptButton.performClick();
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) {
mDecryptButton.performClick();
}
}
private void openFile() {
String filename = mFilename.getText().toString();
Intent intent = new Intent(FileManager.ACTION_PICK_FILE);
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setData(Uri.parse("file://" + filename));
intent.putExtra(FileManager.EXTRA_TITLE, getString(R.string.filemanager_titleDecrypt));
intent.putExtra(FileManager.EXTRA_BUTTON_TEXT, R.string.filemanager_btnOpen);
intent.setType("*/*");
try {
startActivityForResult(intent, Id.request.filename);
} catch (ActivityNotFoundException e) {
// No compatible file manager was found.
Toast.makeText(this, R.string.oiFilemanagerNotInstalled, Toast.LENGTH_SHORT).show();
Toast.makeText(this, R.string.noFilemanagerInstalled, Toast.LENGTH_SHORT).show();
}
}
@ -373,9 +411,9 @@ public class DecryptActivity extends BaseActivity {
// look at the file/message again to check whether there's
// symmetric encryption data in there
if (mDecryptTarget == Id.target.file) {
((FileInputStream) in).reset();
in = new FileInputStream(mInputFilename);
} else {
((ByteArrayInputStream) in).reset();
in = new ByteArrayInputStream(mMessage.getText().toString().getBytes());
}
if (!Apg.hasSymmetricEncryption(this, in)) {
throw new Apg.GeneralException(getString(R.string.error_noKnownEncryptionFound));
@ -396,9 +434,9 @@ public class DecryptActivity extends BaseActivity {
} catch (FileNotFoundException e) {
error = getString(R.string.error_fileNotFound);
} catch (IOException e) {
error = e.getLocalizedMessage();
error = "" + e;
} catch (Apg.GeneralException e) {
error = e.getLocalizedMessage();
error = "" + e;
}
if (error != null) {
Toast.makeText(this, getString(R.string.errorMessage, error),
@ -412,12 +450,11 @@ public class DecryptActivity extends BaseActivity {
String data = mMessage.getText().toString();
data = data.replaceAll("(?m)^", "> ");
data = "\n\n" + data;
intent.putExtra("data", data);
intent.putExtra("subject", "Re: " + mSubject);
intent.putExtra("sendTo", mReplyTo);
intent.putExtra("eyId", mSignatureKeyId);
intent.putExtra("signatureKeyId", getSecretKeyId());
intent.putExtra("encryptionKeyIds", new long[] { mSignatureKeyId });
intent.putExtra(Apg.EXTRA_DATA, 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);
}
@ -474,25 +511,23 @@ public class DecryptActivity extends BaseActivity {
out.close();
if (mDecryptTarget == Id.target.message) {
data.putString("decryptedMessage",
Strings.fromUTF8ByteArray(((ByteArrayOutputStream)
out).toByteArray()));
data.putByteArray(Apg.EXTRA_DECRYPTED_MESSAGE,
((ByteArrayOutputStream) out).toByteArray());
}
} catch (PGPException e) {
error = e.getMessage();
error = "" + e;
} catch (IOException e) {
error = e.getMessage();
error = "" + e;
} catch (SignatureException e) {
error = e.getMessage();
e.printStackTrace();
error = "" + e;
} catch (Apg.GeneralException e) {
error = e.getMessage();
error = "" + e;
}
data.putInt("type", Id.message.done);
data.putInt(Apg.EXTRA_STATUS, Id.message.done);
if (error != null) {
data.putString("error", error);
data.putString(Apg.EXTRA_ERROR, error);
}
msg.setData(data);
@ -509,20 +544,20 @@ public class DecryptActivity extends BaseActivity {
mSignatureLayout.setVisibility(View.GONE);
mReplyButton.setVisibility(View.INVISIBLE);
String error = data.getString("error");
String error = data.getString(Apg.EXTRA_ERROR);
if (error != null) {
Toast.makeText(DecryptActivity.this,
getString(R.string.errorMessage,
data.getString("error")),
Toast.LENGTH_SHORT).show();
getString(R.string.errorMessage, error), Toast.LENGTH_SHORT).show();
return;
}
Toast.makeText(this, R.string.decryptionSuccessful, Toast.LENGTH_SHORT).show();
switch (mDecryptTarget) {
case Id.target.message: {
String decryptedMessage = data.getString("decryptedMessage");
String decryptedMessage =
new String(data.getByteArray(Apg.EXTRA_DECRYPTED_MESSAGE));
mMessage.setText(decryptedMessage);
mMessage.setHorizontallyScrolling(false);
mReplyButton.setVisibility(View.VISIBLE);
break;
}
@ -541,9 +576,9 @@ public class DecryptActivity extends BaseActivity {
}
}
if (data.getBoolean("signature")) {
String userId = data.getString("signatureUserId");
mSignatureKeyId = data.getLong("signatureKeyId");
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: " + Long.toHexString(mSignatureKeyId & 0xffffffffL));
if (userId == null) {
userId = getResources().getString(R.string.unknownUserId);
@ -555,15 +590,22 @@ public class DecryptActivity extends BaseActivity {
}
mUserId.setText(userId);
if (data.getBoolean("signatureSuccess")) {
if (data.getBoolean(Apg.EXTRA_SIGNATURE_SUCCESS)) {
mSignatureStatusImage.setImageResource(R.drawable.overlay_ok);
} else if (data.getBoolean("signatureUnknown")) {
} else if (data.getBoolean(Apg.EXTRA_SIGNATURE_UNKNOWN)) {
mSignatureStatusImage.setImageResource(R.drawable.overlay_error);
} else {
mSignatureStatusImage.setImageResource(R.drawable.overlay_error);
}
mSignatureLayout.setVisibility(View.VISIBLE);
}
if (mReturnResult) {
Intent intent = new Intent();
intent.putExtras(data);
setResult(RESULT_OK, intent);
finish();
}
}
@Override

View File

@ -16,6 +16,7 @@
package org.thialfihar.android.apg;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SignatureException;
@ -24,6 +25,7 @@ import java.util.Vector;
import org.bouncycastle2.openpgp.PGPException;
import org.bouncycastle2.openpgp.PGPSecretKey;
import org.bouncycastle2.openpgp.PGPSecretKeyRing;
import org.thialfihar.android.apg.provider.Database;
import org.thialfihar.android.apg.ui.widget.KeyEditor;
import org.thialfihar.android.apg.ui.widget.SectionView;
import org.thialfihar.android.apg.utils.IterableIterator;
@ -69,7 +71,7 @@ public class EditKeyActivity extends BaseActivity implements OnClickListener {
Intent intent = getIntent();
long keyId = 0;
if (intent.getExtras() != null) {
keyId = intent.getExtras().getLong("keyId");
keyId = intent.getExtras().getLong(Apg.EXTRA_KEY_ID);
}
if (keyId != 0) {
@ -115,7 +117,7 @@ public class EditKeyActivity extends BaseActivity implements OnClickListener {
Toast.makeText(this, "Warning: Key editing is still kind of beta.", Toast.LENGTH_LONG).show();
}
public long getMasterKeyId() {
private long getMasterKeyId() {
if (mKeys.getEditors().getChildCount() == 0) {
return 0;
}
@ -243,22 +245,27 @@ public class EditKeyActivity extends BaseActivity implements OnClickListener {
newPassPhrase = oldPassPhrase;
}
Apg.buildSecretKey(this, mUserIds, mKeys, oldPassPhrase, newPassPhrase, this);
Apg.setCachedPassPhrase(getMasterKeyId(), newPassPhrase);
} catch (NoSuchProviderException e) {
error = e.getMessage();
error = "" + e;
} catch (NoSuchAlgorithmException e) {
error = e.getMessage();
error = "" + e;
} catch (PGPException e) {
error = e.getMessage();
error = "" + e;
} catch (SignatureException e) {
error = e.getMessage();
error = "" + e;
} catch (Apg.GeneralException e) {
error = e.getMessage();
error = "" + e;
} catch (Database.GeneralException e) {
error = "" + e;
} catch (IOException e) {
error = "" + e;
}
data.putInt("type", Id.message.done);
data.putInt(Apg.EXTRA_STATUS, Id.message.done);
if (error != null) {
data.putString("error", error);
data.putString(Apg.EXTRA_ERROR, error);
}
msg.setData(data);
@ -272,11 +279,10 @@ public class EditKeyActivity extends BaseActivity implements OnClickListener {
Bundle data = msg.getData();
removeDialog(Id.dialog.saving);
String error = data.getString("error");
String error = data.getString(Apg.EXTRA_ERROR);
if (error != null) {
Toast.makeText(EditKeyActivity.this,
getString(R.string.errorMessage, data.getString("error")),
Toast.LENGTH_SHORT).show();
getString(R.string.errorMessage, error), Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(EditKeyActivity.this, R.string.keySaved, Toast.LENGTH_SHORT).show();
setResult(RESULT_OK);

View File

@ -35,7 +35,6 @@ import org.bouncycastle2.openpgp.PGPPublicKeyRing;
import org.bouncycastle2.openpgp.PGPSecretKey;
import org.bouncycastle2.openpgp.PGPSecretKeyRing;
import org.bouncycastle2.util.Strings;
import org.openintents.intents.FileManager;
import org.thialfihar.android.apg.Apg.GeneralException;
import org.thialfihar.android.apg.utils.Choice;
@ -62,11 +61,13 @@ import android.widget.Toast;
import android.widget.ViewFlipper;
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;
@ -265,21 +266,26 @@ public class EncryptActivity extends BaseActivity {
}
});
Intent intent = getIntent();
if (intent.getAction() != null &&
(intent.getAction().equals(Apg.Intent.ENCRYPT) ||
intent.getAction().equals(Apg.Intent.ENCRYPT_FILE))) {
Bundle extras = intent.getExtras();
mIntent = getIntent();
if (Apg.Intent.ENCRYPT.equals(mIntent.getAction()) ||
Apg.Intent.ENCRYPT_FILE.equals(mIntent.getAction()) ||
Apg.Intent.ENCRYPT_AND_RETURN.equals(mIntent.getAction())) {
Bundle extras = mIntent.getExtras();
if (extras == null) {
extras = new Bundle();
}
String data = extras.getString("data");
mSendTo = extras.getString("sendTo");
mSubject = extras.getString("subject");
long signatureKeyId = extras.getLong("signatureKeyId");
long encryptionKeyIds[] = extras.getLongArray("encryptionKeyIds");
if (Apg.Intent.ENCRYPT_AND_RETURN.equals(mIntent.getAction())) {
mReturnResult = true;
}
String data = extras.getString(Apg.EXTRA_DATA);
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.findSecretKeyRing(signatureKeyId);
PGPSecretKeyRing keyRing = Apg.getSecretKeyRing(signatureKeyId);
PGPSecretKey masterKey = null;
if (keyRing != null) {
masterKey = Apg.getMasterKey(keyRing);
@ -295,7 +301,7 @@ public class EncryptActivity extends BaseActivity {
if (encryptionKeyIds != null) {
Vector<Long> goodIds = new Vector<Long>();
for (int i = 0; i < encryptionKeyIds.length; ++i) {
PGPPublicKeyRing keyRing = Apg.findPublicKeyRing(encryptionKeyIds[i]);
PGPPublicKeyRing keyRing = Apg.getPublicKeyRing(encryptionKeyIds[i]);
PGPPublicKey masterKey = null;
if (keyRing == null) {
continue;
@ -318,7 +324,8 @@ public class EncryptActivity extends BaseActivity {
}
}
if (intent.getAction().equals(Apg.Intent.ENCRYPT)) {
if (Apg.Intent.ENCRYPT.equals(mIntent.getAction()) ||
Apg.Intent.ENCRYPT_AND_RETURN.equals(mIntent.getAction())) {
if (data != null) {
mMessage.setText(data);
}
@ -327,7 +334,7 @@ public class EncryptActivity extends BaseActivity {
while (mSource.getCurrentView().getId() != R.id.sourceMessage) {
mSource.showNext();
}
} else if (intent.getAction().equals(Apg.Intent.ENCRYPT_FILE)) {
} else if (Apg.Intent.ENCRYPT_FILE.equals(mIntent.getAction())) {
mSource.setInAnimation(null);
mSource.setOutAnimation(null);
while (mSource.getCurrentView().getId() != R.id.sourceFile) {
@ -339,23 +346,47 @@ public class EncryptActivity extends BaseActivity {
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);
mEncryptToClipboardButton.setEnabled(false);
mEncryptToClipboardButton.setVisibility(View.INVISIBLE);
mEncryptButton.setText(R.string.btn_encrypt);
}
if (mReturnResult &&
mMessage.getText().length() > 0 &&
((mEncryptionKeyIds != null &&
mEncryptionKeyIds.length > 0) ||
getSecretKeyId() != 0)) {
encryptClicked();
}
}
private void openFile() {
String filename = mFilename.getText().toString();
Intent intent = new Intent(FileManager.ACTION_PICK_FILE);
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setData(Uri.parse("file://" + filename));
intent.putExtra(FileManager.EXTRA_TITLE, R.string.filemanager_titleEncrypt);
intent.putExtra(FileManager.EXTRA_BUTTON_TEXT, R.string.filemanager_btnOpen);
intent.setType("*/*");
try {
startActivityForResult(intent, Id.request.filename);
} catch (ActivityNotFoundException e) {
// No compatible file manager was found.
Toast.makeText(this, R.string.oiFilemanagerNotInstalled, Toast.LENGTH_SHORT).show();
Toast.makeText(this, R.string.noFilemanagerInstalled, Toast.LENGTH_SHORT).show();
}
}
@ -457,7 +488,7 @@ public class EncryptActivity extends BaseActivity {
return;
}
} else {
boolean encryptIt = mEncryptionKeyIds != null && mEncryptionKeyIds.length > 0;
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();
@ -527,7 +558,7 @@ public class EncryptActivity extends BaseActivity {
} else {
encryptionKeyIds = mEncryptionKeyIds;
signatureKeyId = getSecretKeyId();
signOnly = mEncryptionKeyIds == null || mEncryptionKeyIds.length == 0;
signOnly = (mEncryptionKeyIds == null || mEncryptionKeyIds.length == 0);
}
if (mEncryptTarget == Id.target.file) {
@ -548,7 +579,7 @@ public class EncryptActivity extends BaseActivity {
} else {
String message = mMessage.getText().toString();
if (signOnly) {
if (signOnly && mReturnResult) {
// 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
@ -582,26 +613,27 @@ public class EncryptActivity extends BaseActivity {
out.close();
if (mEncryptTarget != Id.target.file) {
data.putString("message", new String(((ByteArrayOutputStream)out).toByteArray()));
data.putByteArray(Apg.EXTRA_ENCRYPTED_MESSAGE,
((ByteArrayOutputStream)out).toByteArray());
}
} catch (IOException e) {
error = e.getMessage();
error = "" + e;
} catch (PGPException e) {
error = e.getMessage();
error = "" + e;
} catch (NoSuchProviderException e) {
error = e.getMessage();
error = "" + e;
} catch (NoSuchAlgorithmException e) {
error = e.getMessage();
error = "" + e;
} catch (SignatureException e) {
error = e.getMessage();
error = "" + e;
} catch (Apg.GeneralException e) {
error = e.getMessage();
error = "" + e;
}
data.putInt("type", Id.message.done);
data.putInt(Apg.EXTRA_STATUS, Id.message.done);
if (error != null) {
data.putString("error", error);
data.putString(Apg.EXTRA_ERROR, error);
}
msg.setData(data);
@ -645,7 +677,7 @@ public class EncryptActivity extends BaseActivity {
private void selectPublicKeys() {
Intent intent = new Intent(this, SelectPublicKeyListActivity.class);
intent.putExtra("selection", mEncryptionKeyIds);
intent.putExtra(Apg.EXTRA_SELECTION, mEncryptionKeyIds);
startActivityForResult(intent, Id.request.public_keys);
}
@ -702,7 +734,7 @@ public class EncryptActivity extends BaseActivity {
case Id.request.public_keys: {
if (resultCode == RESULT_OK) {
Bundle bundle = data.getExtras();
mEncryptionKeyIds = bundle.getLongArray("selection");
mEncryptionKeyIds = bundle.getLongArray(Apg.EXTRA_SELECTION);
}
updateView();
break;
@ -723,53 +755,61 @@ public class EncryptActivity extends BaseActivity {
removeDialog(Id.dialog.encrypting);
Bundle data = msg.getData();
String error = data.getString("error");
String error = data.getString(Apg.EXTRA_ERROR);
if (error != null) {
Toast.makeText(EncryptActivity.this,
getString(R.string.errorMessage, data.getString("error")),
Toast.LENGTH_SHORT).show();
getString(R.string.errorMessage, error), Toast.LENGTH_SHORT).show();
return;
} else {
String message = data.getString("message");
switch (mEncryptTarget) {
case Id.target.clipboard: {
ClipboardManager clip = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
clip.setText(message);
Toast.makeText(this, R.string.encryptionToClipboardSuccessful,
Toast.LENGTH_SHORT).show();
break;
}
switch (mEncryptTarget) {
case Id.target.clipboard: {
String message = new String(data.getByteArray(Apg.EXTRA_ENCRYPTED_MESSAGE));
ClipboardManager clip = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
clip.setText(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;
}
case Id.target.email: {
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)));
String message = new String(data.getByteArray(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;
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;
}
default: {
// shouldn't happen
break;
}
}
}

View File

@ -16,8 +16,6 @@
package org.thialfihar.android.apg;
import org.openintents.intents.FileManager;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
@ -48,6 +46,8 @@ public class FileDialog {
String defaultFile, OnClickListener onClickListener,
String fileManagerTitle, String fileManagerButton,
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);
@ -102,18 +102,17 @@ public class FileDialog {
private static void openFile() {
String filename = mFilename.getText().toString();
Intent intent = new Intent(FileManager.ACTION_PICK_FILE);
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setData(Uri.parse("file://" + filename));
intent.putExtra(FileManager.EXTRA_TITLE, mFileManagerTitle);
intent.putExtra(FileManager.EXTRA_BUTTON_TEXT, mFileManagerButton);
intent.setType("*/*");
try {
mActivity.startActivityForResult(intent, mRequestCode);
} catch (ActivityNotFoundException e) {
// No compatible file manager was found.
Toast.makeText(mActivity, R.string.oiFilemanagerNotInstalled, Toast.LENGTH_SHORT).show();
Toast.makeText(mActivity, R.string.noFilemanagerInstalled, Toast.LENGTH_SHORT).show();
}
}
}

View File

@ -80,6 +80,11 @@ public final class Id {
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;

View File

@ -0,0 +1,642 @@
/*
* 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.thialfihar.android.apg;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Vector;
import org.bouncycastle2.openpgp.PGPException;
import org.bouncycastle2.openpgp.PGPPublicKeyRing;
import org.bouncycastle2.openpgp.PGPSecretKeyRing;
import org.thialfihar.android.apg.provider.KeyRings;
import org.thialfihar.android.apg.provider.Keys;
import org.thialfihar.android.apg.provider.UserIds;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
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.ViewGroup;
import android.widget.BaseExpandableListAdapter;
import android.widget.ExpandableListView;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
public class KeyListActivity extends BaseActivity {
protected ExpandableListView mList;
protected KeyListAdapter mListAdapter;
protected int mSelectedItem = -1;
protected int mTask = 0;
protected String mImportFilename = Constants.path.app_dir + "/";
protected String mExportFilename = Constants.path.app_dir + "/";
protected int mKeyType = Id.type.public_key;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.key_list);
mList = (ExpandableListView) findViewById(R.id.list);
mListAdapter = new KeyListAdapter(this);
mList.setAdapter(mListAdapter);
registerForContextMenu(mList);
}
@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() {
@Override
public void onOkClick(String filename) {
removeDialog(Id.dialog.import_keys);
mImportFilename = filename;
importKeys();
}
@Override
public void onCancelClick() {
removeDialog(Id.dialog.import_keys);
}
},
getString(R.string.filemanager_titleOpen),
getString(R.string.filemanager_btnOpen),
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() {
@Override
public void onOkClick(String filename) {
removeDialog(thisDialogId);
mExportFilename = filename;
exportKeys();
}
@Override
public void onCancelClick() {
removeDialog(thisDialogId);
}
},
getString(R.string.filemanager_titleSave),
getString(R.string.filemanager_btnSave),
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();
String filename = null;
if (mTask == Id.task.import_keys) {
filename = mImportFilename;
} else {
filename = mExportFilename;
}
try {
if (mTask == Id.task.import_keys) {
data = Apg.importKeyRings(this, mKeyType, filename, 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, filename, 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;
}
if (mTask == Id.task.import_keys) {
data.putInt(Apg.EXTRA_STATUS, Id.message.import_done);
} else {
data.putInt(Apg.EXTRA_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(Apg.EXTRA_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");
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();
}
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 class KeyChild {
public static final int KEY = 0;
public static final int USER_ID = 1;
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 KeyChild(long keyId, boolean isMasterKey, int algorithm, int keySize,
boolean canSign, boolean canEncrypt) {
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 KeyListAdapter(Context context) {
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mDatabase = Apg.getDatabase().db();
mCursor = mDatabase.query(
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') ",
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");
startManagingCursor(mCursor);
rebuild(false);
}
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");
long masterKeyId = -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);
}
}
c.close();
if (masterKeyId != -1) {
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;
}
@Override
public boolean hasStableIds() {
return true;
}
@Override
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 = Long.toHexString(child.keyId & 0xffffffffL);
while (keyIdStr.length() < 8) {
keyIdStr = "0" + keyIdStr;
}
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;
}
}
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);
}
}

View File

@ -87,7 +87,7 @@ public class MailListActivity extends ListActivity {
mconversations = new Vector<Conversation>();
mmessages = new Vector<Message>();
String account = getIntent().getExtras().getString("account");
String account = getIntent().getExtras().getString(Apg.EXTRA_ACCOUNT);
// TODO: what if account is null?
Uri uri = Uri.parse("content://gmail-ls/conversations/" + account);
Cursor cursor =
@ -153,9 +153,9 @@ public class MailListActivity extends ListActivity {
Intent intent = new Intent(MailListActivity.this, DecryptActivity.class);
intent.setAction(Apg.Intent.DECRYPT);
Message message = (Message) ((MailboxAdapter) getListAdapter()).getItem(position);
intent.putExtra("data", message.data);
intent.putExtra("subject", message.subject);
intent.putExtra("replyTo", message.replyTo);
intent.putExtra(Apg.EXTRA_DATA, message.data);
intent.putExtra(Apg.EXTRA_SUBJECT, message.subject);
intent.putExtra(Apg.EXTRA_REPLY_TO, message.replyTo);
startActivity(intent);
}
});

View File

@ -47,6 +47,8 @@ import android.widget.AdapterView.OnItemClickListener;
public class MainActivity extends BaseActivity {
private ListView mAccounts = null;
private AccountListAdapter mListAdapter = null;
private Cursor mAccountCursor;
@Override
public void onCreate(Bundle savedInstanceState) {
@ -95,22 +97,22 @@ public class MainActivity extends BaseActivity {
}
});
Cursor accountCursor = managedQuery(Accounts.CONTENT_URI, null, null, null, null);
mAccountCursor =
Apg.getDatabase().db().query(Accounts.TABLE_NAME,
new String[] {
Accounts._ID,
Accounts.NAME,
}, null, null, null, null, Accounts.NAME + " ASC");
startManagingCursor(mAccountCursor);
mAccounts.setAdapter(new AccountListAdapter(this, accountCursor));
mListAdapter = new AccountListAdapter(this, mAccountCursor);
mAccounts.setAdapter(mListAdapter);
mAccounts.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> arg0, View view, int index, long id) {
Cursor cursor =
managedQuery(Uri.withAppendedPath(Accounts.CONTENT_URI, "" + id), null,
null, null, null);
if (cursor != null && cursor.getCount() > 0) {
cursor.moveToFirst();
int nameIndex = cursor.getColumnIndex(Accounts.NAME);
String accountName = cursor.getString(nameIndex);
startActivity(new Intent(MainActivity.this, MailListActivity.class)
.putExtra("account", accountName));
}
String accountName = (String) mAccounts.getItemAtPosition(index);
startActivity(new Intent(MainActivity.this, MailListActivity.class)
.putExtra(Apg.EXTRA_ACCOUNT, accountName));
}
});
registerForContextMenu(mAccounts);
@ -154,9 +156,10 @@ public class MainActivity extends BaseActivity {
ContentValues values = new ContentValues();
values.put(Accounts.NAME, accountName);
try {
MainActivity.this.getContentResolver()
.insert(Accounts.CONTENT_URI,
values);
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,
@ -188,6 +191,12 @@ public class MainActivity extends BaseActivity {
message.setText("Read the warnings!\n\n" +
"Changes:\n" +
"* k9mail integration, k9mail beta build is available on the k9mail website\n" +
"* support of other file managers (e.g. ASTRO)\n" +
"* Slovenian translation (thanks, 359)\n" +
"* new database, much faster, less memory usage\n" +
"* defined Intents and content provider for other apps\n" +
"* bugfixes\n" +
"\n" +
"WARNING: be careful editing your existing keys, as they " +
"WILL be stripped of certificates right now.\n" +
@ -277,8 +286,11 @@ public class MainActivity extends BaseActivity {
switch (menuItem.getItemId()) {
case Id.menu.delete: {
Uri uri = Uri.withAppendedPath(Accounts.CONTENT_URI, "" + info.id);
this.getContentResolver().delete(uri, null, null);
Apg.getDatabase().db().delete(Accounts.TABLE_NAME,
Accounts._ID + " = ?",
new String[] { "" + info.id });
mAccountCursor.requery();
mListAdapter.notifyDataSetChanged();
return true;
}
@ -297,6 +309,13 @@ public class MainActivity extends BaseActivity {
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();

View File

@ -50,7 +50,6 @@ public class PreferencesActivity extends BaseActivity {
new Choice(180, getString(R.string.choice_3mins)),
new Choice(300, getString(R.string.choice_5mins)),
new Choice(600, getString(R.string.choice_10mins)),
new Choice(0, getString(R.string.choice_untilQuit)),
};
ArrayAdapter<Choice> adapter =
new ArrayAdapter<Choice>(this, android.R.layout.simple_spinner_item, choices);

View File

@ -16,54 +16,19 @@
package org.thialfihar.android.apg;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Vector;
import org.bouncycastle2.openpgp.PGPException;
import org.bouncycastle2.openpgp.PGPPublicKey;
import org.bouncycastle2.openpgp.PGPPublicKeyRing;
import org.thialfihar.android.apg.utils.IterableIterator;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Message;
import android.view.ContextMenu;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.ContextMenu.ContextMenuInfo;
import android.widget.BaseExpandableListAdapter;
import android.widget.ExpandableListView;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
public class PublicKeyListActivity extends BaseActivity {
ExpandableListView mList;
protected int mSelectedItem = -1;
protected int mTask = 0;
private String mImportFilename = Constants.path.app_dir + "/pubring.gpg";
private String mExportFilename = Constants.path.app_dir + "/pubexport.asc";
public class PublicKeyListActivity extends KeyListActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
public void onCreate(Bundle savedInstanceState) {
mExportFilename = Constants.path.app_dir + "/pubexport.asc";
mKeyType = Id.type.public_key;
super.onCreate(savedInstanceState);
setContentView(R.layout.key_list);
mList = (ExpandableListView) findViewById(R.id.list);
mList.setAdapter(new PublicKeyListAdapter(this));
registerForContextMenu(mList);
}
@Override
@ -79,507 +44,17 @@ public class PublicKeyListActivity extends BaseActivity {
return true;
}
@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 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);
int groupPosition = ExpandableListView.getPackedPositionGroup(info.packedPosition);
if (type == ExpandableListView.PACKED_POSITION_TYPE_GROUP) {
PGPPublicKeyRing keyRing = Apg.getPublicKeyRings().get(groupPosition);
String userId = Apg.getMainUserIdSafe(this, Apg.getMasterKey(keyRing));
menu.setHeaderTitle(userId);
// 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);
}
}
@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: {
PGPPublicKeyRing keyRing = Apg.getPublicKeyRings().get(mSelectedItem);
String userId = Apg.getMainUserIdSafe(this, Apg.getMasterKey(keyRing));
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.warning);
builder.setMessage(getString(R.string.keyDeletionConfirmation, 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(mSelectedItem);
mSelectedItem = -1;
removeDialog(Id.dialog.delete_key);
}
});
builder.setNegativeButton(android.R.string.cancel,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
mSelectedItem = -1;
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() {
@Override
public void onOkClick(String filename) {
removeDialog(Id.dialog.import_keys);
mImportFilename = filename;
importKeys();
}
@Override
public void onCancelClick() {
removeDialog(Id.dialog.import_keys);
}
},
getString(R.string.filemanager_titleOpen),
getString(R.string.filemanager_btnOpen),
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(R.string.specifyFileToExportTo),
mExportFilename,
new FileDialog.OnClickListener() {
@Override
public void onOkClick(String filename) {
removeDialog(thisDialogId);
mExportFilename = filename;
exportKeys();
}
@Override
public void onCancelClick() {
removeDialog(thisDialogId);
}
},
getString(R.string.filemanager_titleSave),
getString(R.string.filemanager_btnSave),
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();
String filename = null;
if (mTask == Id.task.import_keys) {
filename = mImportFilename;
} else {
filename = mExportFilename;
}
try {
if (mTask == Id.task.import_keys) {
data = Apg.importKeyRings(this, Id.type.public_key, filename, this);
} else {
Vector<Object> keys = new Vector<Object>();
if (mSelectedItem == -1) {
for (PGPPublicKeyRing key : Apg.getPublicKeyRings()) {
keys.add(key);
}
} else {
keys.add(Apg.getPublicKeyRings().get(mSelectedItem));
}
data = Apg.exportKeyRings(this, keys, filename, this);
}
} catch (FileNotFoundException e) {
error = getString(R.string.error_fileNotFound);
} catch (IOException e) {
error = e.getMessage();
} catch (PGPException e) {
error = e.getMessage();
} catch (Apg.GeneralException e) {
error = e.getMessage();
}
if (mTask == Id.task.import_keys) {
data.putInt("type", Id.message.import_done);
} else {
data.putInt("type", Id.message.export_done);
}
if (error != null) {
data.putString("error", error);
}
msg.setData(data);
sendMessage(msg);
}
private void deleteKey(int index) {
PGPPublicKeyRing keyRing = Apg.getPublicKeyRings().get(index);
Apg.deleteKey(this, keyRing);
refreshList();
}
private void refreshList() {
((PublicKeyListAdapter) mList.getExpandableListAdapter()).notifyDataSetChanged();
}
@Override
public void doneCallback(Message msg) {
super.doneCallback(msg);
Bundle data = msg.getData();
if (data != null) {
int type = data.getInt("type");
switch (type) {
case Id.message.import_done: {
removeDialog(Id.dialog.importing);
String error = data.getString("error");
if (error != null) {
Toast.makeText(PublicKeyListActivity.this,
getString(R.string.errorMessage, data.getString("error")),
Toast.LENGTH_SHORT).show();
} else {
int added = data.getInt("added");
int updated = data.getInt("updated");
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(PublicKeyListActivity.this, message,
Toast.LENGTH_SHORT).show();
}
refreshList();
break;
}
case Id.message.export_done: {
removeDialog(Id.dialog.exporting);
String error = data.getString("error");
if (error != null) {
Toast.makeText(PublicKeyListActivity.this,
getString(R.string.errorMessage, data.getString("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);
} else{
message = getString(R.string.noKeysExported);
}
Toast.makeText(PublicKeyListActivity.this, message,
Toast.LENGTH_SHORT).show();
}
break;
}
default: {
break;
}
}
}
}
private static class PublicKeyListAdapter extends BaseExpandableListAdapter {
private LayoutInflater mInflater;
private class KeyChild {
public static final int KEY = 0;
public static final int USER_ID = 1;
public int type;
public PGPPublicKey key;
public String userId;
public KeyChild(PGPPublicKey key) {
type = KEY;
this.key = key;
}
public KeyChild(String userId) {
type = USER_ID;
this.userId = userId;
}
}
public PublicKeyListAdapter(Context context) {
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
protected Vector<KeyChild> getChildrenOfKeyRing(PGPPublicKeyRing keyRing) {
Vector<KeyChild> children = new Vector<KeyChild>();
PGPPublicKey masterKey = null;
for (PGPPublicKey key : new IterableIterator<PGPPublicKey>(keyRing.getPublicKeys())) {
children.add(new KeyChild(key));
if (key.isMasterKey()) {
masterKey = key;
}
}
if (masterKey != null) {
boolean isFirst = true;
for (String userId : new IterableIterator<String>(masterKey.getUserIDs())) {
if (isFirst) {
// ignore first, it's in the group already
isFirst = false;
continue;
}
children.add(new KeyChild(userId));
}
}
return children;
}
@Override
public boolean hasStableIds() {
return true;
}
@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return true;
}
public int getGroupCount() {
return Apg.getPublicKeyRings().size();
}
public Object getChild(int groupPosition, int childPosition) {
PGPPublicKeyRing keyRing = Apg.getPublicKeyRings().get(groupPosition);
Vector<KeyChild> children = getChildrenOfKeyRing(keyRing);
KeyChild child = children.get(childPosition);
return child;
}
public long getChildId(int groupPosition, int childPosition) {
return childPosition;
}
public int getChildrenCount(int groupPosition) {
return getChildrenOfKeyRing(Apg.getPublicKeyRings().get(groupPosition)).size();
}
public Object getGroup(int position) {
return position;
}
public long getGroupId(int position) {
return position;
}
public View getGroupView(int groupPosition, boolean isExpanded, View convertView,
ViewGroup parent) {
PGPPublicKeyRing keyRing = Apg.getPublicKeyRings().get(groupPosition);
for (PGPPublicKey key : new IterableIterator<PGPPublicKey>(keyRing.getPublicKeys())) {
View view;
if (!key.isMasterKey()) {
continue;
}
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 = Apg.getMainUserId(key);
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;
}
return null;
}
public View getChildView(int groupPosition, int childPosition,
boolean isLastChild, View convertView,
ViewGroup parent) {
PGPPublicKeyRing keyRing = Apg.getPublicKeyRings().get(groupPosition);
Vector<KeyChild> children = getChildrenOfKeyRing(keyRing);
KeyChild child = children.get(childPosition);
View view = null;
switch (child.type) {
case KeyChild.KEY: {
PGPPublicKey key = child.key;
if (key.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 = Long.toHexString(key.getKeyID() & 0xffffffffL);
while (keyIdStr.length() < 8) {
keyIdStr = "0" + keyIdStr;
}
keyId.setText(keyIdStr);
TextView keyDetails = (TextView) view.findViewById(R.id.keyDetails);
String algorithmStr = Apg.getAlgorithmInfo(key);
keyDetails.setText("(" + algorithmStr + ")");
ImageView encryptIcon = (ImageView) view.findViewById(R.id.ic_encryptKey);
if (!Apg.isEncryptionKey(key)) {
encryptIcon.setVisibility(View.GONE);
}
ImageView signIcon = (ImageView) view.findViewById(R.id.ic_signKey);
if (!Apg.isSigningKey(key)) {
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;
}
}
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);
}
}

View File

@ -16,55 +16,24 @@
package org.thialfihar.android.apg;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Vector;
import org.bouncycastle2.openpgp.PGPException;
import org.bouncycastle2.openpgp.PGPSecretKey;
import org.bouncycastle2.openpgp.PGPSecretKeyRing;
import org.thialfihar.android.apg.utils.IterableIterator;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Message;
import android.view.ContextMenu;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.ContextMenu.ContextMenuInfo;
import android.widget.BaseExpandableListAdapter;
import android.widget.ExpandableListView;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
import android.widget.ExpandableListView.OnChildClickListener;
public class SecretKeyListActivity extends BaseActivity implements OnChildClickListener {
ExpandableListView mList;
protected int mSelectedItem = -1;
protected int mTask = 0;
private String mImportFilename = Constants.path.app_dir + "/secring.gpg";
private String mExportFilename = Constants.path.app_dir + "/secexport.asc";
public class SecretKeyListActivity extends KeyListActivity implements OnChildClickListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
public void onCreate(Bundle savedInstanceState) {
mExportFilename = Constants.path.app_dir + "/secexport.asc";
mKeyType = Id.type.secret_key;
super.onCreate(savedInstanceState);
setContentView(R.layout.key_list);
mList = (ExpandableListView) findViewById(R.id.list);
mList.setAdapter(new SecretKeyListAdapter(this));
registerForContextMenu(mList);
mList.setOnChildClickListener(this);
}
@ -86,16 +55,6 @@ public class SecretKeyListActivity extends BaseActivity implements OnChildClickL
@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;
}
case Id.menu.option.create: {
createKey();
return true;
@ -113,12 +72,9 @@ public class SecretKeyListActivity extends BaseActivity implements OnChildClickL
ExpandableListView.ExpandableListContextMenuInfo info =
(ExpandableListView.ExpandableListContextMenuInfo) menuInfo;
int type = ExpandableListView.getPackedPositionType(info.packedPosition);
int groupPosition = ExpandableListView.getPackedPositionGroup(info.packedPosition);
if (type == ExpandableListView.PACKED_POSITION_TYPE_GROUP) {
PGPSecretKeyRing keyRing = Apg.getSecretKeyRings().get(groupPosition);
String userId = Apg.getMainUserIdSafe(this, Apg.getMasterKey(keyRing));
menu.setHeaderTitle(userId);
// 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);
@ -142,18 +98,6 @@ public class SecretKeyListActivity extends BaseActivity implements OnChildClickL
return true;
}
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);
}
@ -170,96 +114,9 @@ public class SecretKeyListActivity extends BaseActivity implements OnChildClickL
@Override
protected Dialog onCreateDialog(int id) {
boolean singleKeyExport = false;
switch (id) {
case Id.dialog.delete_key: {
PGPSecretKeyRing keyRing = Apg.getSecretKeyRings().get(mSelectedItem);
String userId = Apg.getMainUserIdSafe(this, Apg.getMasterKey(keyRing));
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.warning);
builder.setMessage(getString(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(mSelectedItem);
mSelectedItem = -1;
removeDialog(Id.dialog.delete_key);
}
});
builder.setNegativeButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
mSelectedItem = -1;
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() {
@Override
public void onOkClick(String filename) {
removeDialog(Id.dialog.import_keys);
mImportFilename = filename;
importKeys();
}
@Override
public void onCancelClick() {
removeDialog(Id.dialog.import_keys);
}
},
getString(R.string.filemanager_titleOpen),
getString(R.string.filemanager_btnOpen),
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(R.string.specifyFileToExportSecretKeysTo),
mExportFilename,
new FileDialog.OnClickListener() {
@Override
public void onOkClick(String filename) {
removeDialog(thisDialogId);
mExportFilename = filename;
exportKeys();
}
@Override
public void onCancelClick() {
removeDialog(thisDialogId);
}
},
getString(R.string.filemanager_titleSave),
getString(R.string.filemanager_btnSave),
Id.request.filename);
}
case Id.dialog.pass_phrase: {
PGPSecretKeyRing keyRing = Apg.getSecretKeyRings().get(mSelectedItem);
long keyId = keyRing.getSecretKey().getKeyID();
long keyId = ((KeyListAdapter) mList.getExpandableListAdapter()).getGroupId(mSelectedItem);
return AskForSecretKeyPassPhrase.createDialog(this, keyId, this);
}
@ -270,8 +127,7 @@ public class SecretKeyListActivity extends BaseActivity implements OnChildClickL
}
public void checkPassPhraseAndEdit() {
PGPSecretKeyRing keyRing = Apg.getSecretKeyRings().get(mSelectedItem);
long keyId = keyRing.getSecretKey().getKeyID();
long keyId = ((KeyListAdapter) mList.getExpandableListAdapter()).getGroupId(mSelectedItem);
String passPhrase = Apg.getCachedPassPhrase(keyId);
if (passPhrase == null) {
showDialog(Id.dialog.pass_phrase);
@ -295,10 +151,9 @@ public class SecretKeyListActivity extends BaseActivity implements OnChildClickL
}
private void editKey() {
PGPSecretKeyRing keyRing = Apg.getSecretKeyRings().get(mSelectedItem);
long keyId = keyRing.getSecretKey().getKeyID();
long keyId = ((KeyListAdapter) mList.getExpandableListAdapter()).getGroupId(mSelectedItem);
Intent intent = new Intent(this, EditKeyActivity.class);
intent.putExtra("keyId", keyId);
intent.putExtra(Apg.EXTRA_KEY_ID, keyId);
startActivityForResult(intent, Id.message.edit_key);
}
@ -313,24 +168,6 @@ public class SecretKeyListActivity extends BaseActivity implements OnChildClickL
break;
}
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;
}
@ -338,320 +175,4 @@ public class SecretKeyListActivity extends BaseActivity implements OnChildClickL
super.onActivityResult(requestCode, resultCode, data);
}
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();
String filename = null;
if (mTask == Id.task.import_keys) {
filename = mImportFilename;
} else {
filename = mExportFilename;
}
try {
if (mTask == Id.task.import_keys) {
data = Apg.importKeyRings(this, Id.type.secret_key, filename, this);
} else {
Vector<Object> keys = new Vector<Object>();
if (mSelectedItem == -1) {
for (PGPSecretKeyRing key : Apg.getSecretKeyRings()) {
keys.add(key);
}
} else {
keys.add(Apg.getSecretKeyRings().get(mSelectedItem));
}
data = Apg.exportKeyRings(this, keys, filename, this);
}
} catch (FileNotFoundException e) {
error = getString(R.string.error_fileNotFound);
} catch (IOException e) {
error = e.getMessage();
} catch (PGPException e) {
error = e.getMessage();
} catch (Apg.GeneralException e) {
error = e.getMessage();
}
if (mTask == Id.task.import_keys) {
data.putInt("type", Id.message.import_done);
} else {
data.putInt("type", Id.message.export_done);
}
if (error != null) {
data.putString("error", error);
}
msg.setData(data);
sendMessage(msg);
}
private void deleteKey(int index) {
PGPSecretKeyRing keyRing = Apg.getSecretKeyRings().get(index);
Apg.deleteKey(this, keyRing);
refreshList();
}
private void refreshList() {
((SecretKeyListAdapter) mList.getExpandableListAdapter()).notifyDataSetChanged();
}
@Override
public void doneCallback(Message msg) {
super.doneCallback(msg);
Bundle data = msg.getData();
if (data != null) {
int type = data.getInt("type");
switch (type) {
case Id.message.import_done: {
removeDialog(Id.dialog.importing);
String error = data.getString("error");
if (error != null) {
Toast.makeText(SecretKeyListActivity.this,
getString(R.string.errorMessage, data.getString("error")),
Toast.LENGTH_SHORT).show();
} else {
int added = data.getInt("added");
int updated = data.getInt("updated");
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(SecretKeyListActivity.this, message,
Toast.LENGTH_SHORT).show();
}
refreshList();
break;
}
case Id.message.export_done: {
removeDialog(Id.dialog.exporting);
String error = data.getString("error");
if (error != null) {
Toast.makeText(SecretKeyListActivity.this,
getString(R.string.errorMessage, data.getString("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);
} else{
message = getString(R.string.noKeysExported);
}
Toast.makeText(SecretKeyListActivity.this, message,
Toast.LENGTH_SHORT).show();
}
break;
}
default: {
break;
}
}
}
}
private static class SecretKeyListAdapter extends BaseExpandableListAdapter {
private LayoutInflater mInflater;
private class KeyChild {
static final int KEY = 0;
static final int USER_ID = 1;
public int type;
public PGPSecretKey key;
public String userId;
public KeyChild(PGPSecretKey key) {
type = KEY;
this.key = key;
}
public KeyChild(String userId) {
type = USER_ID;
this.userId = userId;
}
}
public SecretKeyListAdapter(Context context) {
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
protected Vector<KeyChild> getChildrenOfKeyRing(PGPSecretKeyRing keyRing) {
Vector<KeyChild> children = new Vector<KeyChild>();
PGPSecretKey masterKey = null;
for (PGPSecretKey key : new IterableIterator<PGPSecretKey>(keyRing.getSecretKeys())) {
children.add(new KeyChild(key));
if (key.isMasterKey()) {
masterKey = key;
}
}
if (masterKey != null) {
boolean isFirst = true;
for (String userId : new IterableIterator<String>(masterKey.getUserIDs())) {
if (isFirst) {
// ignore first, it's in the group already
isFirst = false;
continue;
}
children.add(new KeyChild(userId));
}
}
return children;
}
@Override
public boolean hasStableIds() {
return true;
}
@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return true;
}
public int getGroupCount() {
return Apg.getSecretKeyRings().size();
}
public Object getChild(int groupPosition, int childPosition) {
PGPSecretKeyRing keyRing = Apg.getSecretKeyRings().get(groupPosition);
Vector<KeyChild> children = getChildrenOfKeyRing(keyRing);
KeyChild child = children.get(childPosition);
return child;
}
public long getChildId(int groupPosition, int childPosition) {
return childPosition;
}
public int getChildrenCount(int groupPosition) {
return getChildrenOfKeyRing(Apg.getSecretKeyRings().get(groupPosition)).size();
}
public Object getGroup(int position) {
return position;
}
public long getGroupId(int position) {
return position;
}
public View getGroupView(int groupPosition, boolean isExpanded,
View convertView, ViewGroup parent) {
PGPSecretKeyRing keyRing = Apg.getSecretKeyRings().get(groupPosition);
for (PGPSecretKey key : new IterableIterator<PGPSecretKey>(keyRing.getSecretKeys())) {
View view;
if (!key.isMasterKey()) {
continue;
}
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 = Apg.getMainUserId(key);
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;
}
return null;
}
public View getChildView(int groupPosition, int childPosition,
boolean isLastChild, View convertView,
ViewGroup parent) {
PGPSecretKeyRing keyRing = Apg.getSecretKeyRings().get(groupPosition);
Vector<KeyChild> children = getChildrenOfKeyRing(keyRing);
KeyChild child = children.get(childPosition);
View view = null;
switch (child.type) {
case KeyChild.KEY: {
PGPSecretKey key = child.key;
if (key.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 = Long.toHexString(key.getKeyID() & 0xffffffffL);
while (keyIdStr.length() < 8) {
keyIdStr = "0" + keyIdStr;
}
keyId.setText(keyIdStr);
TextView keyDetails = (TextView) view.findViewById(R.id.keyDetails);
String algorithmStr = Apg.getAlgorithmInfo(key);
keyDetails.setText("(" + algorithmStr + ")");
ImageView encryptIcon = (ImageView) view.findViewById(R.id.ic_encryptKey);
if (!Apg.isEncryptionKey(key)) {
encryptIcon.setVisibility(View.GONE);
}
ImageView signIcon = (ImageView) view.findViewById(R.id.ic_signKey);
if (!Apg.isSigningKey(key)) {
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;
}
}
return view;
}
}
}

View File

@ -16,12 +16,8 @@
package org.thialfihar.android.apg;
import java.util.Collections;
import java.util.Vector;
import org.bouncycastle2.openpgp.PGPPublicKey;
import org.bouncycastle2.openpgp.PGPPublicKeyRing;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
@ -42,27 +38,21 @@ public class SelectPublicKeyListActivity extends BaseActivity {
mIntent = getIntent();
long selectedKeyIds[] = null;
if (mIntent.getExtras() != null) {
selectedKeyIds = mIntent.getExtras().getLongArray("selection");
selectedKeyIds = mIntent.getExtras().getLongArray(Apg.EXTRA_SELECTION);
}
mList = (ListView) findViewById(R.id.list);
// needed in Android 1.5, where the XML attribute gets ignored
mList.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
Vector<PGPPublicKeyRing> keyRings =
(Vector<PGPPublicKeyRing>) Apg.getPublicKeyRings().clone();
Collections.sort(keyRings, new Apg.PublicKeySorter());
mList.setAdapter(new SelectPublicKeyListAdapter(mList, keyRings));
SelectPublicKeyListAdapter adapter = new SelectPublicKeyListAdapter(this, mList);
mList.setAdapter(adapter);
if (selectedKeyIds != null) {
for (int i = 0; i < keyRings.size(); ++i) {
PGPPublicKeyRing keyRing = keyRings.get(i);
PGPPublicKey key = Apg.getMasterKey(keyRing);
if (key == null) {
continue;
}
for (int i = 0; i < adapter.getCount(); ++i) {
long keyId = adapter.getItemId(i);
for (int j = 0; j < selectedKeyIds.length; ++j) {
if (key.getKeyID() == selectedKeyIds[j]) {
if (keyId == selectedKeyIds[j]) {
mList.setItemChecked(i, true);
break;
}
@ -106,7 +96,7 @@ public class SelectPublicKeyListActivity extends BaseActivity {
for (int i = 0; i < vector.size(); ++i) {
selectedKeyIds[i] = vector.get(i);
}
data.putExtra("selection", selectedKeyIds);
data.putExtra(Apg.EXTRA_SELECTION, selectedKeyIds);
setResult(RESULT_OK, data);
finish();
}

View File

@ -16,15 +16,16 @@
package org.thialfihar.android.apg;
import java.text.DateFormat;
import java.util.Date;
import java.util.Vector;
import org.bouncycastle2.openpgp.PGPPublicKey;
import org.bouncycastle2.openpgp.PGPPublicKeyRing;
import org.thialfihar.android.apg.utils.IterableIterator;
import org.thialfihar.android.apg.provider.KeyRings;
import org.thialfihar.android.apg.provider.Keys;
import org.thialfihar.android.apg.provider.UserIds;
import android.app.Activity;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -34,40 +35,55 @@ import android.widget.ListView;
import android.widget.TextView;
public class SelectPublicKeyListAdapter extends BaseAdapter {
protected Vector<PGPPublicKeyRing> mKeyRings;
protected LayoutInflater mInflater;
protected ListView mParent;
protected SQLiteDatabase mDatabase;
protected Cursor mCursor;
public SelectPublicKeyListAdapter(ListView parent,
Vector<PGPPublicKeyRing> keyRings) {
setKeyRings(keyRings);
public SelectPublicKeyListAdapter(Activity activity, ListView parent) {
mParent = parent;
mDatabase = Apg.getDatabase().db();
mInflater = (LayoutInflater) parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
long now = new Date().getTime() / 1000;
mCursor = mDatabase.query(
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') ",
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, UserIds.TABLE_NAME + "." + UserIds.USER_ID + " ASC");
public void setKeyRings(Vector<PGPPublicKeyRing> keyRings) {
mKeyRings = keyRings;
notifyDataSetChanged();
}
public Vector<PGPPublicKeyRing> getKeyRings() {
return mKeyRings;
activity.startManagingCursor(mCursor);
}
@Override
public boolean isEnabled(int position) {
PGPPublicKeyRing keyRing = mKeyRings.get(position);
if (Apg.getMasterKey(keyRing) == null) {
return false;
}
Vector<PGPPublicKey> encryptKeys = Apg.getUsableEncryptKeys(keyRing);
if (encryptKeys.size() == 0) {
return false;
}
return true;
mCursor.moveToPosition(position);
return mCursor.getInt(4) > 0; // valid CAN_ENCRYPT
}
@Override
@ -77,93 +93,62 @@ public class SelectPublicKeyListAdapter extends BaseAdapter {
@Override
public int getCount() {
return mKeyRings.size();
return mCursor.getCount();
}
@Override
public Object getItem(int position) {
return mKeyRings.get(position);
return position;
}
@Override
public long getItemId(int position) {
PGPPublicKeyRing keyRing = mKeyRings.get(position);
PGPPublicKey key = Apg.getMasterKey(keyRing);
if (key != null) {
return key.getKeyID();
}
return 0;
mCursor.moveToPosition(position);
return mCursor.getLong(1); // MASTER_KEY_ID
}
@Override
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);
PGPPublicKeyRing keyRing = mKeyRings.get(position);
PGPPublicKey key = null;
for (PGPPublicKey tKey : new IterableIterator<PGPPublicKey>(keyRing.getPublicKeys())) {
if (tKey.isMasterKey()) {
key = tKey;
break;
}
}
Vector<PGPPublicKey> encryptKeys = Apg.getEncryptKeys(keyRing);
Vector<PGPPublicKey> usableKeys = Apg.getUsableEncryptKeys(keyRing);
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 creation = (TextView) view.findViewById(R.id.creation);
creation.setText(R.string.noDate);
TextView expiry = (TextView) view.findViewById(R.id.expiry);
expiry.setText(R.string.noExpiry);
TextView status = (TextView) view.findViewById(R.id.status);
status.setText(R.string.unknownStatus);
if (key != null) {
String userId = Apg.getMainUserId(key);
if (userId != null) {
String chunks[] = userId.split(" <", 2);
userId = chunks[0];
if (chunks.length > 1) {
mainUserIdRest.setText("<" + chunks[1]);
}
mainUserId.setText(userId);
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]);
}
keyId.setText("" + Long.toHexString(key.getKeyID() & 0xffffffffL));
mainUserId.setText(userId);
}
long masterKeyId = mCursor.getLong(1); // MASTER_KEY_ID
keyId.setText("" + Long.toHexString(masterKeyId & 0xffffffffL));
if (mainUserIdRest.getText().length() == 0) {
mainUserIdRest.setVisibility(View.GONE);
}
PGPPublicKey timespanKey = key;
if (usableKeys.size() > 0) {
timespanKey = usableKeys.get(0);
if (enabled) {
status.setText(R.string.canEncrypt);
} else if (encryptKeys.size() > 0) {
timespanKey = encryptKeys.get(0);
Date now = new Date();
if (now.compareTo(Apg.getCreationDate(timespanKey)) > 0) {
status.setText(R.string.notValid);
} else {
status.setText(R.string.expired);
}
} else {
status.setText(R.string.noKey);
}
creation.setText(DateFormat.getDateInstance().format(Apg.getCreationDate(timespanKey)));
Date expiryDate = Apg.getExpiryDate(timespanKey);
if (expiryDate != null) {
expiry.setText(DateFormat.getDateInstance().format(expiryDate));
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() + " ");
@ -176,8 +161,6 @@ public class SelectPublicKeyListAdapter extends BaseAdapter {
mainUserId.setEnabled(enabled);
mainUserIdRest.setEnabled(enabled);
keyId.setEnabled(enabled);
creation.setEnabled(enabled);
expiry.setEnabled(enabled);
selected.setEnabled(enabled);
status.setEnabled(enabled);

View File

@ -16,32 +16,16 @@
package org.thialfihar.android.apg;
import java.text.DateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.Vector;
import org.bouncycastle2.openpgp.PGPSecretKey;
import org.bouncycastle2.openpgp.PGPSecretKeyRing;
import org.thialfihar.android.apg.utils.IterableIterator;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.AdapterView.OnItemClickListener;
public class SelectSecretKeyListActivity extends BaseActivity {
protected Vector<PGPSecretKeyRing> mKeyRings;
protected LayoutInflater mInflater;
protected Intent mIntent;
protected ListView mList;
protected SelectSecretKeyListAdapter mListAdapter;
protected long mSelectedKeyId = 0;
@ -49,158 +33,20 @@ public class SelectSecretKeyListActivity extends BaseActivity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
// fill things
mIntent = getIntent();
mKeyRings = (Vector<PGPSecretKeyRing>) Apg.getSecretKeyRings().clone();
Collections.sort(mKeyRings, new Apg.SecretKeySorter());
setContentView(R.layout.select_secret_key);
mList = (ListView) findViewById(R.id.list);
mList.setAdapter(new SecretKeyListAdapter(this));
mListAdapter = new SelectSecretKeyListAdapter(this, mList);
mList.setAdapter(mListAdapter);
mList.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
Intent data = new Intent();
data.putExtra("selectedKeyId", id);
data.putExtra(Apg.EXTRA_KEY_ID, id);
setResult(RESULT_OK, data);
finish();
}
});
}
private class SecretKeyListAdapter extends BaseAdapter {
public SecretKeyListAdapter(Context context) {
}
@Override
public boolean isEnabled(int position) {
PGPSecretKeyRing keyRing = mKeyRings.get(position);
if (Apg.getMasterKey(keyRing) == null) {
return false;
}
Vector<PGPSecretKey> usableKeys = Apg.getUsableSigningKeys(keyRing);
if (usableKeys.size() == 0) {
return false;
}
return true;
}
@Override
public boolean hasStableIds() {
return true;
}
@Override
public int getCount() {
return mKeyRings.size();
}
@Override
public Object getItem(int position) {
return mKeyRings.get(position);
}
@Override
public long getItemId(int position) {
PGPSecretKeyRing keyRing = mKeyRings.get(position);
PGPSecretKey key = Apg.getMasterKey(keyRing);
if (key != null) {
return key.getKeyID();
}
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = mInflater.inflate(R.layout.select_secret_key_item, null);
boolean enabled = isEnabled(position);
PGPSecretKeyRing keyRing = mKeyRings.get(position);
PGPSecretKey key = null;
for (PGPSecretKey tKey : new IterableIterator<PGPSecretKey>(keyRing.getSecretKeys())) {
if (tKey.isMasterKey()) {
key = tKey;
break;
}
}
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 creation = (TextView) view.findViewById(R.id.creation);
creation.setText(R.string.noDate);
TextView expiry = (TextView) view.findViewById(R.id.expiry);
expiry.setText(R.string.noExpiry);
TextView status = (TextView) view.findViewById(R.id.status);
status.setText(R.string.unknownStatus);
if (key != null) {
String userId = Apg.getMainUserId(key);
if (userId != null) {
String chunks[] = userId.split(" <", 2);
userId = chunks[0];
if (chunks.length > 1) {
mainUserIdRest.setText("<" + chunks[1]);
}
mainUserId.setText(userId);
}
keyId.setText("" + Long.toHexString(key.getKeyID() & 0xffffffffL));
}
if (mainUserIdRest.getText().length() == 0) {
mainUserIdRest.setVisibility(View.GONE);
}
Vector<PGPSecretKey> signingKeys = Apg.getSigningKeys(keyRing);
Vector<PGPSecretKey> usableKeys = Apg.getUsableSigningKeys(keyRing);
PGPSecretKey timespanKey = key;
if (usableKeys.size() > 0) {
timespanKey = usableKeys.get(0);
status.setText(R.string.canSign);
} else if (signingKeys.size() > 0) {
timespanKey = signingKeys.get(0);
Date now = new Date();
if (now.compareTo(Apg.getCreationDate(timespanKey)) > 0) {
status.setText(R.string.notValid);
} else {
status.setText(R.string.expired);
}
} else {
status.setText(R.string.noKey);
}
creation.setText(DateFormat.getDateInstance().format(Apg.getCreationDate(timespanKey)));
Date expiryDate = Apg.getExpiryDate(timespanKey);
if (expiryDate != null) {
expiry.setText(DateFormat.getDateInstance().format(expiryDate));
}
status.setText(status.getText() + " ");
view.setEnabled(enabled);
mainUserId.setEnabled(enabled);
mainUserIdRest.setEnabled(enabled);
keyId.setEnabled(enabled);
creation.setEnabled(enabled);
expiry.setEnabled(enabled);
status.setEnabled(enabled);
return view;
}
}
}

View File

@ -0,0 +1,147 @@
package org.thialfihar.android.apg;
import java.util.Date;
import org.thialfihar.android.apg.provider.KeyRings;
import org.thialfihar.android.apg.provider.Keys;
import org.thialfihar.android.apg.provider.UserIds;
import android.app.Activity;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
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;
public SelectSecretKeyListAdapter(Activity activity, ListView parent) {
mParent = parent;
mDatabase = Apg.getDatabase().db();
mInflater = (LayoutInflater) parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
long now = new Date().getTime() / 1000;
mCursor = mDatabase.query(
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') ",
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);
}
@Override
public boolean isEnabled(int position) {
mCursor.moveToPosition(position);
return mCursor.getInt(4) > 0; // valid CAN_SIGN
}
@Override
public boolean hasStableIds() {
return true;
}
@Override
public int getCount() {
return mCursor.getCount();
}
@Override
public Object getItem(int position) {
return position;
}
@Override
public long getItemId(int position) {
mCursor.moveToPosition(position);
return mCursor.getLong(1); // MASTER_KEY_ID
}
@Override
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("" + Long.toHexString(masterKeyId & 0xffffffffL));
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;
}
}

View File

@ -0,0 +1,81 @@
package org.thialfihar.android.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() {
// TODO: I suppose we could read out the time left until the first cache entry
// expiration, then use that for the timer...
// 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;
}
}

View File

@ -16,7 +16,12 @@
package org.thialfihar.android.apg.provider;
public class Accounts extends Accounts1 {
private Accounts() {
}
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";
}

View File

@ -18,15 +18,12 @@ package org.thialfihar.android.apg.provider;
import java.util.HashMap;
import org.thialfihar.android.apg.Id;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.text.TextUtils;
@ -34,153 +31,190 @@ import android.text.TextUtils;
public class DataProvider extends ContentProvider {
public static final String AUTHORITY = "org.thialfihar.android.apg.provider";
private static final String DATABASE_NAME = "apg";
private static final int DATABASE_VERSION = 1;
private static final int PUBLIC_KEY_RINGS = 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_KEYS = 111;
private static final int PUBLIC_KEY_RING_KEY_RANK = 112;
private static final int PUBLIC_KEY_RING_USER_IDS = 121;
private static final int PUBLIC_KEY_RING_USER_ID_RANK = 122;
private static final int PUBLIC_KEYS = 101;
private static final int PUBLIC_KEY_ID = 102;
private static final int PUBLIC_KEY_BY_KEY_ID = 103;
private static final int SECRET_KEY_RINGS = 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_KEYS = 211;
private static final int SECRET_KEY_RING_KEY_RANK = 212;
private static final int SECRET_KEY_RING_USER_IDS = 221;
private static final int SECRET_KEY_RING_USER_ID_RANK = 222;
private static final int SECRET_KEYS = 201;
private static final int SECRET_KEY_ID = 202;
private static final int SECRET_KEY_BY_KEY_ID = 203;
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 int ACCOUNTS = 301;
private static final int ACCOUNT_ID = 302;
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 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 static final HashMap<String, String> mPublicKeysProjectionMap;
private static final HashMap<String, String> mSecretKeysProjectionMap;
private static final HashMap<String, String> mAccountsProjectionMap;
private DatabaseHelper mdbHelper;
private Database mDb;
static {
mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
mUriMatcher.addURI(DataProvider.AUTHORITY, "public_keys", PUBLIC_KEYS);
mUriMatcher.addURI(DataProvider.AUTHORITY, "public_keys/#", PUBLIC_KEY_ID);
mUriMatcher.addURI(DataProvider.AUTHORITY, "public_keys/key_id/*", PUBLIC_KEY_BY_KEY_ID);
mUriMatcher.addURI(AUTHORITY, "key_rings/public/key_id/*", PUBLIC_KEY_RING_BY_KEY_ID);
mUriMatcher.addURI(DataProvider.AUTHORITY, "secret_keys", SECRET_KEYS);
mUriMatcher.addURI(DataProvider.AUTHORITY, "secret_keys/#", SECRET_KEY_ID);
mUriMatcher.addURI(DataProvider.AUTHORITY, "secret_keys/key_id/*", SECRET_KEY_BY_KEY_ID);
mUriMatcher.addURI(AUTHORITY, "key_rings/public/*/keys", PUBLIC_KEY_RING_KEYS);
mUriMatcher.addURI(AUTHORITY, "key_rings/public/*/keys/#", PUBLIC_KEY_RING_KEY_RANK);
mUriMatcher.addURI(DataProvider.AUTHORITY, "accounts", ACCOUNTS);
mUriMatcher.addURI(DataProvider.AUTHORITY, "accounts/#", ACCOUNT_ID);
mUriMatcher.addURI(AUTHORITY, "key_rings/public/*/user_ids", PUBLIC_KEY_RING_USER_IDS);
mUriMatcher.addURI(AUTHORITY, "key_rings/public/*/user_ids/#", PUBLIC_KEY_RING_USER_ID_RANK);
mPublicKeysProjectionMap = new HashMap<String, String>();
mPublicKeysProjectionMap.put(PublicKeys._ID, PublicKeys._ID);
mPublicKeysProjectionMap.put(PublicKeys.KEY_ID, PublicKeys.KEY_ID);
mPublicKeysProjectionMap.put(PublicKeys.KEY_DATA, PublicKeys.KEY_DATA);
mPublicKeysProjectionMap.put(PublicKeys.WHO_ID, PublicKeys.WHO_ID);
mUriMatcher.addURI(AUTHORITY, "key_rings/public", PUBLIC_KEY_RINGS);
mUriMatcher.addURI(AUTHORITY, "key_rings/public/*", PUBLIC_KEY_RING_ID);
mSecretKeysProjectionMap = new HashMap<String, String>();
mSecretKeysProjectionMap.put(PublicKeys._ID, PublicKeys._ID);
mSecretKeysProjectionMap.put(PublicKeys.KEY_ID, PublicKeys.KEY_ID);
mSecretKeysProjectionMap.put(PublicKeys.KEY_DATA, PublicKeys.KEY_DATA);
mSecretKeysProjectionMap.put(PublicKeys.WHO_ID, PublicKeys.WHO_ID);
mUriMatcher.addURI(AUTHORITY, "key_rings/secret/key_id/*", SECRET_KEY_RING_BY_KEY_ID);
mAccountsProjectionMap = new HashMap<String, String>();
mAccountsProjectionMap.put(Accounts._ID, Accounts._ID);
mAccountsProjectionMap.put(Accounts.NAME, Accounts.NAME);
}
mUriMatcher.addURI(AUTHORITY, "key_rings/secret/*/keys", SECRET_KEY_RING_KEYS);
mUriMatcher.addURI(AUTHORITY, "key_rings/secret/*/keys/#", SECRET_KEY_RING_KEY_RANK);
/**
* This class helps open, create, and upgrade the database file.
*/
private static class DatabaseHelper extends SQLiteOpenHelper {
mUriMatcher.addURI(AUTHORITY, "key_rings/secret/*/user_ids", SECRET_KEY_RING_USER_IDS);
mUriMatcher.addURI(AUTHORITY, "key_rings/secret/*/user_ids/#", SECRET_KEY_RING_USER_ID_RANK);
DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE " + PublicKeys.TABLE_NAME + " (" +
PublicKeys._ID + " " + PublicKeys._ID_type + "," +
PublicKeys.KEY_ID + " " + PublicKeys.KEY_ID_type + ", " +
PublicKeys.KEY_DATA + " " + PublicKeys.KEY_DATA_type + ", " +
PublicKeys.WHO_ID + " " + PublicKeys.WHO_ID_type + ");");
db.execSQL("CREATE TABLE " + SecretKeys.TABLE_NAME + " (" +
SecretKeys._ID + " " + SecretKeys._ID_type + "," +
SecretKeys.KEY_ID + " " + SecretKeys.KEY_ID_type + ", " +
SecretKeys.KEY_DATA + " " + SecretKeys.KEY_DATA_type + ", " +
SecretKeys.WHO_ID + " " + SecretKeys.WHO_ID_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) {
// TODO: upgrade db if necessary, and do that in a clever way
}
mUriMatcher.addURI(AUTHORITY, "key_rings/secret", SECRET_KEY_RINGS);
mUriMatcher.addURI(AUTHORITY, "key_rings/secret/*", SECRET_KEY_RING_ID);
}
@Override
public boolean onCreate() {
mdbHelper = new DatabaseHelper(getContext());
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_RINGS:
case PUBLIC_KEY_RING_ID:
case PUBLIC_KEY_RING_BY_KEY_ID:
case PUBLIC_KEY_RING_KEYS:
case PUBLIC_KEY_RING_KEY_RANK:
case PUBLIC_KEY_RING_USER_IDS:
case PUBLIC_KEY_RING_USER_ID_RANK:
type = Id.database.type_public;
break;
case SECRET_KEY_RINGS:
case SECRET_KEY_RING_ID:
case SECRET_KEY_RING_BY_KEY_ID:
case SECRET_KEY_RING_KEYS:
case SECRET_KEY_RING_KEY_RANK:
case SECRET_KEY_RING_USER_IDS:
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_RINGS:
case SECRET_KEY_RINGS: {
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(MASTER_KEY_ID,
KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID);
projectionMap.put(USER_ID,
UserIds.TABLE_NAME + "." + UserIds.USER_ID);
switch (mUriMatcher.match(uri)) {
case PUBLIC_KEYS: {
qb.setTables(PublicKeys.TABLE_NAME);
qb.setProjectionMap(mPublicKeysProjectionMap);
break;
}
case PUBLIC_KEY_ID: {
qb.setTables(PublicKeys.TABLE_NAME);
qb.setProjectionMap(mPublicKeysProjectionMap);
qb.appendWhere(PublicKeys._ID + "=" + uri.getPathSegments().get(1));
case PUBLIC_KEY_RING_ID:
case SECRET_KEY_RING_ID: {
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(MASTER_KEY_ID,
KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID);
projectionMap.put(USER_ID,
UserIds.TABLE_NAME + "." + UserIds.USER_ID);
qb.appendWhere(" AND " +
KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID + " = ");
qb.appendWhereEscapeString(uri.getPathSegments().get(2));
break;
}
case PUBLIC_KEY_BY_KEY_ID: {
qb.setTables(PublicKeys.TABLE_NAME);
qb.setProjectionMap(mPublicKeysProjectionMap);
qb.appendWhere(PublicKeys.KEY_ID + "=" + uri.getPathSegments().get(2));
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') ");
case SECRET_KEYS: {
qb.setTables(SecretKeys.TABLE_NAME);
qb.setProjectionMap(mSecretKeysProjectionMap);
break;
}
projectionMap.put(MASTER_KEY_ID,
KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID);
projectionMap.put(USER_ID,
UserIds.TABLE_NAME + "." + UserIds.USER_ID);
case SECRET_KEY_ID: {
qb.setTables(SecretKeys.TABLE_NAME);
qb.setProjectionMap(mSecretKeysProjectionMap);
qb.appendWhere(SecretKeys._ID + "=" + uri.getPathSegments().get(1));
break;
}
qb.appendWhere(" AND tmp." + Keys.KEY_ID + " = ");
qb.appendWhereEscapeString(uri.getPathSegments().get(3));
case SECRET_KEY_BY_KEY_ID: {
qb.setTables(SecretKeys.TABLE_NAME);
qb.setProjectionMap(mSecretKeysProjectionMap);
qb.appendWhere(SecretKeys.KEY_ID + "=" + uri.getPathSegments().get(2));
break;
}
case ACCOUNTS: {
qb.setTables(Accounts.TABLE_NAME);
qb.setProjectionMap(mAccountsProjectionMap);
break;
}
case ACCOUNT_ID: {
qb.setTables(Accounts.TABLE_NAME);
qb.setProjectionMap(mAccountsProjectionMap);
qb.appendWhere(Accounts._ID + "=" + uri.getPathSegments().get(1));
break;
}
@ -189,20 +223,20 @@ public class DataProvider extends ContentProvider {
}
}
qb.setProjectionMap(projectionMap);
// If no sort order is specified use the default
String orderBy;
if (TextUtils.isEmpty(sortOrder)) {
orderBy = PublicKeys.DEFAULT_SORT_ORDER;
orderBy = null;
} else {
orderBy = sortOrder;
}
// Get the database and run the query
SQLiteDatabase db = mdbHelper.getReadableDatabase();
Cursor c = qb.query(db, projection, selection, selectionArgs, null, null, orderBy);
//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
// Tell the cursor what uri to watch, so it knows when its source data changes
c.setNotificationUri(getContext().getContentResolver(), uri);
return c;
}
@ -210,278 +244,68 @@ public class DataProvider extends ContentProvider {
@Override
public String getType(Uri uri) {
switch (mUriMatcher.match(uri)) {
case PUBLIC_KEYS: {
return PublicKeys.CONTENT_TYPE;
}
case PUBLIC_KEY_RINGS:
return PUBLIC_KEY_RING_CONTENT_DIR_TYPE;
case PUBLIC_KEY_ID: {
return PublicKeys.CONTENT_ITEM_TYPE;
}
case PUBLIC_KEY_RING_ID:
return PUBLIC_KEY_RING_CONTENT_ITEM_TYPE;
case PUBLIC_KEY_BY_KEY_ID: {
return PublicKeys.CONTENT_ITEM_TYPE;
}
case PUBLIC_KEY_RING_BY_KEY_ID:
return PUBLIC_KEY_RING_CONTENT_ITEM_TYPE;
case SECRET_KEYS: {
return SecretKeys.CONTENT_TYPE;
}
case PUBLIC_KEY_RING_KEYS:
return PUBLIC_KEY_CONTENT_DIR_TYPE;
case SECRET_KEY_ID: {
return SecretKeys.CONTENT_ITEM_TYPE;
}
case PUBLIC_KEY_RING_KEY_RANK:
return PUBLIC_KEY_CONTENT_ITEM_TYPE;
case SECRET_KEY_BY_KEY_ID: {
return SecretKeys.CONTENT_ITEM_TYPE;
}
case PUBLIC_KEY_RING_USER_IDS:
return USER_ID_CONTENT_DIR_TYPE;
case ACCOUNTS: {
return Accounts.CONTENT_TYPE;
}
case PUBLIC_KEY_RING_USER_ID_RANK:
return USER_ID_CONTENT_ITEM_TYPE;
case ACCOUNT_ID: {
return Accounts.CONTENT_ITEM_TYPE;
}
case SECRET_KEY_RINGS:
return SECRET_KEY_RING_CONTENT_DIR_TYPE;
default: {
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_KEYS:
return SECRET_KEY_CONTENT_DIR_TYPE;
case SECRET_KEY_RING_KEY_RANK:
return SECRET_KEY_CONTENT_ITEM_TYPE;
case SECRET_KEY_RING_USER_IDS:
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) {
switch (mUriMatcher.match(uri)) {
case PUBLIC_KEYS: {
ContentValues values;
if (initialValues != null) {
values = new ContentValues(initialValues);
} else {
values = new ContentValues();
}
if (!values.containsKey(PublicKeys.WHO_ID)) {
values.put(PublicKeys.WHO_ID, "");
}
SQLiteDatabase db = mdbHelper.getWritableDatabase();
long rowId = db.insert(PublicKeys.TABLE_NAME, PublicKeys.WHO_ID, values);
if (rowId > 0) {
Uri transferUri = ContentUris.withAppendedId(PublicKeys.CONTENT_URI, rowId);
getContext().getContentResolver().notifyChange(transferUri, null);
return transferUri;
}
throw new SQLException("Failed to insert row into " + uri);
}
case SECRET_KEYS: {
ContentValues values;
if (initialValues != null) {
values = new ContentValues(initialValues);
} else {
values = new ContentValues();
}
if (!values.containsKey(SecretKeys.WHO_ID)) {
values.put(SecretKeys.WHO_ID, "");
}
SQLiteDatabase db = mdbHelper.getWritableDatabase();
long rowId = db.insert(SecretKeys.TABLE_NAME, SecretKeys.WHO_ID, values);
if (rowId > 0) {
Uri transferUri = ContentUris.withAppendedId(SecretKeys.CONTENT_URI, rowId);
getContext().getContentResolver().notifyChange(transferUri, null);
return transferUri;
}
throw new SQLException("Failed to insert row into " + uri);
}
case ACCOUNTS: {
ContentValues values;
if (initialValues != null) {
values = new ContentValues(initialValues);
} else {
values = new ContentValues();
}
SQLiteDatabase db = mdbHelper.getWritableDatabase();
long rowId = db.insert(Accounts.TABLE_NAME, null, values);
if (rowId > 0) {
Uri transferUri = ContentUris.withAppendedId(Accounts.CONTENT_URI, rowId);
getContext().getContentResolver().notifyChange(transferUri, null);
return transferUri;
}
throw new SQLException("Failed to insert row into " + uri);
}
default: {
throw new IllegalArgumentException("Unknown URI " + uri);
}
}
// not supported
return null;
}
@Override
public int delete(Uri uri, String where, String[] whereArgs) {
SQLiteDatabase db = mdbHelper.getWritableDatabase();
int count;
switch (mUriMatcher.match(uri)) {
case PUBLIC_KEYS: {
count = db.delete(PublicKeys.TABLE_NAME, where, whereArgs);
break;
}
case PUBLIC_KEY_ID: {
String publicKeyId = uri.getPathSegments().get(1);
count = db.delete(PublicKeys.TABLE_NAME,
PublicKeys._ID + "=" + publicKeyId +
(!TextUtils.isEmpty(where) ?
" AND (" + where + ')' : ""),
whereArgs);
break;
}
case PUBLIC_KEY_BY_KEY_ID: {
String publicKeyKeyId = uri.getPathSegments().get(2);
count = db.delete(PublicKeys.TABLE_NAME,
PublicKeys.KEY_ID + "=" + publicKeyKeyId +
(!TextUtils.isEmpty(where) ?
" AND (" + where + ')' : ""),
whereArgs);
break;
}
case SECRET_KEYS: {
count = db.delete(SecretKeys.TABLE_NAME, where, whereArgs);
break;
}
case SECRET_KEY_ID: {
String secretKeyId = uri.getPathSegments().get(1);
count = db.delete(SecretKeys.TABLE_NAME,
SecretKeys._ID + "=" + secretKeyId +
(!TextUtils.isEmpty(where) ?
" AND (" + where + ')' : ""),
whereArgs);
break;
}
case SECRET_KEY_BY_KEY_ID: {
String secretKeyKeyId = uri.getPathSegments().get(2);
count = db.delete(SecretKeys.TABLE_NAME,
SecretKeys.KEY_ID + "=" + secretKeyKeyId +
(!TextUtils.isEmpty(where) ?
" AND (" + where + ')' : ""),
whereArgs);
break;
}
case ACCOUNTS: {
count = db.delete(Accounts.TABLE_NAME, where, whereArgs);
break;
}
case ACCOUNT_ID: {
String accountId = uri.getPathSegments().get(1);
count = db.delete(Accounts.TABLE_NAME,
Accounts._ID + "=" + accountId +
(!TextUtils.isEmpty(where) ?
" AND (" + where + ')' : ""),
whereArgs);
break;
}
default: {
throw new IllegalArgumentException("Unknown URI " + uri);
}
}
getContext().getContentResolver().notifyChange(uri, null);
return count;
// not supported
return 0;
}
@Override
public int update(Uri uri, ContentValues values, String where, String[] whereArgs) {
SQLiteDatabase db = mdbHelper.getWritableDatabase();
int count;
switch (mUriMatcher.match(uri)) {
case PUBLIC_KEYS: {
count = db.update(PublicKeys.TABLE_NAME, values, where, whereArgs);
break;
}
case PUBLIC_KEY_ID: {
String publicKeyId = uri.getPathSegments().get(1);
count = db.update(PublicKeys.TABLE_NAME, values,
PublicKeys._ID + "=" + publicKeyId +
(!TextUtils.isEmpty(where) ?
" AND (" + where + ')' : ""),
whereArgs);
break;
}
case PUBLIC_KEY_BY_KEY_ID: {
String publicKeyKeyId = uri.getPathSegments().get(2);
count = db.update(PublicKeys.TABLE_NAME, values,
PublicKeys.KEY_ID + "=" + publicKeyKeyId +
(!TextUtils.isEmpty(where) ?
" AND (" + where + ')' : ""),
whereArgs);
break;
}
case SECRET_KEYS: {
count = db.update(SecretKeys.TABLE_NAME, values, where, whereArgs);
break;
}
case SECRET_KEY_ID: {
String secretKeyId = uri.getPathSegments().get(1);
count = db.update(SecretKeys.TABLE_NAME, values,
SecretKeys._ID + "=" + secretKeyId +
(!TextUtils.isEmpty(where) ?
" AND (" + where + ')' : ""),
whereArgs);
break;
}
case SECRET_KEY_BY_KEY_ID: {
String secretKeyKeyId = uri.getPathSegments().get(2);
count = db.update(SecretKeys.TABLE_NAME, values,
SecretKeys.KEY_ID + "=" + secretKeyKeyId +
(!TextUtils.isEmpty(where) ?
" AND (" + where + ')' : ""),
whereArgs);
break;
}
case ACCOUNTS: {
count = db.update(Accounts.TABLE_NAME, values, where, whereArgs);
break;
}
case ACCOUNT_ID: {
String accountId = uri.getPathSegments().get(1);
count = db.update(Accounts.TABLE_NAME, values,
Accounts._ID + "=" + accountId +
(!TextUtils.isEmpty(where) ?
" AND (" + where + ')' : ""),
whereArgs);
break;
}
default: {
throw new IllegalArgumentException("Unknown URI " + uri);
}
}
getContext().getContentResolver().notifyChange(uri, null);
return count;
// not supported
return 0;
}
}

View File

@ -0,0 +1,605 @@
package org.thialfihar.android.apg.provider;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.Vector;
import org.bouncycastle2.openpgp.PGPException;
import org.bouncycastle2.openpgp.PGPPublicKey;
import org.bouncycastle2.openpgp.PGPPublicKeyRing;
import org.bouncycastle2.openpgp.PGPSecretKey;
import org.bouncycastle2.openpgp.PGPSecretKeyRing;
import org.thialfihar.android.apg.Apg;
import org.thialfihar.android.apg.Id;
import org.thialfihar.android.apg.utils.IterableIterator;
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;
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;
}
}

View File

@ -16,21 +16,18 @@
package org.thialfihar.android.apg.provider;
import android.net.Uri;
import android.provider.BaseColumns;
class Accounts1 implements BaseColumns {
public static final String TABLE_NAME = "accounts";
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 NAME = "c_name";
public static final String NAME_type = "TEXT";
public static final Uri CONTENT_URI =
Uri.parse("content://" + DataProvider.AUTHORITY + "/accounts");
public static final String CONTENT_TYPE =
"vnd.android.cursor.dir/vnd.thialfihar.apg.account";
public static final String CONTENT_ITEM_TYPE =
"vnd.android.cursor.item/vnd.thialfihar.apg.account";
public static final String DEFAULT_SORT_ORDER = _ID + " DESC";
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";
}

View File

@ -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.thialfihar.android.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";
}

View File

@ -1,22 +0,0 @@
/*
* 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.thialfihar.android.apg.provider;
public class PublicKeys extends PublicKeys1 {
private PublicKeys() {
}
}

View File

@ -1,42 +0,0 @@
/*
* 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.thialfihar.android.apg.provider;
import android.net.Uri;
import android.provider.BaseColumns;
class PublicKeys1 implements BaseColumns {
public static final String TABLE_NAME = "public_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 KEY_DATA = "c_key_data";
public static final String KEY_DATA_type = "BLOB";
public static final String WHO_ID = "c_who_id";
public static final String WHO_ID_type = "INTEGER";
public static final Uri CONTENT_URI =
Uri.parse("content://" + DataProvider.AUTHORITY + "/public_keys");
public static final Uri CONTENT_URI_BY_KEY_ID =
Uri.parse("content://" + DataProvider.AUTHORITY + "/public_keys/key_id");
public static final String CONTENT_TYPE =
"vnd.android.cursor.dir/vnd.thialfihar.apg.public_key";
public static final String CONTENT_ITEM_TYPE =
"vnd.android.cursor.item/vnd.thialfihar.apg.public_key";
public static final String DEFAULT_SORT_ORDER = _ID + " DESC";
}

View File

@ -1,42 +0,0 @@
/*
* 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.thialfihar.android.apg.provider;
import android.net.Uri;
import android.provider.BaseColumns;
class SecretKeys1 implements BaseColumns {
public static final String TABLE_NAME = "secret_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 KEY_DATA = "c_key_data";
public static final String KEY_DATA_type = "BLOB";
public static final String WHO_ID = "c_who_id";
public static final String WHO_ID_type = "INTEGER";
public static final Uri CONTENT_URI =
Uri.parse("content://" + DataProvider.AUTHORITY + "/secret_keys");
public static final Uri CONTENT_URI_BY_KEY_ID =
Uri.parse("content://" + DataProvider.AUTHORITY + "/secret_keys/key_id");
public static final String CONTENT_TYPE =
"vnd.android.cursor.dir/vnd.thialfihar.apg.secret_key";
public static final String CONTENT_ITEM_TYPE =
"vnd.android.cursor.item/vnd.thialfihar.apg.secret_key";
public static final String DEFAULT_SORT_ORDER = _ID + " DESC";
}

View File

@ -16,7 +16,16 @@
package org.thialfihar.android.apg.provider;
public class SecretKeys extends SecretKeys1 {
private SecretKeys() {
}
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";
}

View File

@ -233,7 +233,7 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener {
}
public GregorianCalendar getExpiryDate() {
return mExpiryDate;
return mExpiryDate;
}
public int getUsage() {

View File

@ -76,7 +76,7 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor
}
}
String error = data.getString("error");
String error = data.getString(Apg.EXTRA_ERROR);
if (error != null) {
Toast.makeText(getContext(),
getContext().getString(R.string.errorMessage, error),
@ -310,24 +310,24 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor
mNewKeySize, passPhrase,
masterKey);
} catch (NoSuchProviderException e) {
error = e.getMessage();
error = "" + e;
} catch (NoSuchAlgorithmException e) {
error = e.getMessage();
error = "" + e;
} catch (PGPException e) {
error = e.getMessage();
error = "" + e;
} catch (InvalidParameterException e) {
error = e.getMessage();
error = "" + e;
} catch (InvalidAlgorithmParameterException e) {
error = e.getMessage();
error = "" + e;
} catch (Apg.GeneralException e) {
error = e.getMessage();
error = "" + e;
}
Message message = new Message();
Bundle data = new Bundle();
data.putBoolean("closeProgressDialog", true);
if (error != null) {
data.putString("error", error);
data.putString(Apg.EXTRA_ERROR, error);
} else {
data.putBoolean("gotNewKey", true);
}