diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 03a653c66..f9a9e3f13 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -17,7 +17,7 @@ + android:versionCode="15" android:versionName="1.0.1"> + android:configChanges="keyboardHidden|orientation|keyboard"> + + + + + + + + + android:configChanges="keyboardHidden|orientation|keyboard"> + + + + + + + + + + + @@ -92,6 +111,9 @@ + + + @@ -106,13 +128,22 @@ android:label="@string/title_preferences" android:configChanges="keyboardHidden|orientation|keyboard"/> + + + android:authorities="org.thialfihar.android.apg.provider"/> - - - \ No newline at end of file + + + + + + diff --git a/res/layout/decrypt.xml b/res/layout/decrypt.xml index 03e2f6311..41bbee479 100644 --- a/res/layout/decrypt.xml +++ b/res/layout/decrypt.xml @@ -77,6 +77,7 @@ diff --git a/res/layout/select_public_key_item.xml b/res/layout/select_public_key_item.xml index bb0dd30a4..beca23176 100644 --- a/res/layout/select_public_key_item.xml +++ b/res/layout/select_public_key_item.xml @@ -69,20 +69,6 @@ android:layout_width="wrap_content" android:layout_height="fill_parent"/> - - - - - - - - Import Keys Export Key Export Keys + Key Not Found User IDs Keys + General Defaults @@ -92,6 +94,9 @@ Hash Algorithm Public Key Pass Phrase + Pass Phrase Cache + Message Compression + File Compression Select 1 Selected @@ -108,9 +113,16 @@ not valid + None Sign only Encrypt only Sign and Encrypt + 15 secs + 1 min + 3 mins + 5 mins + 10 mins + until quit DSA ElGamal @@ -133,7 +145,7 @@ Using clipboard content. Key saved. Set a pass phrase via the option menu first. - OI File Manager not installed. + No compatible file manager installed. The pass phrases didn't match. Empty pass phrases are not allowed. Pass phrase for symmetric encryption: @@ -150,19 +162,20 @@ Please specify which file to encrypt to.\nWARNING! File will be overwritten if it exists. Please specify which file to decrypt to.\nWARNING! File will be overwritten if it exists. Specify the Google Mail account you want to add. - Please specify which file to import from. + Please specify which file to import keys from. (.asc or .gpg) Please specify which file to export to.\nWARNING! File will be overwritten if it exists. Please specify which file to export to.\nWARNING! You are about to export SECRET keys.\nWARNING! File will be overwritten if it exists. Do you really want to delete the key '%s'?\nYou can't undo this! Do you really want to delete the SECRET key '%s'?\nYou can't undo this! - Succssfully added %s keys and updated %s keys." - Succssfully added %s keys. - Succssfully updated %s keys. + Succssfully added %s key(s) and updated %s key(s)." + Succssfully added %s key(s). + Succssfully updated %s key(s). No keys added or updated. Succssfully exported 1 key. Succssfully exported %s keys. No keys exported. Note: only subkeys support ElGamal, and for ElGamal the nearest keysize of 1536, 2048, 3072, 4096, or 8192 will be used. + Couldn't find key %08X. @@ -227,4 +240,3 @@ verifying integrity... - diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml index 5bb4dc2ba..008981b0e 100644 --- a/res/values-ko/strings.xml +++ b/res/values-ko/strings.xml @@ -38,10 +38,12 @@ Import Keys Export Key Export Keys + Key Not Found User IDs Keys + General Defaults @@ -92,6 +94,9 @@ Hash Algorithm Public Key Pass Phrase + Pass Phrase Cache + Message Compression + File Compression Select 1 Selected @@ -108,9 +113,16 @@ not valid + None Sign only Encrypt only Sign and Encrypt + 15 secs + 1 min + 3 mins + 5 mins + 10 mins + until quit DSA ElGamal @@ -133,7 +145,7 @@ Using clipboard content. Key saved. Set a pass phrase via the option menu first. - OI File Manager not installed. + No compatible file manager installed. The pass phrases didn't match. Empty pass phrases are not allowed. Pass phrase for symmetric encryption: @@ -150,19 +162,20 @@ Please specify which file to encrypt to.\nWARNING! File will be overwritten if it exists. Please specify which file to decrypt to.\nWARNING! File will be overwritten if it exists. Specify the Google Mail account you want to add. - Please specify which file to import from. + Please specify which file to import keys from. (.asc or .gpg) Please specify which file to export to.\nWARNING! File will be overwritten if it exists. Please specify which file to export to.\nWARNING! You are about to export SECRET keys.\nWARNING! File will be overwritten if it exists. Do you really want to delete the key '%s'?\nYou can't undo this! Do you really want to delete the SECRET key '%s'?\nYou can't undo this! - Succssfully added %s keys and updated %s keys." - Succssfully added %s keys. - Succssfully updated %s keys. + Succssfully added %s key(s) and updated %s key(s)." + Succssfully added %s key(s). + Succssfully updated %s key(s). No keys added or updated. Succssfully exported 1 key. Succssfully exported %s keys. No keys exported. Note: only subkeys support ElGamal, and for ElGamal the nearest keysize of 1536, 2048, 3072, 4096, or 8192 will be used. + Couldn't find key %08X. @@ -227,4 +240,3 @@ verifying integrity... - diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml index 5bb4dc2ba..008981b0e 100644 --- a/res/values-ru/strings.xml +++ b/res/values-ru/strings.xml @@ -38,10 +38,12 @@ Import Keys Export Key Export Keys + Key Not Found User IDs Keys + General Defaults @@ -92,6 +94,9 @@ Hash Algorithm Public Key Pass Phrase + Pass Phrase Cache + Message Compression + File Compression Select 1 Selected @@ -108,9 +113,16 @@ not valid + None Sign only Encrypt only Sign and Encrypt + 15 secs + 1 min + 3 mins + 5 mins + 10 mins + until quit DSA ElGamal @@ -133,7 +145,7 @@ Using clipboard content. Key saved. Set a pass phrase via the option menu first. - OI File Manager not installed. + No compatible file manager installed. The pass phrases didn't match. Empty pass phrases are not allowed. Pass phrase for symmetric encryption: @@ -150,19 +162,20 @@ Please specify which file to encrypt to.\nWARNING! File will be overwritten if it exists. Please specify which file to decrypt to.\nWARNING! File will be overwritten if it exists. Specify the Google Mail account you want to add. - Please specify which file to import from. + Please specify which file to import keys from. (.asc or .gpg) Please specify which file to export to.\nWARNING! File will be overwritten if it exists. Please specify which file to export to.\nWARNING! You are about to export SECRET keys.\nWARNING! File will be overwritten if it exists. Do you really want to delete the key '%s'?\nYou can't undo this! Do you really want to delete the SECRET key '%s'?\nYou can't undo this! - Succssfully added %s keys and updated %s keys." - Succssfully added %s keys. - Succssfully updated %s keys. + Succssfully added %s key(s) and updated %s key(s)." + Succssfully added %s key(s). + Succssfully updated %s key(s). No keys added or updated. Succssfully exported 1 key. Succssfully exported %s keys. No keys exported. Note: only subkeys support ElGamal, and for ElGamal the nearest keysize of 1536, 2048, 3072, 4096, or 8192 will be used. + Couldn't find key %08X. @@ -227,4 +240,3 @@ verifying integrity... - diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml new file mode 100644 index 000000000..8bb198ad6 --- /dev/null +++ b/res/values-sl/strings.xml @@ -0,0 +1,242 @@ + + + + + APG + + + Poštni nabiralnik + Upravljanje javnih ključev + Upravljanje zasebnih ključev + Izberi prejemnike + Izberi podpis + Šifriraj + Dešifriraj + Avtentikacija + Ustvari ključ + Uredi ključ + Nastavitve + Spremeni geslo + Določi geslo + "Pošlji e-pošto..." + Šifriraj v datoteko + Dešifriraj v datoteko + Dodaj račun + Uvozi ključe + Izvozi ključ + Izvozi ključe + Ključ ni bil najden + + + Uporabniške identitete + Ključi + Splošno + Privzete nastavitve + + + Šifriraj v odložišče + Šifriraj in pošlji + Šifriraj + Dešifriraj + Overi + Izberi prejemnike + Odgovori + Šifriraj sporočilo + Dešifriraj sporočilo + Šifriraj datoteko + Dešifriraj datoteko + Shrani + Prekliči + Izbriši + Brez + + + O programu + Dodaj GMail račun + Izbriši račun + Upravljanje javnih ključev + Upravljanje zasebnih ključev + Nastavitve + Spremeni geslo + Določi geslo + Uvozi ključe + Izvozi ključe + Izvozi ključ + Izbriši ključ + Ustvari ključ + Uredi ključ + + + Podpiši + Sporočilo + Datoteka + Geslo + Ponovi + Algoritem + ASCII Armour + Javni ključ(i) + Po šifriranju izbriši + Po dešifriranju izbriši + Šifrirni algoritem + Hash algoritem + Javni ključ + Geslo + Predpomnilnik gesel + Zgoščevanje sporočil + Zgoščevanje datotek + + Izberi + 1 izbran + Izbrani + <nepoznan> + <brez> + <brez ključa> + - + <nikoli> + + lahko šifrira + lahko podpiše + potečeno + neveljavno + + + Brez + Samo podpis + Samo šifriranje + Podpis in šifriranje + 15 sek + 1 min + 3 min + 5 min + 10 min + do izhoda + + DSA + ElGamal + RSA + + Odpri... + Shrani kot... + Izberi datoteko za šifriranje... + Izberi datoteko za dešifriranje... + Odpri + Shrani + + Opozorilo + Napaka + Opozorilo: %s + Napaka: %s + + + Napačno geslo. + Uporabljam vsebino odložišča. + Ključ shranjen. + Najprej preko menija možnosti določite geslo. + Nameščen ni noben združljiv upravitelj datotek. + Gesli se ne ujemata. + Prazna gesla niso dovoljena. + Geslo za simetrično enkripcijo: + Geslo za %s: + Ali ste prepričani, da želite izbrisati\n%s? + Uspešno izbrisano. + Najprej izberite datoteko. + Uspešno dešifrirano. + Uspešno šifrirano. + Uspešno šifrirano v odložišče. + Vstavite geslo dvakrat. + Izberite vsaj en šifrirni ključ. + Izberite vsaj en šifrirni ključ ali ključ za podpis. + Določite datoteko v katero želite šifrirati.\nPOZOR! Če ta datoteka že obstaja, bo prepisana. + Določite datoteko v katero želite dešifrirati.\nPOZOR! Če ta datoteka že obstaja, bo prepisana. + Določite Google Mail račun, ki ga želite dodati. + Določite iz katere datoteke želite uvoziti ključe. (.asc ali .gpg) + Določite v katero datoteko želite izvoziti.\nPOZOR! Če ta datoteka že obstaja, bo prepisana. + Določite v katero datoteko želite izvoziti.\nPOZOR! Izvozili boste ZASEBNI ključ.\nPOZOR! Če ta datoteka že obstaja, bo prepisana. + Ali zares želite izbrisati ključ '%s'?\nTega ne boste mogli popraviti! + Ali zares želite izbrisati ZASEBNI ključ '%s'?\nTega ne boste mogli popraviti! + Uspešno dodani ključi: %s. Uspešno posodobljeni ključi: %s." + Uspešno dodani ključi: %s. + Uspešno posodobljeni ključi: %s. + Noben ključ ni bil dodan ali posodobljen. + Uspešno izvožen 1 ključ. + Uspešno izvoženi ključi: %s + Noben ključ ni bil izvožen. + Opomba: le podključi podpirajo ElGamal. Za ElGamal bo uporabljena velikost najbližja 1536, 2048, 3072, 4096, ali 8192. + Ne najdem ključa %08X. + + + izbris '%s' ni uspel + ne najdem datoteke + najden ni bil noben ustrezen zasebni kluč + najdena ni bila nobena poznana vrsta enkripcije + zunanji pomnilnik ni pripravljen + račun '%s' ni najden + dodajanje računa '%s' ni uspelo + neveljaven e-naslov '%s' + velikost ključa mora biti vsaj 512bit + statični ključ ne more biti ključ ElGamal + neznana izbira algoritma + določiti morate ime + določiti morate naslov e-pošte + potrebujem vsaj eno uporabniško identiteto + glavna uporabniška identiteta ne more biti prazna + potrebujem vsaj statični ključ + datum poteka mora biti kasnejši od datuma nastanka + dan ni bil noben šifrirni ključ ali geslo + podpis ni bil uspešen + dano ni bilo nobeno geslo + dan ni bil noben podpisni ključ + neveljavni šifrirni podatki + pokvarjeni podatki + ne najdem podatkov s simetrično enkripcijo + napačno geslo + napaka pri shranjevanju nakaterih ključev + + + končano. + inicializiram... + shranjujem... + uvažam... + izvažam... + generiram ključ, to lahko traja nekaj časa... + gradim ključ... + pripravljam statični ključ... + potrjujem statični ključ... + gradim datoteko s statičnimi ključi... + dodajam podključe... + shranjujem datoteko s ključi... + uvažam zasebne ključe... + uvažam javne ključe... + reloading keys... + izvažam ključ... + izvažam ključe... + izvlačim podpisni kluč... + izvlačim ključ... + pripravljam tok... + šifriram podatke... + dešifriram podatke... + pripravljam podpis... + generiram podpis... + obdelujem podpis... + overovljam podpis... + podpisujem... + berem podatke... + iščem ključ... + raztezam podatke... + overovljam integriteto... + + diff --git a/res/values/strings.xml b/res/values/strings.xml index 856ed0c4e..72c1d1f9c 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -38,6 +38,7 @@ Import Keys Export Key Export Keys + Key Not Found User IDs @@ -144,7 +145,7 @@ Using clipboard content. Key saved. Set a pass phrase via the option menu first. - OI File Manager not installed. + No compatible file manager installed. The pass phrases didn't match. Empty pass phrases are not allowed. Pass phrase for symmetric encryption: @@ -161,19 +162,20 @@ Please specify which file to encrypt to.\nWARNING! File will be overwritten if it exists. Please specify which file to decrypt to.\nWARNING! File will be overwritten if it exists. Specify the Google Mail account you want to add. - Please specify which file to import from. + Please specify which file to import keys from. (.asc or .gpg) Please specify which file to export to.\nWARNING! File will be overwritten if it exists. Please specify which file to export to.\nWARNING! You are about to export SECRET keys.\nWARNING! File will be overwritten if it exists. Do you really want to delete the key '%s'?\nYou can't undo this! Do you really want to delete the SECRET key '%s'?\nYou can't undo this! - Succssfully added %s keys and updated %s keys." - Succssfully added %s keys. - Succssfully updated %s keys. + Succssfully added %s key(s) and updated %s key(s)." + Succssfully added %s key(s). + Succssfully updated %s key(s). No keys added or updated. Succssfully exported 1 key. Succssfully exported %s keys. No keys exported. Note: only subkeys support ElGamal, and for ElGamal the nearest keysize of 1536, 2048, 3072, 4096, or 8192 will be used. + Couldn't find key %08X. @@ -237,5 +239,9 @@ decompressing data... verifying integrity... + + Read key details from APG. + Read of public and secret keys stored in APG, such as key ID and user IDs. The keys themselves can NOT be read. + diff --git a/src/org/openintents/intents/FileManager.java b/src/org/openintents/intents/FileManager.java deleted file mode 100644 index 3a5cc0d86..000000000 --- a/src/org/openintents/intents/FileManager.java +++ /dev/null @@ -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. - *

- * These specifiers extend the standard Android specifiers. - *

- */ -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. - * - *

- * Constant Value: "org.openintents.action.PICK_FILE" - *

- */ - 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. - * - *

- * Constant Value: "org.openintents.action.PICK_DIRECTORY" - *

- */ - public static final String ACTION_PICK_DIRECTORY = "org.openintents.action.PICK_DIRECTORY"; - - /** - * The title to display. - * - *

- * This is shown in the title bar of the file manager. - *

- * - *

- * Constant Value: "org.openintents.extra.TITLE" - *

- */ - public static final String EXTRA_TITLE = "org.openintents.extra.TITLE"; - - /** - * The text on the button to display. - * - *

- * Depending on the use, it makes sense to set this to "Open" or "Save". - *

- * - *

- * Constant Value: "org.openintents.extra.BUTTON_TEXT" - *

- */ - public static final String EXTRA_BUTTON_TEXT = "org.openintents.extra.BUTTON_TEXT"; - -} diff --git a/src/org/thialfihar/android/apg/Apg.java b/src/org/thialfihar/android/apg/Apg.java index e290a501e..3c35064a4 100644 --- a/src/org/thialfihar/android/apg/Apg.java +++ b/src/org/thialfihar/android/apg/Apg.java @@ -19,6 +19,7 @@ package org.thialfihar.android.apg; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.EOFException; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -35,8 +36,6 @@ import java.security.SecureRandom; import java.security.Security; import java.security.SignatureException; import java.util.Calendar; -import java.util.Collections; -import java.util.Comparator; import java.util.Date; import java.util.GregorianCalendar; import java.util.HashMap; @@ -80,18 +79,19 @@ import org.bouncycastle2.openpgp.PGPSignatureList; import org.bouncycastle2.openpgp.PGPSignatureSubpacketGenerator; import org.bouncycastle2.openpgp.PGPSignatureSubpacketVector; import org.bouncycastle2.openpgp.PGPUtil; -import org.thialfihar.android.apg.provider.PublicKeys; -import org.thialfihar.android.apg.provider.SecretKeys; +import org.thialfihar.android.apg.provider.Database; +import org.thialfihar.android.apg.provider.KeyRings; +import org.thialfihar.android.apg.provider.Keys; +import org.thialfihar.android.apg.provider.UserIds; import org.thialfihar.android.apg.ui.widget.KeyEditor; import org.thialfihar.android.apg.ui.widget.SectionView; import org.thialfihar.android.apg.ui.widget.UserIdEditor; import org.thialfihar.android.apg.utils.IterableIterator; import android.app.Activity; -import android.content.ContentValues; import android.content.Context; import android.database.Cursor; -import android.net.Uri; +import android.database.sqlite.SQLiteDatabase; import android.os.Bundle; import android.os.Environment; import android.view.ViewGroup; @@ -102,9 +102,35 @@ public class Apg { public static final String ENCRYPT = "org.thialfihar.android.apg.intent.ENCRYPT"; public static final String DECRYPT_FILE = "org.thialfihar.android.apg.intent.DECRYPT_FILE"; public static final String ENCRYPT_FILE = "org.thialfihar.android.apg.intent.ENCRYPT_FILE"; + public static final String DECRYPT_AND_RETURN = "org.thialfihar.android.apg.intent.DECRYPT_AND_RETURN"; + public static final String ENCRYPT_AND_RETURN = "org.thialfihar.android.apg.intent.ENCRYPT_AND_RETURN"; + public static final String SELECT_PUBLIC_KEYS = "org.thialfihar.android.apg.intent.SELECT_PUBLIC_KEYS"; + public static final String SELECT_SECRET_KEY = "org.thialfihar.android.apg.intent.SELECT_SECRET_KEY"; } - public static String VERSION = "0.9.5"; + public static final String EXTRA_DATA = "data"; + public static final String EXTRA_STATUS = "status"; + public static final String EXTRA_ERROR = "error"; + public static final String EXTRA_DECRYPTED_MESSAGE = "decryptedMessage"; + public static final String EXTRA_ENCRYPTED_MESSAGE = "decryptedMessage"; + public static final String EXTRA_SIGNATURE = "signature"; + public static final String EXTRA_SIGNATURE_KEY_ID = "signatureKeyId"; + public static final String EXTRA_SIGNATURE_USER_ID = "signatureUserId"; + public static final String EXTRA_SIGNATURE_SUCCESS = "signatureSuccess"; + public static final String EXTRA_SIGNATURE_UNKNOWN = "signatureUnknown"; + public static final String EXTRA_USER_ID = "userId"; + public static final String EXTRA_KEY_ID = "keyId"; + public static final String EXTRA_REPLY_TO = "replyTo"; + public static final String EXTRA_SEND_TO = "sendTo"; + public static final String EXTRA_SUBJECT = "subject"; + public static final String EXTRA_ENCRYPTION_KEY_IDS = "encryptionKeyIds"; + public static final String EXTRA_SELECTION = "selection"; + public static final String EXTRA_MESSAGE = "message"; + public static final String EXTRA_PROGRESS = "progress"; + public static final String EXTRA_MAX = "max"; + public static final String EXTRA_ACCOUNT = "account"; + + public static String VERSION = "1.0.1"; public static String FULL_VERSION = "APG v" + VERSION; private static final int[] PREFERRED_SYMMETRIC_ALGORITHMS = @@ -125,41 +151,20 @@ public class Apg { CompressionAlgorithmTags.BZIP2, CompressionAlgorithmTags.ZIP }; - protected static Vector mPublicKeyRings = new Vector(); - protected static Vector mSecretKeyRings = new Vector(); - public static Pattern PGP_MESSAGE = Pattern.compile(".*?(-----BEGIN PGP MESSAGE-----.*?-----END PGP MESSAGE-----).*", Pattern.DOTALL); public static Pattern PGP_SIGNED_MESSAGE = - Pattern.compile(".*?(-----BEGIN PGP SIGNED MESSAGE-----.*?-----BEGIN PGP SIGNATURE-----.*?-----END PGP SIGNATURE-----).*", - Pattern.DOTALL); - - protected static boolean mInitialized = false; - - protected static HashMap mSecretKeyIdToIdMap; - protected static HashMap mSecretKeyIdToKeyRingMap; - protected static HashMap mPublicKeyIdToIdMap; - protected static HashMap mPublicKeyIdToKeyRingMap; - - public static final String PUBLIC_KEY_PROJECTION[] = - new String[] { - PublicKeys._ID, - PublicKeys.KEY_ID, - PublicKeys.KEY_DATA, - PublicKeys.WHO_ID, }; - public static final String SECRET_KEY_PROJECTION[] = - new String[] { - PublicKeys._ID, - PublicKeys.KEY_ID, - PublicKeys.KEY_DATA, - PublicKeys.WHO_ID, }; + Pattern.compile(".*?(-----BEGIN PGP SIGNED MESSAGE-----.*?-----BEGIN PGP SIGNATURE-----.*?-----END PGP SIGNATURE-----).*", + Pattern.DOTALL); private static HashMap mPassPhraseCache = new HashMap(); private static String mEditPassPhrase = null; + private static Database mDatabase = null; + public static class GeneralException extends Exception { static final long serialVersionUID = 0xf812773342L; @@ -176,102 +181,14 @@ public class Apg { } } - static { - mPublicKeyRings = new Vector(); - mSecretKeyRings = new Vector(); - mSecretKeyIdToIdMap = new HashMap(); - mSecretKeyIdToKeyRingMap = new HashMap(); - mPublicKeyIdToIdMap = new HashMap(); - mPublicKeyIdToKeyRingMap = new HashMap(); - } - - public static void initialize(Activity context) { - if (mInitialized) { - return; - } - - 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 - } - } - - loadKeyRings(context, Id.type.public_key); - loadKeyRings(context, Id.type.secret_key); - - mInitialized = true; - } - - public static class PublicKeySorter implements Comparator { - @Override - public int compare(PGPPublicKeyRing object1, PGPPublicKeyRing object2) { - PGPPublicKey key1 = getMasterKey(object1); - PGPPublicKey key2 = getMasterKey(object2); - if (key1 == null && key2 == null) { - return 0; - } - - if (key1 == null) { - return -1; - } - - if (key2 == null) { - return 1; - } - - String uid1 = getMainUserId(key1); - String uid2 = getMainUserId(key2); - if (uid1 == null && uid2 == null) { - return 0; - } - - if (uid1 == null) { - return -1; - } - - if (uid2 == null) { - return 1; - } - - return uid1.compareTo(uid2); + public static void initialize(Context context) { + if (mDatabase == null) { + mDatabase = new Database(context); } } - public static class SecretKeySorter implements Comparator { - @Override - public int compare(PGPSecretKeyRing object1, PGPSecretKeyRing object2) { - PGPSecretKey key1 = getMasterKey(object1); - PGPSecretKey key2 = getMasterKey(object2); - if (key1 == null && key2 == null) { - return 0; - } - - if (key1 == null) { - return -1; - } - - if (key2 == null) { - return 1; - } - - String uid1 = getMainUserId(key1); - String uid2 = getMainUserId(key2); - if (uid1 == null && uid2 == null) { - return 0; - } - - if (uid1 == null) { - return -1; - } - - if (uid2 == null) { - return 1; - } - - return uid1.compareTo(uid2); - } + public static Database getDatabase() { + return mDatabase; } public static void setEditPassPhrase(String passPhrase) { @@ -289,7 +206,7 @@ public class Apg { public static String getCachedPassPhrase(long keyId) { long realId = keyId; if (realId != Id.key.symmetric) { - PGPSecretKeyRing keyRing = findSecretKeyRing(keyId); + PGPSecretKeyRing keyRing = getSecretKeyRing(keyId); if (keyRing == null) { return null; } @@ -308,19 +225,30 @@ public class Apg { return cpp.passPhrase; } - public static void cleanUpCache(int ttl) { + public static int cleanUpCache(int ttl, int initialDelay) { + int delay = initialDelay; + long realTtl = ttl * 1000; long now = new Date().getTime(); - Vector oldKeys = new Vector(); for (Map.Entry pair : mPassPhraseCache.entrySet()) { - if ((now - pair.getValue().timestamp) >= 1000 * ttl) { + long lived = now - pair.getValue().timestamp; + if (lived >= realTtl) { oldKeys.add(pair.getKey()); + } else { + // see, whether the remaining time for this cache entry improves our + // check delay + long nextCheck = realTtl - lived + 1000; + if (nextCheck < delay) { + delay = (int)nextCheck; + } } } for (long keyId : oldKeys) { mPassPhraseCache.remove(keyId); } + + return delay; } public static PGPSecretKey createKey(Context context, @@ -413,11 +341,10 @@ public class Apg { secretKey = (PGPSecretKey) it.next(); } - return secretKey; } - private static long getNumDatesBetween(GregorianCalendar first, GregorianCalendar second) { + private static long getNumDaysBetween(GregorianCalendar first, GregorianCalendar second) { GregorianCalendar tmp = new GregorianCalendar(); tmp.setTime(first.getTime()); long numDays = (second.getTimeInMillis() - first.getTimeInMillis()) / 1000 / 86400; @@ -434,7 +361,7 @@ public class Apg { String oldPassPhrase, String newPassPhrase, ProgressDialogUpdater progress) throws Apg.GeneralException, NoSuchProviderException, PGPException, - NoSuchAlgorithmException, SignatureException { + NoSuchAlgorithmException, SignatureException, IOException, Database.GeneralException { progress.setProgress(R.string.progress_buildingKey, 0, 100); @@ -465,7 +392,7 @@ public class Apg { } catch (UserIdEditor.NoEmailException e) { throw new Apg.GeneralException(context.getString(R.string.error_userIdNeedsAnEmailAddress)); } catch (UserIdEditor.InvalidEmailException e) { - throw new Apg.GeneralException(e.getMessage()); + throw new Apg.GeneralException("" + e); } if (userId.equals("")) { @@ -549,11 +476,12 @@ public class Apg { hashedPacketsGen.setPreferredHashAlgorithms(true, PREFERRED_HASH_ALGORITHMS); hashedPacketsGen.setPreferredCompressionAlgorithms(true, PREFERRED_COMPRESSION_ALGORITHMS); + // TODO: this doesn't work quite right yet if (keyEditor.getExpiryDate() != null) { GregorianCalendar creationDate = new GregorianCalendar(); creationDate.setTime(getCreationDate(masterKey)); GregorianCalendar expiryDate = keyEditor.getExpiryDate(); - long numDays = getNumDatesBetween(creationDate, expiryDate); + long numDays = getNumDaysBetween(creationDate, expiryDate); if (numDays <= 0) { throw new GeneralException(context.getString(R.string.error_expiryMustComeAfterCreation)); } @@ -600,11 +528,12 @@ public class Apg { } hashedPacketsGen.setKeyFlags(true, keyFlags); + // TODO: this doesn't work quite right yet if (keyEditor.getExpiryDate() != null) { GregorianCalendar creationDate = new GregorianCalendar(); creationDate.setTime(getCreationDate(masterKey)); GregorianCalendar expiryDate = keyEditor.getExpiryDate(); - long numDays = getNumDatesBetween(creationDate, expiryDate); + long numDays = getNumDaysBetween(creationDate, expiryDate); if (numDays <= 0) { throw new GeneralException(context.getString(R.string.error_expiryMustComeAfterCreation)); } @@ -619,79 +548,16 @@ public class Apg { PGPPublicKeyRing publicKeyRing = keyGen.generatePublicKeyRing(); progress.setProgress(R.string.progress_savingKeyRing, 90, 100); - saveKeyRing(context, secretKeyRing); - saveKeyRing(context, publicKeyRing); + mDatabase.saveKeyRing(secretKeyRing); + mDatabase.saveKeyRing(publicKeyRing); - loadKeyRings(context, Id.type.public_key); - loadKeyRings(context, Id.type.secret_key); progress.setProgress(R.string.progress_done, 100, 100); } - private static int saveKeyRing(Activity context, PGPPublicKeyRing keyRing) { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - ContentValues values = new ContentValues(); - - PGPPublicKey masterKey = getMasterKey(keyRing); - if (masterKey == null) { - return Id.return_value.no_master_key; - } - - try { - keyRing.encode(out); - out.close(); - } catch (IOException e) { - return Id.return_value.error; - } - - values.put(PublicKeys.KEY_ID, masterKey.getKeyID()); - values.put(PublicKeys.KEY_DATA, out.toByteArray()); - - Uri uri = Uri.withAppendedPath(PublicKeys.CONTENT_URI_BY_KEY_ID, "" + masterKey.getKeyID()); - Cursor cursor = context.managedQuery(uri, PUBLIC_KEY_PROJECTION, null, null, null); - if (cursor != null && cursor.getCount() > 0) { - context.getContentResolver().update(uri, values, null, null); - return Id.return_value.updated; - } else { - context.getContentResolver().insert(PublicKeys.CONTENT_URI, values); - return Id.return_value.ok; - } - } - - private static int saveKeyRing(Activity context, PGPSecretKeyRing keyRing) { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - ContentValues values = new ContentValues(); - - PGPSecretKey masterKey = getMasterKey(keyRing); - if (masterKey == null) { - return Id.return_value.no_master_key; - } - - try { - keyRing.encode(out); - out.close(); - } catch (IOException e) { - return Id.return_value.error; - } - - values.put(SecretKeys.KEY_ID, masterKey.getKeyID()); - values.put(SecretKeys.KEY_DATA, out.toByteArray()); - - Uri uri = Uri.withAppendedPath(SecretKeys.CONTENT_URI_BY_KEY_ID, "" + masterKey.getKeyID()); - Cursor cursor = context.managedQuery(uri, SECRET_KEY_PROJECTION, null, null, null); - if (cursor != null && cursor.getCount() > 0) { - context.getContentResolver().update(uri, values, null, null); - return Id.return_value.updated; - } else { - context.getContentResolver().insert(SecretKeys.CONTENT_URI, values); - return Id.return_value.ok; - } - } - public static Bundle importKeyRings(Activity context, int type, String filename, ProgressDialogUpdater progress) throws GeneralException, FileNotFoundException, PGPException, IOException { Bundle returnData = new Bundle(); - PGPObjectFactory objectFactory = null; if (type == Id.type.secret_key) { progress.setProgress(R.string.progress_importingSecretKeys, 0, 100); @@ -704,53 +570,61 @@ public class Apg { } FileInputStream fileIn = new FileInputStream(filename); - InputStream in = PGPUtil.getDecoderStream(fileIn); - objectFactory = new PGPObjectFactory(in); - - Vector objects = new Vector(); - Object obj = objectFactory.nextObject(); - while (obj != null) { - objects.add(obj); - obj = objectFactory.nextObject(); - } - + long fileSize = new File(filename).length(); + PositionAwareInputStream progressIn = new PositionAwareInputStream(fileIn); + // need to have access to the bufferedInput, so we can reuse it for the possible + // PGPObject chunks after the first one, e.g. files with several consecutive ASCII + // armour blocks + BufferedInputStream bufferedInput = new BufferedInputStream(progressIn); int newKeys = 0; int oldKeys = 0; - for (int i = 0; i < objects.size(); ++i) { - progress.setProgress(i * 100 / objects.size(), 100); - obj = objects.get(i); - PGPPublicKeyRing publicKeyRing; - PGPSecretKeyRing secretKeyRing; - int retValue; - - if (type == Id.type.secret_key) { - if (!(obj instanceof PGPSecretKeyRing)) { - continue; + try { + while (true) { + InputStream in = PGPUtil.getDecoderStream(bufferedInput); + PGPObjectFactory objectFactory = new PGPObjectFactory(in); + Object obj = objectFactory.nextObject(); + // if the first is already a null object, then we can stop trying + if (obj == null) { + break; } - secretKeyRing = (PGPSecretKeyRing) obj; - retValue = saveKeyRing(context, secretKeyRing); - } else { - if (!(obj instanceof PGPPublicKeyRing)) { - continue; + while (obj != null) { + PGPPublicKeyRing publicKeyRing; + PGPSecretKeyRing secretKeyRing; + // a return value that doesn't match any Id.return_value.* values, in case + // saveKeyRing is never called + int retValue = 2107; + + try { + if (type == Id.type.secret_key && obj instanceof PGPSecretKeyRing) { + secretKeyRing = (PGPSecretKeyRing) obj; + retValue = mDatabase.saveKeyRing(secretKeyRing); + } else if (type == Id.type.public_key && obj instanceof PGPPublicKeyRing) { + publicKeyRing = (PGPPublicKeyRing) obj; + retValue = mDatabase.saveKeyRing(publicKeyRing); + } + } catch (IOException e) { + retValue = Id.return_value.error; + } catch (Database.GeneralException e) { + retValue = Id.return_value.error; + } + + if (retValue == Id.return_value.error) { + throw new GeneralException(context.getString(R.string.error_savingKeys)); + } + + if (retValue == Id.return_value.updated) { + ++oldKeys; + } else if (retValue == Id.return_value.ok) { + ++newKeys; + } + progress.setProgress((int)(100 * progressIn.position() / fileSize), 100); + obj = objectFactory.nextObject(); } - publicKeyRing = (PGPPublicKeyRing) obj; - retValue = saveKeyRing(context, publicKeyRing); - } - - if (retValue == Id.return_value.error) { - throw new GeneralException(context.getString(R.string.error_savingKeys)); - } - - if (retValue == Id.return_value.updated) { - ++oldKeys; - } else if (retValue == Id.return_value.ok) { - ++newKeys; } + } catch (EOFException e) { + // nothing to do, we are done } - progress.setProgress(R.string.progress_reloadingKeys, 100, 100); - loadKeyRings(context, type); - returnData.putInt("added", newKeys); returnData.putInt("updated", oldKeys); @@ -759,12 +633,13 @@ public class Apg { return returnData; } - public static Bundle exportKeyRings(Activity context, Vector keys, String filename, + public static Bundle exportKeyRings(Activity context, Vector keyRingIds, + String filename, ProgressDialogUpdater progress) throws GeneralException, FileNotFoundException, PGPException, IOException { Bundle returnData = new Bundle(); - if (keys.size() == 1) { + if (keyRingIds.size() == 1) { progress.setProgress(R.string.progress_exportingKey, 0, 100); } else { progress.setProgress(R.string.progress_exportingKeys, 0, 100); @@ -777,9 +652,9 @@ public class Apg { ArmoredOutputStream out = new ArmoredOutputStream(fileOut); int numKeys = 0; - for (int i = 0; i < keys.size(); ++i) { - progress.setProgress(i * 100 / keys.size(), 100); - Object obj = keys.get(i); + for (int i = 0; i < keyRingIds.size(); ++i) { + progress.setProgress(i * 100 / keyRingIds.size(), 100); + Object obj = mDatabase.getKeyRing(keyRingIds.get(i)); PGPPublicKeyRing publicKeyRing; PGPSecretKeyRing secretKeyRing; @@ -803,61 +678,6 @@ public class Apg { return returnData; } - private static void loadKeyRings(Activity context, int type) { - Cursor cursor; - if (type == Id.type.secret_key) { - mSecretKeyRings.clear(); - mSecretKeyIdToIdMap.clear(); - mSecretKeyIdToKeyRingMap.clear(); - cursor = context.managedQuery(SecretKeys.CONTENT_URI, SECRET_KEY_PROJECTION, - null, null, null); - } else { - mPublicKeyRings.clear(); - mPublicKeyIdToIdMap.clear(); - mPublicKeyIdToKeyRingMap.clear(); - cursor = context.managedQuery(PublicKeys.CONTENT_URI, PUBLIC_KEY_PROJECTION, - null, null, null); - } - - for (int i = 0; i < cursor.getCount(); ++i) { - cursor.moveToPosition(i); - String sharedIdColumn = PublicKeys._ID; // same in both - String sharedKeyIdColumn = PublicKeys.KEY_ID; // same in both - String sharedKeyDataColumn = PublicKeys.KEY_DATA; // same in both - int idIndex = cursor.getColumnIndex(sharedIdColumn); - int keyIdIndex = cursor.getColumnIndex(sharedKeyIdColumn); - int keyDataIndex = cursor.getColumnIndex(sharedKeyDataColumn); - - byte keyData[] = cursor.getBlob(keyDataIndex); - int id = cursor.getInt(idIndex); - long keyId = cursor.getLong(keyIdIndex); - - try { - if (type == Id.type.secret_key) { - PGPSecretKeyRing key = new PGPSecretKeyRing(keyData); - mSecretKeyRings.add(key); - mSecretKeyIdToIdMap.put(keyId, id); - mSecretKeyIdToKeyRingMap.put(keyId, key); - } else { - PGPPublicKeyRing key = new PGPPublicKeyRing(keyData); - mPublicKeyRings.add(key); - mPublicKeyIdToIdMap.put(keyId, id); - mPublicKeyIdToKeyRingMap.put(keyId, key); - } - } catch (IOException e) { - // TODO: some error handling - } catch (PGPException e) { - // TODO: some error handling - } - } - - if (type == Id.type.secret_key) { - Collections.sort(mSecretKeyRings, new SecretKeySorter()); - } else { - Collections.sort(mPublicKeyRings, new PublicKeySorter()); - } - } - public static Date getCreationDate(PGPPublicKey key) { return key.getCreationTime(); } @@ -867,6 +687,9 @@ public class Apg { } public static PGPPublicKey getMasterKey(PGPPublicKeyRing keyRing) { + if (keyRing == null) { + return null; + } for (PGPPublicKey key : new IterableIterator(keyRing.getPublicKeys())) { if (key.isMasterKey()) { return key; @@ -877,6 +700,9 @@ public class Apg { } public static PGPSecretKey getMasterKey(PGPSecretKeyRing keyRing) { + if (keyRing == null) { + return null; + } for (PGPSecretKey key : new IterableIterator(keyRing.getSecretKeys())) { if (key.isMasterKey()) { return key; @@ -982,7 +808,7 @@ public class Apg { } public static PGPPublicKey getEncryptPublicKey(long masterKeyId) { - PGPPublicKeyRing keyRing = mPublicKeyIdToKeyRingMap.get(masterKeyId); + PGPPublicKeyRing keyRing = getPublicKeyRing(masterKeyId); if (keyRing == null) { return null; } @@ -994,7 +820,7 @@ public class Apg { } public static PGPSecretKey getSigningKey(long masterKeyId) { - PGPSecretKeyRing keyRing = mSecretKeyIdToKeyRingMap.get(masterKeyId); + PGPSecretKeyRing keyRing = getSecretKeyRing(masterKeyId); if (keyRing == null) { return null; } @@ -1035,14 +861,6 @@ public class Apg { return userId; } - public static PGPPublicKeyRing getPublicKeyRing(long keyId) { - return mPublicKeyIdToKeyRingMap.get(keyId); - } - - public static PGPSecretKeyRing getSecretKeyRing(long keyId) { - return mSecretKeyIdToKeyRingMap.get(keyId); - } - public static boolean isEncryptionKey(PGPPublicKey key) { if (!key.isEncryptionKey()) { return false; @@ -1122,9 +940,17 @@ public class Apg { } public static String getAlgorithmInfo(PGPPublicKey key) { + return getAlgorithmInfo(key.getAlgorithm(), key.getBitStrength()); + } + + public static String getAlgorithmInfo(PGPSecretKey key) { + return getAlgorithmInfo(key.getPublicKey()); + } + + public static String getAlgorithmInfo(int algorithm, int keySize) { String algorithmStr = null; - switch (key.getAlgorithm()) { + switch (algorithm) { case PGPPublicKey.RSA_ENCRYPT: case PGPPublicKey.RSA_GENERAL: case PGPPublicKey.RSA_SIGN: { @@ -1148,97 +974,121 @@ public class Apg { break; } } - return algorithmStr + ", " + key.getBitStrength() + "bit"; + return algorithmStr + ", " + keySize + "bit"; } - public static String getAlgorithmInfo(PGPSecretKey key) { - return getAlgorithmInfo(key.getPublicKey()); + public static void deleteKey(int keyRingId) { + mDatabase.deleteKeyRing(keyRingId); } - public static void deleteKey(Activity context, PGPPublicKeyRing keyRing) { - PGPPublicKey masterKey = getMasterKey(keyRing); - Uri uri = Uri.withAppendedPath(PublicKeys.CONTENT_URI_BY_KEY_ID, "" + masterKey.getKeyID()); - context.getContentResolver().delete(uri, null, null); - loadKeyRings(context, Id.type.public_key); + public static Object getKeyRing(int keyRingId) { + return mDatabase.getKeyRing(keyRingId); } - public static void deleteKey(Activity context, PGPSecretKeyRing keyRing) { - PGPSecretKey masterKey = getMasterKey(keyRing); - Uri uri = Uri.withAppendedPath(SecretKeys.CONTENT_URI_BY_KEY_ID, "" + masterKey.getKeyID()); - context.getContentResolver().delete(uri, null, null); - loadKeyRings(context, Id.type.secret_key); - } - - public static PGPPublicKey findPublicKey(long keyId) { - PGPPublicKey key = null; - for (int i = 0; i < mPublicKeyRings.size(); ++i) { - PGPPublicKeyRing keyRing = mPublicKeyRings.get(i); - try { - key = keyRing.getPublicKey(keyId); - if (key != null) { - return key; - } - } catch (PGPException e) { - // just not found, can ignore this - } + public static PGPSecretKeyRing getSecretKeyRing(long keyId) { + byte[] data = mDatabase.getKeyRingDataFromKeyId(Id.database.type_secret, keyId); + if (data == null) { + return null; + } + try { + return new PGPSecretKeyRing(data); + } catch (IOException e) { + // no good way to handle this, return null + // TODO: some info? + } catch (PGPException e) { + // no good way to handle this, return null + // TODO: some info? } return null; } - public static PGPSecretKey findSecretKey(long keyId) { - PGPSecretKey key = null; - for (int i = 0; i < mSecretKeyRings.size(); ++i) { - PGPSecretKeyRing keyRing = mSecretKeyRings.get(i); - key = keyRing.getSecretKey(keyId); - if (key != null) { - return key; - } + public static PGPPublicKeyRing getPublicKeyRing(long keyId) { + byte[] data = mDatabase.getKeyRingDataFromKeyId(Id.database.type_public, keyId); + if (data == null) { + return null; + } + try { + return new PGPPublicKeyRing(data); + } catch (IOException e) { + // no good way to handle this, return null + // TODO: some info? } return null; } - public static PGPSecretKeyRing findSecretKeyRing(long keyId) { - for (int i = 0; i < mSecretKeyRings.size(); ++i) { - PGPSecretKeyRing keyRing = mSecretKeyRings.get(i); - PGPSecretKey key = null; - key = keyRing.getSecretKey(keyId); - if (key != null) { - return keyRing; - } + public static PGPSecretKey getSecretKey(long keyId) { + PGPSecretKeyRing keyRing = getSecretKeyRing(keyId); + if (keyRing == null) { + return null; } - return null; + return keyRing.getSecretKey(keyId); } - public static PGPPublicKeyRing findPublicKeyRing(long keyId) { - for (int i = 0; i < mPublicKeyRings.size(); ++i) { - PGPPublicKeyRing keyRing = mPublicKeyRings.get(i); - PGPPublicKey key = null; - try { - key = keyRing.getPublicKey(keyId); - if (key != null) { - return keyRing; - } - } catch (PGPException e) { - // key not found - } + public static PGPPublicKey getPublicKey(long keyId) { + PGPPublicKeyRing keyRing = getPublicKeyRing(keyId); + if (keyRing == null) { + return null; + } + try { + return keyRing.getPublicKey(keyId); + } catch (PGPException e) { + return null; } - return null; } - public static PGPPublicKey getPublicMasterKey(long keyId) { - PGPPublicKey key = null; - for (int i = 0; i < mPublicKeyRings.size(); ++i) { - PGPPublicKeyRing keyRing = mPublicKeyRings.get(i); - try { - key = keyRing.getPublicKey(keyId); - if (key != null) { - return getMasterKey(keyRing); - } - } catch (PGPException e) { - // just not found, can ignore this - } + public static Vector getKeyRingIds(int type) { + SQLiteDatabase db = mDatabase.db(); + Vector keyIds = new Vector(); + Cursor c = db.query(KeyRings.TABLE_NAME, + new String[] { KeyRings._ID }, + KeyRings.TYPE + " = ?", new String[] { "" + type }, + null, null, null); + if (c != null && c.moveToFirst()) { + do { + keyIds.add(c.getInt(0)); + } while (c.moveToNext()); } - return null; + + if (c != null) { + c.close(); + } + + return keyIds; + } + + public static String getMainUserId(long keyId, int type) { + SQLiteDatabase db = mDatabase.db(); + Cursor c = db.query(Keys.TABLE_NAME + " INNER JOIN " + KeyRings.TABLE_NAME + " ON (" + + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " + + Keys.TABLE_NAME + "." + Keys.KEY_RING_ID + ") " + + " INNER JOIN " + Keys.TABLE_NAME + " AS masterKey ON (" + + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " + + "masterKey." + Keys.KEY_RING_ID + " AND " + + "masterKey." + Keys.IS_MASTER_KEY + " = '1') " + + " INNER JOIN " + UserIds.TABLE_NAME + " ON (" + + UserIds.TABLE_NAME + "." + UserIds.KEY_ID + " = " + + "masterKey." + Keys._ID + " AND " + + UserIds.TABLE_NAME + "." + UserIds.RANK + " = '0')", + new String[] { UserIds.USER_ID }, + Keys.TABLE_NAME + "." + Keys.KEY_ID + " = ? AND " + + KeyRings.TABLE_NAME + "." + KeyRings.TYPE + " = ?", + new String[] { + "" + keyId, + "" + type, + }, + null, null, null); + String userId = ""; + if (c != null && c.moveToFirst()) { + do { + userId = c.getString(0); + } while (c.moveToNext()); + } + + if (c != null) { + c.close(); + } + + return userId; } public static void encrypt(Context context, @@ -1277,7 +1127,7 @@ public class Apg { } if (signatureKeyId != 0) { - signingKeyRing = findSecretKeyRing(signatureKeyId); + signingKeyRing = getSecretKeyRing(signatureKeyId); signingKey = getSigningKey(signatureKeyId); if (signingKey == null) { throw new GeneralException(context.getString(R.string.error_signatureFailed)); @@ -1329,7 +1179,7 @@ public class Apg { if (compression == Id.choice.compression.none) { bcpgOut = new BCPGOutputStream(encryptOut); } else { - compressGen = new PGPCompressedDataGenerator(CompressionAlgorithmTags.ZLIB); + compressGen = new PGPCompressedDataGenerator(compression); bcpgOut = new BCPGOutputStream(compressGen.open(encryptOut)); } if (signatureKeyId != 0) { @@ -1393,7 +1243,7 @@ public class Apg { throw new GeneralException(context.getString(R.string.error_noSignatureKey)); } - signingKeyRing = findSecretKeyRing(signatureKeyId); + signingKeyRing = getSecretKeyRing(signatureKeyId); signingKey = getSigningKey(signatureKeyId); if (signingKey == null) { throw new GeneralException(context.getString(R.string.error_signatureFailed)); @@ -1479,7 +1329,7 @@ public class Apg { if (obj instanceof PGPPublicKeyEncryptedData) { gotAsymmetricEncryption = true; PGPPublicKeyEncryptedData pbe = (PGPPublicKeyEncryptedData) obj; - secretKey = findSecretKey(pbe.getKeyID()); + secretKey = getSecretKey(pbe.getKeyID()); if (secretKey != null) { break; } @@ -1532,6 +1382,9 @@ public class Apg { String passPhrase, ProgressDialogUpdater progress, boolean assumeSymmetric) throws IOException, GeneralException, PGPException, SignatureException { + if (passPhrase == null) { + passPhrase = ""; + } Bundle returnData = new Bundle(); InputStream in = PGPUtil.getDecoderStream(inStream); PGPObjectFactory pgpF = new PGPObjectFactory(in); @@ -1589,7 +1442,7 @@ public class Apg { Object obj = it.next(); if (obj instanceof PGPPublicKeyEncryptedData) { PGPPublicKeyEncryptedData encData = (PGPPublicKeyEncryptedData) obj; - secretKey = findSecretKey(encData.getKeyID()); + secretKey = getSecretKey(encData.getKeyID()); if (secretKey != null) { pbe = encData; break; @@ -1634,11 +1487,11 @@ public class Apg { if (dataChunk instanceof PGPOnePassSignatureList) { progress.setProgress(R.string.progress_processingSignature, currentProgress, 100); - returnData.putBoolean("signature", true); + returnData.putBoolean(EXTRA_SIGNATURE, true); PGPOnePassSignatureList sigList = (PGPOnePassSignatureList) dataChunk; for (int i = 0; i < sigList.size(); ++i) { signature = sigList.get(i); - signatureKey = findPublicKey(signature.getKeyID()); + signatureKey = getPublicKey(signature.getKeyID()); if (signatureKeyId == 0) { signatureKeyId = signature.getKeyID(); } @@ -1648,21 +1501,21 @@ public class Apg { signatureIndex = i; signatureKeyId = signature.getKeyID(); String userId = null; - PGPPublicKeyRing sigKeyRing = findPublicKeyRing(signatureKeyId); + PGPPublicKeyRing sigKeyRing = getPublicKeyRing(signatureKeyId); if (sigKeyRing != null) { userId = getMainUserId(getMasterKey(sigKeyRing)); } - returnData.putString("signatureUserId", userId); + returnData.putString(EXTRA_SIGNATURE_USER_ID, userId); break; } } - returnData.putLong("signatureKeyId", signatureKeyId); + returnData.putLong(EXTRA_SIGNATURE_KEY_ID, signatureKeyId); if (signature != null) { signature.initVerify(signatureKey, new BouncyCastleProvider()); } else { - returnData.putBoolean("signatureUnknown", true); + returnData.putBoolean(EXTRA_SIGNATURE_UNKNOWN, true); } dataChunk = plainFact.nextObject(); @@ -1694,7 +1547,7 @@ public class Apg { try { signature.update(buffer, 0, n); } catch (SignatureException e) { - returnData.putBoolean("signatureSuccess", false); + returnData.putBoolean(EXTRA_SIGNATURE_SUCCESS, false); signature = null; } } @@ -1714,9 +1567,9 @@ public class Apg { PGPSignatureList signatureList = (PGPSignatureList) plainFact.nextObject(); PGPSignature messageSignature = (PGPSignature) signatureList.get(signatureIndex); if (signature.verify(messageSignature)) { - returnData.putBoolean("signatureSuccess", true); + returnData.putBoolean(EXTRA_SIGNATURE_SUCCESS, true); } else { - returnData.putBoolean("signatureSuccess", false); + returnData.putBoolean(EXTRA_SIGNATURE_SUCCESS, false); } } } @@ -1769,7 +1622,7 @@ public class Apg { byte[] clearText = out.toByteArray(); outStream.write(clearText); - returnData.putBoolean("signature", true); + returnData.putBoolean(EXTRA_SIGNATURE, true); progress.setProgress(R.string.progress_processingSignature, 60, 100); PGPObjectFactory pgpFact = new PGPObjectFactory(aIn); @@ -1783,7 +1636,7 @@ public class Apg { PGPPublicKey signatureKey = null; for (int i = 0; i < sigList.size(); ++i) { signature = sigList.get(i); - signatureKey = findPublicKey(signature.getKeyID()); + signatureKey = getPublicKey(signature.getKeyID()); if (signatureKeyId == 0) { signatureKeyId = signature.getKeyID(); } @@ -1792,19 +1645,19 @@ public class Apg { } else { signatureKeyId = signature.getKeyID(); String userId = null; - PGPPublicKeyRing sigKeyRing = findPublicKeyRing(signatureKeyId); + PGPPublicKeyRing sigKeyRing = getPublicKeyRing(signatureKeyId); if (sigKeyRing != null) { userId = getMainUserId(getMasterKey(sigKeyRing)); } - returnData.putString("signatureUserId", userId); + returnData.putString(EXTRA_SIGNATURE_USER_ID, userId); break; } } - returnData.putLong("signatureKeyId", signatureKeyId); + returnData.putLong(EXTRA_SIGNATURE_KEY_ID, signatureKeyId); if (signature == null) { - returnData.putBoolean("signatureUnknown", true); + returnData.putBoolean(EXTRA_SIGNATURE_UNKNOWN, true); progress.setProgress(R.string.progress_done, 100, 100); return returnData; } @@ -1829,21 +1682,12 @@ public class Apg { while (lookAhead != -1); } - returnData.putBoolean("signatureSuccess", signature.verify()); + returnData.putBoolean(EXTRA_SIGNATURE_SUCCESS, signature.verify()); progress.setProgress(R.string.progress_done, 100, 100); return returnData; } - public static Vector getPublicKeyRings() { - return mPublicKeyRings; - } - - public static Vector getSecretKeyRings() { - return mSecretKeyRings; - } - - // taken from ClearSignedFileProcessor in BC private static int readInputLine(ByteArrayOutputStream bOut, InputStream fIn) throws IOException { diff --git a/src/org/thialfihar/android/apg/AskForSecretKeyPassPhrase.java b/src/org/thialfihar/android/apg/AskForSecretKeyPassPhrase.java index 67aad7529..01cd2de25 100644 --- a/src/org/thialfihar/android/apg/AskForSecretKeyPassPhrase.java +++ b/src/org/thialfihar/android/apg/AskForSecretKeyPassPhrase.java @@ -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() { diff --git a/src/org/thialfihar/android/apg/BaseActivity.java b/src/org/thialfihar/android/apg/BaseActivity.java index 64705ba1f..210e09409 100644 --- a/src/org/thialfihar/android/apg/BaseActivity.java +++ b/src/org/thialfihar/android/apg/BaseActivity.java @@ -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) { diff --git a/src/org/thialfihar/android/apg/CachedPassPhrase.java b/src/org/thialfihar/android/apg/CachedPassPhrase.java index e7566220e..74248aee3 100644 --- a/src/org/thialfihar/android/apg/CachedPassPhrase.java +++ b/src/org/thialfihar/android/apg/CachedPassPhrase.java @@ -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; diff --git a/src/org/thialfihar/android/apg/DecryptActivity.java b/src/org/thialfihar/android/apg/DecryptActivity.java index 80ad13d5e..de8dcb3ff 100644 --- a/src/org/thialfihar/android/apg/DecryptActivity.java +++ b/src/org/thialfihar/android/apg/DecryptActivity.java @@ -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 diff --git a/src/org/thialfihar/android/apg/EditKeyActivity.java b/src/org/thialfihar/android/apg/EditKeyActivity.java index b1e37e8a1..e71fd8e8c 100644 --- a/src/org/thialfihar/android/apg/EditKeyActivity.java +++ b/src/org/thialfihar/android/apg/EditKeyActivity.java @@ -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); diff --git a/src/org/thialfihar/android/apg/EncryptActivity.java b/src/org/thialfihar/android/apg/EncryptActivity.java index 073e9dc9c..926d79599 100644 --- a/src/org/thialfihar/android/apg/EncryptActivity.java +++ b/src/org/thialfihar/android/apg/EncryptActivity.java @@ -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 goodIds = new Vector(); 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; } } } diff --git a/src/org/thialfihar/android/apg/FileDialog.java b/src/org/thialfihar/android/apg/FileDialog.java index 22d64fc84..b6bbbf3f1 100644 --- a/src/org/thialfihar/android/apg/FileDialog.java +++ b/src/org/thialfihar/android/apg/FileDialog.java @@ -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(); } } } diff --git a/src/org/thialfihar/android/apg/Id.java b/src/org/thialfihar/android/apg/Id.java index 47cd0a890..4567f937d 100644 --- a/src/org/thialfihar/android/apg/Id.java +++ b/src/org/thialfihar/android/apg/Id.java @@ -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; diff --git a/src/org/thialfihar/android/apg/KeyListActivity.java b/src/org/thialfihar/android/apg/KeyListActivity.java new file mode 100644 index 000000000..7e86504b3 --- /dev/null +++ b/src/org/thialfihar/android/apg/KeyListActivity.java @@ -0,0 +1,642 @@ +/* + * Copyright (C) 2010 Thialfihar + * + * 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 = ""; + 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 keyRingIds = new Vector(); + 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> 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>(); + for (int i = 0; i < mCursor.getCount(); ++i) { + mChildren.add(null); + } + } + + protected Vector getChildrenOfGroup(int groupPosition) { + Vector children = mChildren.get(groupPosition); + if (children != null) { + return children; + } + + mCursor.moveToPosition(groupPosition); + children = new Vector(); + 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 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); + } +} diff --git a/src/org/thialfihar/android/apg/MailListActivity.java b/src/org/thialfihar/android/apg/MailListActivity.java index f0abe8f45..d166feede 100644 --- a/src/org/thialfihar/android/apg/MailListActivity.java +++ b/src/org/thialfihar/android/apg/MailListActivity.java @@ -87,7 +87,7 @@ public class MailListActivity extends ListActivity { mconversations = new Vector(); mmessages = new Vector(); - 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); } }); diff --git a/src/org/thialfihar/android/apg/MainActivity.java b/src/org/thialfihar/android/apg/MainActivity.java index a4d584304..65dd20d95 100644 --- a/src/org/thialfihar/android/apg/MainActivity.java +++ b/src/org/thialfihar/android/apg/MainActivity.java @@ -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(); diff --git a/src/org/thialfihar/android/apg/PreferencesActivity.java b/src/org/thialfihar/android/apg/PreferencesActivity.java index fae63d63b..e80e1ad5f 100644 --- a/src/org/thialfihar/android/apg/PreferencesActivity.java +++ b/src/org/thialfihar/android/apg/PreferencesActivity.java @@ -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 adapter = new ArrayAdapter(this, android.R.layout.simple_spinner_item, choices); diff --git a/src/org/thialfihar/android/apg/PublicKeyListActivity.java b/src/org/thialfihar/android/apg/PublicKeyListActivity.java index 67bc608ad..4997f60b7 100644 --- a/src/org/thialfihar/android/apg/PublicKeyListActivity.java +++ b/src/org/thialfihar/android/apg/PublicKeyListActivity.java @@ -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 keys = new Vector(); - 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 getChildrenOfKeyRing(PGPPublicKeyRing keyRing) { - Vector children = new Vector(); - PGPPublicKey masterKey = null; - for (PGPPublicKey key : new IterableIterator(keyRing.getPublicKeys())) { - children.add(new KeyChild(key)); - if (key.isMasterKey()) { - masterKey = key; - } - } - - if (masterKey != null) { - boolean isFirst = true; - for (String userId : new IterableIterator(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 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(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 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); - } } diff --git a/src/org/thialfihar/android/apg/SecretKeyListActivity.java b/src/org/thialfihar/android/apg/SecretKeyListActivity.java index a69fc5b9c..0252c46d2 100644 --- a/src/org/thialfihar/android/apg/SecretKeyListActivity.java +++ b/src/org/thialfihar/android/apg/SecretKeyListActivity.java @@ -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 keys = new Vector(); - 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 getChildrenOfKeyRing(PGPSecretKeyRing keyRing) { - Vector children = new Vector(); - PGPSecretKey masterKey = null; - for (PGPSecretKey key : new IterableIterator(keyRing.getSecretKeys())) { - children.add(new KeyChild(key)); - if (key.isMasterKey()) { - masterKey = key; - } - } - - if (masterKey != null) { - boolean isFirst = true; - for (String userId : new IterableIterator(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 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(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 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; - } - } } diff --git a/src/org/thialfihar/android/apg/SelectPublicKeyListActivity.java b/src/org/thialfihar/android/apg/SelectPublicKeyListActivity.java index fbb0b6fe0..aeb6d59a3 100644 --- a/src/org/thialfihar/android/apg/SelectPublicKeyListActivity.java +++ b/src/org/thialfihar/android/apg/SelectPublicKeyListActivity.java @@ -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 keyRings = - (Vector) 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,8 +96,8 @@ 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(); } -} \ No newline at end of file +} diff --git a/src/org/thialfihar/android/apg/SelectPublicKeyListAdapter.java b/src/org/thialfihar/android/apg/SelectPublicKeyListAdapter.java index 1b0b82fd8..ffc344ead 100644 --- a/src/org/thialfihar/android/apg/SelectPublicKeyListAdapter.java +++ b/src/org/thialfihar/android/apg/SelectPublicKeyListAdapter.java @@ -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 mKeyRings; protected LayoutInflater mInflater; protected ListView mParent; + protected SQLiteDatabase mDatabase; + protected Cursor mCursor; - public SelectPublicKeyListAdapter(ListView parent, - Vector 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 keyRings) { - mKeyRings = keyRings; - notifyDataSetChanged(); - } - - public Vector 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 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(keyRing.getPublicKeys())) { - if (tKey.isMasterKey()) { - key = tKey; - break; - } - } - - Vector encryptKeys = Apg.getEncryptKeys(keyRing); - Vector 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); diff --git a/src/org/thialfihar/android/apg/SelectSecretKeyListActivity.java b/src/org/thialfihar/android/apg/SelectSecretKeyListActivity.java index b6811d6e3..cd87a94b6 100644 --- a/src/org/thialfihar/android/apg/SelectSecretKeyListActivity.java +++ b/src/org/thialfihar/android/apg/SelectSecretKeyListActivity.java @@ -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 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) 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 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(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 signingKeys = Apg.getSigningKeys(keyRing); - Vector 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; - } - } -} \ No newline at end of file +} diff --git a/src/org/thialfihar/android/apg/SelectSecretKeyListAdapter.java b/src/org/thialfihar/android/apg/SelectSecretKeyListAdapter.java new file mode 100644 index 000000000..33cd15b40 --- /dev/null +++ b/src/org/thialfihar/android/apg/SelectSecretKeyListAdapter.java @@ -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; + } +} \ No newline at end of file diff --git a/src/org/thialfihar/android/apg/Service.java b/src/org/thialfihar/android/apg/Service.java new file mode 100644 index 000000000..4457274ff --- /dev/null +++ b/src/org/thialfihar/android/apg/Service.java @@ -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; + } +} diff --git a/src/org/thialfihar/android/apg/provider/Accounts.java b/src/org/thialfihar/android/apg/provider/Accounts.java index 4fce2b607..8162472ec 100644 --- a/src/org/thialfihar/android/apg/provider/Accounts.java +++ b/src/org/thialfihar/android/apg/provider/Accounts.java @@ -16,7 +16,12 @@ package org.thialfihar.android.apg.provider; -public class Accounts extends Accounts1 { - private Accounts() { - } -} \ No newline at end of file +import android.provider.BaseColumns; + +public class Accounts implements BaseColumns { + public static final String TABLE_NAME = "accounts"; + + public static final String _ID_type = "INTEGER PRIMARY KEY"; + public static final String NAME = "c_name"; + public static final String NAME_type = "TEXT"; +} diff --git a/src/org/thialfihar/android/apg/provider/DataProvider.java b/src/org/thialfihar/android/apg/provider/DataProvider.java index fbc1be047..8a3fefdff 100644 --- a/src/org/thialfihar/android/apg/provider/DataProvider.java +++ b/src/org/thialfihar/android/apg/provider/DataProvider.java @@ -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 mPublicKeysProjectionMap; - private static final HashMap mSecretKeysProjectionMap; - private static final HashMap 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(); - 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(); - 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(); - 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 projectionMap = new HashMap(); + 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; } } diff --git a/src/org/thialfihar/android/apg/provider/Database.java b/src/org/thialfihar/android/apg/provider/Database.java new file mode 100644 index 000000000..810ebebbf --- /dev/null +++ b/src/org/thialfihar/android/apg/provider/Database.java @@ -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 sKeyRingsProjection; + public static HashMap sKeysProjection; + public static HashMap sUserIdsProjection; + + private SQLiteDatabase mDb = null; + private int mStatus = 0; + + static { + sKeyRingsProjection = new HashMap(); + 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(); + 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(); + 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 seenIds = new Vector(); + int rank = 0; + for (PGPPublicKey key : new IterableIterator(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 seenIds = new Vector(); + int rank = 0; + for (PGPSecretKey key : new IterableIterator(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 seenIds = new Vector(); + int userIdRank = 0; + for (String userId : new IterableIterator(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 seenIds = new Vector(); + int userIdRank = 0; + for (String userId : new IterableIterator(key.getUserIDs())) { + seenIds.add(saveUserId(rowId, userId, userIdRank)); + ++userIdRank; + } + + String seenIdsStr = ""; + for (Integer id : seenIds) { + if (seenIdsStr.length() > 0) { + seenIdsStr += ","; + } + seenIdsStr += id; + } + mDb.delete(UserIds.TABLE_NAME, + UserIds.KEY_ID + " = ? AND " + + UserIds._ID + " NOT IN (" + seenIdsStr + ")", + new String[] { "" + rowId }); + + return (int)rowId; + } + + private int saveUserId(long keyId, String userId, int rank) throws GeneralException { + ContentValues values = new ContentValues(); + + values.put(UserIds.KEY_ID, keyId); + values.put(UserIds.USER_ID, userId); + values.put(UserIds.RANK, rank); + + long rowId = insertOrUpdateUserId(values); + + if (rowId == -1) { + throw new GeneralException("saving user id " + userId + " failed"); + } + + return (int)rowId; + } + + private long insertOrUpdateKeyRing(ContentValues values) { + Cursor c = mDb.query(KeyRings.TABLE_NAME, new String[] { KeyRings._ID }, + KeyRings.MASTER_KEY_ID + " = ? AND " + KeyRings.TYPE + " = ?", + new String[] { + values.getAsString(KeyRings.MASTER_KEY_ID), + values.getAsString(KeyRings.TYPE), + }, + null, null, null); + long rowId = -1; + if (c != null && c.moveToFirst()) { + rowId = c.getLong(0); + mDb.update(KeyRings.TABLE_NAME, values, + KeyRings._ID + " = ?", new String[] { "" + rowId }); + mStatus = Id.return_value.updated; + } else { + rowId = mDb.insert(KeyRings.TABLE_NAME, KeyRings.WHO_ID, values); + mStatus = Id.return_value.ok; + } + + if (c != null) { + c.close(); + } + + return rowId; + } + + private long insertOrUpdateKey(ContentValues values) { + Cursor c = mDb.query(Keys.TABLE_NAME, new String[] { Keys._ID }, + Keys.KEY_ID + " = ? AND " + Keys.TYPE + " = ?", + new String[] { + values.getAsString(Keys.KEY_ID), + values.getAsString(Keys.TYPE), + }, + null, null, null); + long rowId = -1; + if (c != null && c.moveToFirst()) { + rowId = c.getLong(0); + mDb.update(Keys.TABLE_NAME, values, + Keys._ID + " = ?", new String[] { "" + rowId }); + } else { + rowId = mDb.insert(Keys.TABLE_NAME, Keys.KEY_DATA, values); + } + + if (c != null) { + c.close(); + } + + return rowId; + } + + private long insertOrUpdateUserId(ContentValues values) { + Cursor c = mDb.query(UserIds.TABLE_NAME, new String[] { UserIds._ID }, + UserIds.KEY_ID + " = ? AND " + UserIds.USER_ID + " = ?", + new String[] { + values.getAsString(UserIds.KEY_ID), + values.getAsString(UserIds.USER_ID), + }, + null, null, null); + long rowId = -1; + if (c != null && c.moveToFirst()) { + rowId = c.getLong(0); + mDb.update(UserIds.TABLE_NAME, values, + UserIds._ID + " = ?", new String[] { "" + rowId }); + } else { + rowId = mDb.insert(UserIds.TABLE_NAME, UserIds.USER_ID, values); + } + + if (c != null) { + c.close(); + } + + return rowId; + } + + public Object getKeyRing(int keyRingId) { + Cursor c = mDb.query(KeyRings.TABLE_NAME, + new String[] { KeyRings.KEY_RING_DATA, KeyRings.TYPE }, + KeyRings._ID + " = ?", + new String[] { + "" + keyRingId, + }, + null, null, null); + byte[] data = null; + Object keyRing = null; + if (c != null && c.moveToFirst()) { + data = c.getBlob(0); + if (data != null) { + try { + if (c.getInt(1) == Id.database.type_public) { + keyRing = new PGPPublicKeyRing(data); + } else { + keyRing = new PGPSecretKeyRing(data); + } + } catch (IOException e) { + // can't load it, then + } catch (PGPException e) { + // can't load it, then + } + } + } + + if (c != null) { + c.close(); + } + + return keyRing; + } + + public byte[] getKeyRingDataFromKeyId(int type, long keyId) { + Cursor c = mDb.query(Keys.TABLE_NAME + " INNER JOIN " + KeyRings.TABLE_NAME + " ON (" + + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " + + Keys.TABLE_NAME + "." + Keys.KEY_RING_ID + ")", + new String[] { KeyRings.TABLE_NAME + "." + KeyRings.KEY_RING_DATA }, + Keys.TABLE_NAME + "." + Keys.KEY_ID + " = ? AND " + + KeyRings.TABLE_NAME + "." + KeyRings.TYPE + " = ?", + new String[] { + "" + keyId, + "" + type, + }, + null, null, null); + + byte[] data = null; + if (c != null && c.moveToFirst()) { + data = c.getBlob(0); + } + + if (c != null) { + c.close(); + } + + return data; + } + + public byte[] getKeyDataFromKeyId(int type, long keyId) { + Cursor c = mDb.query(Keys.TABLE_NAME, new String[] { Keys.KEY_DATA }, + Keys.KEY_ID + " = ? AND " + Keys.TYPE + " = ?", + new String[] { + "" + keyId, + "" + type, + }, + null, null, null); + byte[] data = null; + if (c != null && c.moveToFirst()) { + data = c.getBlob(0); + } + + if (c != null) { + c.close(); + } + + return data; + } + + public void deleteKeyRing(int keyRingId) { + mDb.beginTransaction(); + mDb.delete(KeyRings.TABLE_NAME, + KeyRings._ID + " = ?", new String[] { "" + keyRingId }); + + Cursor c = mDb.query(Keys.TABLE_NAME, new String[] { Keys._ID }, + Keys.KEY_RING_ID + " = ?", + new String[] { + "" + keyRingId, + }, + null, null, null); + if (c != null && c.moveToFirst()) { + do { + int keyId = c.getInt(0); + deleteKey(keyId); + } while (c.moveToNext()); + } + + if (c != null) { + c.close(); + } + + mDb.setTransactionSuccessful(); + mDb.endTransaction(); + } + + private void deleteKey(int keyId) { + mDb.delete(Keys.TABLE_NAME, + Keys._ID + " = ?", new String[] { "" + keyId }); + + mDb.delete(UserIds.TABLE_NAME, + UserIds.KEY_ID + " = ?", new String[] { "" + keyId }); + } + + public SQLiteDatabase db() { + return mDb; + } +} diff --git a/src/org/thialfihar/android/apg/provider/Accounts1.java b/src/org/thialfihar/android/apg/provider/KeyRings.java similarity index 54% rename from src/org/thialfihar/android/apg/provider/Accounts1.java rename to src/org/thialfihar/android/apg/provider/KeyRings.java index 9009e4598..a4eae6695 100644 --- a/src/org/thialfihar/android/apg/provider/Accounts1.java +++ b/src/org/thialfihar/android/apg/provider/KeyRings.java @@ -1,36 +1,33 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * 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 Accounts1 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"; - - 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"; -} \ No newline at end of file +/* + * Copyright (C) 2010 Thialfihar + * + * 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 KeyRings implements BaseColumns { + public static final String TABLE_NAME = "key_rings"; + + public static final String _ID_type = "INTEGER PRIMARY KEY"; + public static final String MASTER_KEY_ID = "c_master_key_id"; + public static final String MASTER_KEY_ID_type = "INT64"; + public static final String TYPE = "c_type"; + public static final String TYPE_type = "INTEGER"; + public static final String WHO_ID = "c_who_id"; + public static final String WHO_ID_type = "INTEGER"; + public static final String KEY_RING_DATA = "c_key_ring_data"; + public static final String KEY_RING_DATA_type = "BLOB"; +} diff --git a/src/org/thialfihar/android/apg/provider/Keys.java b/src/org/thialfihar/android/apg/provider/Keys.java new file mode 100644 index 000000000..618c5e920 --- /dev/null +++ b/src/org/thialfihar/android/apg/provider/Keys.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2010 Thialfihar + * + * 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"; +} diff --git a/src/org/thialfihar/android/apg/provider/PublicKeys.java b/src/org/thialfihar/android/apg/provider/PublicKeys.java deleted file mode 100644 index f15841fa5..000000000 --- a/src/org/thialfihar/android/apg/provider/PublicKeys.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * 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() { - } -} \ No newline at end of file diff --git a/src/org/thialfihar/android/apg/provider/PublicKeys1.java b/src/org/thialfihar/android/apg/provider/PublicKeys1.java deleted file mode 100644 index d12a67a17..000000000 --- a/src/org/thialfihar/android/apg/provider/PublicKeys1.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * 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"; -} \ No newline at end of file diff --git a/src/org/thialfihar/android/apg/provider/SecretKeys1.java b/src/org/thialfihar/android/apg/provider/SecretKeys1.java deleted file mode 100644 index 3ca405f70..000000000 --- a/src/org/thialfihar/android/apg/provider/SecretKeys1.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * 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"; -} \ No newline at end of file diff --git a/src/org/thialfihar/android/apg/provider/SecretKeys.java b/src/org/thialfihar/android/apg/provider/UserIds.java similarity index 54% rename from src/org/thialfihar/android/apg/provider/SecretKeys.java rename to src/org/thialfihar/android/apg/provider/UserIds.java index d31f306ae..2b1162beb 100644 --- a/src/org/thialfihar/android/apg/provider/SecretKeys.java +++ b/src/org/thialfihar/android/apg/provider/UserIds.java @@ -16,7 +16,16 @@ package org.thialfihar.android.apg.provider; -public class SecretKeys extends SecretKeys1 { - private SecretKeys() { - } -} \ No newline at end of file +import android.provider.BaseColumns; + +public class UserIds implements BaseColumns { + public static final String TABLE_NAME = "user_ids"; + + public static final String _ID_type = "INTEGER PRIMARY KEY"; + public static final String KEY_ID = "c_key_id"; + public static final String KEY_ID_type = "INTEGER"; + public static final String USER_ID = "c_user_id"; + public static final String USER_ID_type = "TEXT"; + public static final String RANK = "c_rank"; + public static final String RANK_type = "INTEGER"; +} diff --git a/src/org/thialfihar/android/apg/ui/widget/KeyEditor.java b/src/org/thialfihar/android/apg/ui/widget/KeyEditor.java index bc38fba4c..044da2db2 100644 --- a/src/org/thialfihar/android/apg/ui/widget/KeyEditor.java +++ b/src/org/thialfihar/android/apg/ui/widget/KeyEditor.java @@ -233,7 +233,7 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener { } public GregorianCalendar getExpiryDate() { - return mExpiryDate; + return mExpiryDate; } public int getUsage() { diff --git a/src/org/thialfihar/android/apg/ui/widget/SectionView.java b/src/org/thialfihar/android/apg/ui/widget/SectionView.java index cc1410c26..e5dcd4aca 100644 --- a/src/org/thialfihar/android/apg/ui/widget/SectionView.java +++ b/src/org/thialfihar/android/apg/ui/widget/SectionView.java @@ -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); }