diff --git a/.gitmodules b/.gitmodules index 919f7e1db..6fa51e40c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -28,6 +28,9 @@ [submodule "extern/minidns"] path = extern/minidns url = https://github.com/open-keychain/minidns.git +[submodule "extern/TokenAutoComplete"] + path = extern/TokenAutoComplete + url = https://github.com/open-keychain/TokenAutoComplete [submodule "extern/openpgp-card-nfc-lib"] path = extern/openpgp-card-nfc-lib url = https://github.com/open-keychain/openpgp-card-nfc-lib.git \ No newline at end of file diff --git a/CHANGELOG b/CHANGELOG index 893082aa4..be5ea325c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,17 @@ +2.8 +* So many bugs have been fixed in this release that we focus on the main new features +* Key edit: awesome new design, key revocation +* Key import: awesome new design, secure keyserver connections via hkps, keyserver resolving via DNS SRV records +* New first time screen +* New create key screen: autocompletion of name and email based on your personal Android accounts +* File encryption: awesome new design, support for encrypting multiple files +* New icons to show status of key (by Brennan Novak) +* Important bug fix: Importing of large key collections from a file is now possible +* Notification showing cached passphrases + +This release wouldn't be possible without the work of Vincent Breitmoser (GSoC 2014), mar-v-in (GSoC 2014), Daniel Albert, Art O Cathain, Daniel Haß, Tim Bray, Thialfihar + + 2.7 * Purple! (Dominik, Vincent) * New key view design (Dominik, Vincent) @@ -6,50 +20,50 @@ * Keybase.io import (Tim Bray) 2.6.1 -* some fixes for regression bugs +* Some fixes for regression bugs 2.6 -* key certifications (thanks to Vincent Breitmoser) -* support for GnuPG partial secret keys (thanks to Vincent Breitmoser) -* new design for signature verification -* custom key length (thanks to Greg Witczak) -* fix share-functionality from other apps +* Key certifications (thanks to Vincent Breitmoser) +* Support for GnuPG partial secret keys (thanks to Vincent Breitmoser) +* New design for signature verification +* Custom key length (thanks to Greg Witczak) +* Fix share-functionality from other apps 2.5 -* fix decryption of symmetric pgp messages/files -* refactored edit key screen (thanks to Ash Hughes) -* new modern design for encrypt/decrypt screens +* Fix decryption of symmetric pgp messages/files +* Refactored edit key screen (thanks to Ash Hughes) +* New modern design for encrypt/decrypt screens * OpenPGP API version 3 (multiple api accounts, internal fixes, key lookup) 2.4 Thanks to all applicants of Google Summer of Code 2014 who made this release feature rich and bug free! Besides several small patches, a notable number of patches are made by the following people (in alphabetical order): Daniel Hammann, Daniel Haß, Greg Witczak, Miroojin Bakshi, Nikhil Peter Raj, Paul Sarbinowski, Sreeram Boyapati, Vincent Breitmoser. -* new unified key list -* colorized key fingerprint -* support for keyserver ports -* deactivate possibility to generate weak keys -* much more internal work on the API -* certify user ids -* keyserver query based on machine-readable output -* lock navigation drawer on tablets -* suggestions for emails on creation of keys -* search in public key lists -* and much more improvements and fixes… +* New unified key list +* Colorized key fingerprint +* Support for keyserver ports +* Deactivate possibility to generate weak keys +* Much more internal work on the API +* Certify user ids +* Keyserver query based on machine-readable output +* Lock navigation drawer on tablets +* Suggestions for emails on creation of keys +* Search in public key lists +* And much more improvements and fixes… 2.3.1 -* hotfix for crash when upgrading from old versions +* Hotfix for crash when upgrading from old versions 2.3 -* remove unnecessary export of public keys when exporting secret key (thanks to Ash Hughes) -* fix setting expiry dates on keys (thanks to Ash Hughes) -* more internal fixes when editing keys (thanks to Ash Hughes) -* querying keyservers directly from the import screen -* fix layout and dialog style on Android 2.2-3.0 -* fix crash on keys with empty user ids -* fix crash and empty lists when coming back from signing screen +* Remove unnecessary export of public keys when exporting secret key (thanks to Ash Hughes) +* Fix setting expiry dates on keys (thanks to Ash Hughes) +* More internal fixes when editing keys (thanks to Ash Hughes) +* Querying keyservers directly from the import screen +* Fix layout and dialog style on Android 2.2-3.0 +* Fix crash on keys with empty user ids +* Fix crash and empty lists when coming back from signing screen * Bouncy Castle (cryptography library) updated from 1.47 to 1.50 and build from source -* fix upload of key from signing screen +* Fix upload of key from signing screen 2.2 * New design with navigation drawer @@ -76,110 +90,110 @@ Daniel Hammann, Daniel Haß, Greg Witczak, Miroojin Bakshi, Nikhil Peter Raj, Pa * New AIDL API 1.0.8 -* basic keyserver support (HKP, please report bugs :)) -* app2sd (untested, let me know if there are problems) -* more choices for pass phrase cache: 1, 2, 4, 8, hours -* translations: Norwegian (thanks, Sander Danielsen), Chinese (thanks, Zhang Fredrick) -* bugfixes -* optimizations +* Basic keyserver support (HKP, please report bugs :)) +* App2sd (untested, let me know if there are problems) +* More choices for pass phrase cache: 1, 2, 4, 8, hours +* Translations: Norwegian (thanks, Sander Danielsen), Chinese (thanks, Zhang Fredrick) +* Bugfixes +* Optimizations 1.0.7 -* clear sign problem with lacking trailing newline fixed -* more options for pass phrase cache time to live (20, 40, 60 mins) +* Clear sign problem with lacking trailing newline fixed +* More options for pass phrase cache time to live (20, 40, 60 mins) 1.0.6 -* account adding crash on Froyo fixed -* secure file deletion -* option to delete key file after import -* stream encryption/decryption (gallery, etc.) -* new options (language, force v3 signatures) -* interface changes -* bugfixes +* Account adding crash on Froyo fixed +* Secure file deletion +* Option to delete key file after import +* Stream encryption/decryption (gallery, etc.) +* New options (language, force v3 signatures) +* Interface changes +* Bugfixes 1.0.5 * German and Italian translation -* much smaller package, due to reduced BC sources -* new preferences GUI -* layout adjustment for localization -* signature bugfix +* Much smaller package, due to reduced BC sources +* New preferences GUI +* Layout adjustment for localization +* Signature bugfix 1.0.4 -* fixed another crash caused by some SDK bug with query builder +* Fixed another crash caused by some SDK bug with query builder 1.0.3 -* fixed crashes during encryption/signing and possibly key export +* Fixed crashes during encryption/signing and possibly key export 1.0.2 -* filterable key lists -* smarter preselection of encryption keys -* new Intent handling for VIEW and SEND, allows files to be encrypted/decrypted out of file managers -* fixes and additional features (key preselection) for k9, new beta build available +* Filterable key lists +* Smarter preselection of encryption keys +* New Intent handling for VIEW and SEND, allows files to be encrypted/decrypted out of file managers +* Fixes and additional features (key preselection) for k9, new beta build available 1.0.1 * GMail account listing was broken in 1.0.0, fixed again 1.0.0 -* k9mail integration, APG supporting beta build of k9mail -* support of more file managers (including ASTRO) +* K-9 Mail integration, APG supporting beta build of K-9 Mail +* Support of more file managers (including ASTRO) * Slovenian translation -* new database, much faster, less memory usage -* defined Intents and content provider for other apps -* bugfixes +* New database, much faster, less memory usage +* Defined Intents and content provider for other apps +* Bugfixes 0.9.7 * 0.9.5 must have introduced a bug that prevented symmetric encryption, this release fixes it 0.9.6 -* finally fixed that bug that prevents the import of keys exported with Enigmail (and others), since this likely affects many users... it gets its own quick release +* Finally fixed that bug that prevents the import of keys exported with Enigmail (and others), since this likely affects many users... it gets its own quick release 0.9.5 -* k9mail integration: using "More -> Forward (alternate)" -* pass phrase cache -* compression preferences added -* accurate decryption progress bar -* internationalization prepared, hopefully translations will follow +* K-9 Mail integration: using "More -> Forward (alternate)" +* Passphrase cache +* Compression preferences added +* Accurate decryption progress bar +* Internationalization prepared, hopefully translations will follow 0.9.4 * Android 1.5 support, I *hope*, please report problems with layout and graphics -* yet another interface change, hopefully this will be it :) -* symmetric encryption for messages -* encrypt and decrypt processes all wrapped into ONE activity respectively in preparation for defined Intents to use APG in other apps +* Yet another interface change, hopefully this will be it :) +* Symmetric encryption for messages +* Encrypt and decrypt processes all wrapped into ONE activity respectively in preparation for defined Intents to use APG in other apps 0.9.3 -* handle large files correctly -* better progress bars (especially for file encryption/decryption) -* option to delete files after en-/decryption -* bug fixes, layout tweaks +* Handle large files correctly +* Better progress bars (especially for file encryption/decryption) +* Option to delete files after en-/decryption +* Bug fixes, layout tweaks 0.9.2 -* settings for default encryption/hash algorithm -* hushmail key support +* Settings for default encryption/hash algorithm +* Hushmail key support * GUI improvements (encrypt file layout rewritten) -* bug fixes +* Bug fixes 0.9.1 * ElGamal support for subkeys -* fixes of some silly 0.9.0 bugs +* Fixes of some silly 0.9.0 bugs 0.9.0 * OI File Manager support -* file encryption/decryption +* File encryption/decryption 0.8.1 -* display/verify signed-only mails -* bug fixes, layout fixes +* Display/verify signed-only mails +* Bug fixes, layout fixes 0.8.0 -* create/edit keys -* export keys +* Create/edit keys +* Export keys * GUI more Android-like -* a lot of code review, rewriting things -* tidy up strings and error handling +* A lot of code review, rewriting things +* Tidy up strings and error handling 0.7.1 -* minor fixes, some code review -* recognize ElGamal encryption keys as suitable for encryption -* allow signing only +* Minor fixes, some code review +* Recognize ElGamal encryption keys as suitable for encryption +* Allow signing only 0.7.0 -* initial public release \ No newline at end of file +* Initial public release \ No newline at end of file diff --git a/OpenKeychain/build.gradle b/OpenKeychain/build.gradle index 265411595..32ae8ceb0 100644 --- a/OpenKeychain/build.gradle +++ b/OpenKeychain/build.gradle @@ -1,4 +1,4 @@ -apply plugin: 'android' +apply plugin: 'com.android.application' dependencies { // NOTE: Always use fixed version codes not dynamic ones, e.g. 0.7.3 instead of 0.7.+, see README for more information @@ -18,6 +18,7 @@ dependencies { compile project(':extern:SuperToasts:supertoasts') compile project(':extern:minidns') compile project(':extern:KeybaseLib:Lib') + compile project(':extern:TokenAutoComplete:library') compile project(':extern:openpgp-card-nfc-lib:library') } diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml index 7af9d895f..c10629c6d 100644 --- a/OpenKeychain/src/main/AndroidManifest.xml +++ b/OpenKeychain/src/main/AndroidManifest.xml @@ -49,6 +49,10 @@ android:name="android.hardware.touchscreen" android:required="false" /> + + + + @@ -152,12 +156,14 @@ - + + + @@ -170,26 +176,16 @@ android:label="@string/title_decrypt" android:windowSoftInputMode="stateHidden"> - - - + + + - - + + - - - - - - - - - - - - - + + + @@ -202,8 +198,9 @@ - + + @@ -644,6 +641,12 @@ android:resource="@xml/custom_pgp_contacts_structure" /> + + diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java index 16b6173f0..7d1af704d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java @@ -17,16 +17,16 @@ package org.sufficientlysecure.keychain; -import android.os.Build; import android.os.Environment; -import org.spongycastle.bcpg.CompressionAlgorithmTags; import org.spongycastle.jce.provider.BouncyCastleProvider; import org.sufficientlysecure.keychain.remote.ui.AppsListActivity; import org.sufficientlysecure.keychain.ui.DecryptActivity; import org.sufficientlysecure.keychain.ui.EncryptActivity; import org.sufficientlysecure.keychain.ui.KeyListActivity; +import java.io.File; + public final class Constants { public static final boolean DEBUG = BuildConfig.DEBUG; @@ -49,12 +49,11 @@ public final class Constants { public static final String CUSTOM_CONTACT_DATA_MIME_TYPE = "vnd.android.cursor.item/vnd.org.sufficientlysecure.keychain.key"; - public static boolean KITKAT = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; + public static int TEMPFILE_TTL = 24 * 60 * 60 * 1000; // 1 day public static final class Path { - public static final String APP_DIR = Environment.getExternalStorageDirectory() - + "/OpenKeychain"; - public static final String APP_DIR_FILE = APP_DIR + "/export.asc"; + public static final File APP_DIR = new File(Environment.getExternalStorageDirectory(), "OpenKeychain"); + public static final File APP_DIR_FILE = new File(APP_DIR, "export.asc"); } public static final class Pref { @@ -89,23 +88,6 @@ public final class Constants { }; } - public static final class choice { - public static final class algorithm { - // TODO: legacy reasons :/ better: PublicKeyAlgorithmTags - public static final int dsa = 0x21070001; - public static final int elgamal = 0x21070002; - public static final int rsa = 0x21070003; - } - - public static final class compression { - // TODO: legacy reasons :/ better: CompressionAlgorithmTags.UNCOMPRESSED - public static final int none = 0x21070001; - public static final int zlib = CompressionAlgorithmTags.ZLIB; - public static final int bzip2 = CompressionAlgorithmTags.BZIP2; - public static final int zip = CompressionAlgorithmTags.ZIP; - } - } - public static final class key { public static final int none = 0; public static final int symmetric = -1; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java index e70b134aa..9b9880533 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java @@ -28,11 +28,10 @@ import android.os.Environment; import org.spongycastle.jce.provider.BouncyCastleProvider; import org.sufficientlysecure.keychain.helper.Preferences; import org.sufficientlysecure.keychain.helper.TlsHelper; +import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.PRNGFixes; -import java.io.File; -import java.security.Provider; import java.security.Security; public class KeychainApplication extends Application { @@ -73,8 +72,7 @@ public class KeychainApplication extends Application { // Create APG directory on sdcard if not existing if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { - File dir = new File(Constants.Path.APP_DIR); - if (!dir.exists() && !dir.mkdirs()) { + if (!Constants.Path.APP_DIR.exists() && !Constants.Path.APP_DIR.mkdirs()) { // ignore this for now, it's not crucial // that the directory doesn't exist at this point } @@ -86,9 +84,11 @@ public class KeychainApplication extends Application { setupAccountAsNeeded(this); // Update keyserver list as needed - Preferences.getPreferences(this).updateKeyServers(); + Preferences.getPreferences(this).updatePreferences(); TlsHelper.addStaticCA("pool.sks-keyservers.net", getAssets(), "sks-keyservers.netCA.cer"); + + TemporaryStorageProvider.cleanUp(this); } public static void setupAccountAsNeeded(Context context) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/ContactHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/ContactHelper.java index e639824ec..8697e49f7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/ContactHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/ContactHelper.java @@ -19,8 +19,13 @@ package org.sufficientlysecure.keychain.helper; import android.accounts.Account; import android.accounts.AccountManager; -import android.content.*; +import android.content.ContentProviderOperation; +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.Context; import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.net.Uri; import android.os.Build; import android.provider.ContactsContract; @@ -33,7 +38,14 @@ import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.util.Log; -import java.util.*; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; public class ContactHelper { @@ -60,6 +72,8 @@ public class ContactHelper { ContactsContract.Data.RAW_CONTACT_ID + "=? AND " + ContactsContract.Data.MIMETYPE + "=?"; public static final String ID_SELECTION = ContactsContract.RawContacts._ID + "=?"; + private static final Map photoCache = new HashMap(); + public static List getPossibleUserEmails(Context context) { Set accountMails = getAccountEmails(context); accountMails.addAll(getMainProfileContactEmails(context)); @@ -232,6 +246,30 @@ public class ContactHelper { return null; } + public static Bitmap photoFromFingerprint(ContentResolver contentResolver, String fingerprint) { + if (fingerprint == null) return null; + if (!photoCache.containsKey(fingerprint)) { + photoCache.put(fingerprint, loadPhotoFromFingerprint(contentResolver, fingerprint)); + } + return photoCache.get(fingerprint); + } + + private static Bitmap loadPhotoFromFingerprint(ContentResolver contentResolver, String fingerprint) { + if (fingerprint == null) return null; + try { + int rawContactId = findRawContactId(contentResolver, fingerprint); + if (rawContactId == -1) return null; + Uri rawContactUri = ContentUris.withAppendedId(ContactsContract.RawContacts.CONTENT_URI, rawContactId); + Uri contactUri = ContactsContract.RawContacts.getContactLookupUri(contentResolver, rawContactUri); + InputStream photoInputStream = + ContactsContract.Contacts.openContactPhotoInputStream(contentResolver, contactUri); + if (photoInputStream == null) return null; + return BitmapFactory.decodeStream(photoInputStream); + } catch (Throwable ignored) { + return null; + } + } + /** * Write the current Keychain to the contact db */ @@ -356,7 +394,7 @@ public class ContactHelper { int rawContactId, long masterKeyId) { ops.add(selectByRawContactAndItemType(ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI), rawContactId, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE).build()); - Cursor ids = resolver.query(KeychainContract.UserIds.buildUserIdsUri(Long.toString(masterKeyId)), + Cursor ids = resolver.query(KeychainContract.UserIds.buildUserIdsUri(masterKeyId), USER_IDS_PROJECTION, NON_REVOKED_SELECTION, null, null); if (ids != null) { while (ids.moveToNext()) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/EmailKeyHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/EmailKeyHelper.java index 5d281d5b0..d8efdc480 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/EmailKeyHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/EmailKeyHelper.java @@ -21,6 +21,7 @@ import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.os.Messenger; + import org.sufficientlysecure.keychain.keyimport.HkpKeyserver; import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry; import org.sufficientlysecure.keychain.keyimport.Keyserver; @@ -29,6 +30,7 @@ import org.sufficientlysecure.keychain.service.KeychainIntentService; import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Set; public class EmailKeyHelper { @@ -86,7 +88,7 @@ public class EmailKeyHelper { for (ImportKeysListEntry key : keyServer.search(mail)) { if (key.isRevoked() || key.isExpired()) continue; for (String userId : key.getUserIds()) { - if (userId.toLowerCase().contains(mail.toLowerCase())) { + if (userId.toLowerCase().contains(mail.toLowerCase(Locale.ENGLISH))) { keys.add(key); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/ExportHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/ExportHelper.java index 16ef28311..bcd57b290 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/ExportHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/ExportHelper.java @@ -30,18 +30,17 @@ import android.widget.Toast; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment; -import org.sufficientlysecure.keychain.ui.dialog.FileDialogFragment; import org.sufficientlysecure.keychain.util.Log; +import java.io.File; + public class ExportHelper { - protected FileDialogFragment mFileDialog; - protected String mExportFilename; + protected File mExportFile; ActionBarActivity mActivity; @@ -68,47 +67,30 @@ public class ExportHelper { /** * Show dialog where to export keys */ - public void showExportKeysDialog(final long[] masterKeyIds, final String exportFilename, + public void showExportKeysDialog(final long[] masterKeyIds, final File exportFile, final boolean showSecretCheckbox) { - mExportFilename = exportFilename; + mExportFile = exportFile; - // Message is received after file is selected - Handler returnHandler = new Handler() { + String title = null; + if (masterKeyIds == null) { + // export all keys + title = mActivity.getString(R.string.title_export_keys); + } else { + // export only key specified at data uri + title = mActivity.getString(R.string.title_export_key); + } + + String message = mActivity.getString(R.string.specify_file_to_export_to); + String checkMsg = showSecretCheckbox ? + mActivity.getString(R.string.also_export_secret_keys) : null; + + FileHelper.saveFile(new FileHelper.FileDialogCallback() { @Override - public void handleMessage(Message message) { - if (message.what == FileDialogFragment.MESSAGE_OKAY) { - Bundle data = message.getData(); - mExportFilename = data.getString(FileDialogFragment.MESSAGE_DATA_FILENAME); - - exportKeys(masterKeyIds, data.getBoolean(FileDialogFragment.MESSAGE_DATA_CHECKED)); - } + public void onFileSelected(File file, boolean checked) { + mExportFile = file; + exportKeys(masterKeyIds, checked); } - }; - - // Create a new Messenger for the communication back - final Messenger messenger = new Messenger(returnHandler); - - DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() { - public void run() { - String title = null; - if (masterKeyIds == null) { - // export all keys - title = mActivity.getString(R.string.title_export_keys); - } else { - // export only key specified at data uri - title = mActivity.getString(R.string.title_export_key); - } - - String message = mActivity.getString(R.string.specify_file_to_export_to); - String checkMsg = showSecretCheckbox ? - mActivity.getString(R.string.also_export_secret_keys) : null; - - mFileDialog = FileDialogFragment.newInstance(messenger, title, message, - exportFilename, checkMsg); - - mFileDialog.show(mActivity.getSupportFragmentManager(), "fileDialog"); - } - }); + }, mActivity.getSupportFragmentManager() ,title, message, exportFile, checkMsg); } /** @@ -125,7 +107,7 @@ public class ExportHelper { // fill values for this action Bundle data = new Bundle(); - data.putString(KeychainIntentService.EXPORT_FILENAME, mExportFilename); + data.putString(KeychainIntentService.EXPORT_FILENAME, mExportFile.getAbsolutePath()); data.putBoolean(KeychainIntentService.EXPORT_SECRET, exportSecret); if (masterKeyIds == null) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/FileHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/FileHelper.java index e0c94b947..b640ecb03 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/FileHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/FileHelper.java @@ -23,15 +23,26 @@ import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.Point; import android.net.Uri; import android.os.Build; import android.os.Environment; +import android.os.Handler; +import android.os.Message; +import android.os.Messenger; +import android.provider.DocumentsContract; +import android.provider.OpenableColumns; import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; import android.widget.Toast; -import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; +import org.sufficientlysecure.keychain.ui.dialog.FileDialogFragment; + +import java.io.File; +import java.text.DecimalFormat; public class FileHelper { @@ -55,25 +66,18 @@ public class FileHelper { * Opens the preferred installed file manager on Android and shows a toast if no manager is * installed. * - * @param activity - * @param filename default selected file, not supported by all file managers + * @param fragment + * @param last default selected Uri, not supported by all file managers * @param mimeType can be text/plain for example * @param requestCode requestCode used to identify the result coming back from file manager to * onActivityResult() in your activity */ - public static void openFile(Activity activity, String filename, String mimeType, int requestCode) { - Intent intent = buildFileIntent(filename, mimeType); + public static void openFile(Fragment fragment, Uri last, String mimeType, int requestCode) { + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); - try { - activity.startActivityForResult(intent, requestCode); - } catch (ActivityNotFoundException e) { - // No compatible file manager was found. - Toast.makeText(activity, R.string.no_filemanager_installed, Toast.LENGTH_SHORT).show(); - } - } - - public static void openFile(Fragment fragment, String filename, String mimeType, int requestCode) { - Intent intent = buildFileIntent(filename, mimeType); + intent.setData(last); + intent.setType(mimeType); try { fragment.startActivityForResult(intent, requestCode); @@ -84,86 +88,153 @@ public class FileHelper { } } + public static void saveFile(final FileDialogCallback callback, final FragmentManager fragmentManager, + final String title, final String message, final File defaultFile, + final String checkMsg) { + // Message is received after file is selected + Handler returnHandler = new Handler() { + @Override + public void handleMessage(Message message) { + if (message.what == FileDialogFragment.MESSAGE_OKAY) { + callback.onFileSelected( + new File(message.getData().getString(FileDialogFragment.MESSAGE_DATA_FILE)), + message.getData().getBoolean(FileDialogFragment.MESSAGE_DATA_CHECKED)); + } + } + }; + + // Create a new Messenger for the communication back + final Messenger messenger = new Messenger(returnHandler); + + DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() { + @Override + public void run() { + FileDialogFragment fileDialog = FileDialogFragment.newInstance(messenger, title, message, + defaultFile, checkMsg); + + fileDialog.show(fragmentManager, "fileDialog"); + } + }); + } + + public static void saveFile(Fragment fragment, String title, String message, File defaultFile, int requestCode) { + saveFile(fragment, title, message, defaultFile, requestCode, null); + } + + public static void saveFile(final Fragment fragment, String title, String message, File defaultFile, + final int requestCode, String checkMsg) { + saveFile(new FileDialogCallback() { + @Override + public void onFileSelected(File file, boolean checked) { + Intent intent = new Intent(); + intent.setData(Uri.fromFile(file)); + fragment.onActivityResult(requestCode, Activity.RESULT_OK, intent); + } + }, fragment.getActivity().getSupportFragmentManager(), title, message, defaultFile, checkMsg); + } + + @TargetApi(Build.VERSION_CODES.KITKAT) + public static void openDocument(Fragment fragment, String mimeType, int requestCode) { + openDocument(fragment, mimeType, false, requestCode); + } /** * Opens the storage browser on Android 4.4 or later for opening a file + * * @param fragment - * @param last default selected file - * @param mimeType can be text/plain for example + * @param mimeType can be text/plain for example + * @param multiple allow file chooser to return multiple files * @param requestCode used to identify the result coming back from storage browser onActivityResult() in your */ @TargetApi(Build.VERSION_CODES.KITKAT) - public static void openDocument(Fragment fragment, Uri last, String mimeType, int requestCode) { + public static void openDocument(Fragment fragment, String mimeType, boolean multiple, int requestCode) { Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); intent.addCategory(Intent.CATEGORY_OPENABLE); - intent.setData(last); intent.setType(mimeType); + intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, multiple); + fragment.startActivityForResult(intent, requestCode); } /** * Opens the storage browser on Android 4.4 or later for saving a file + * * @param fragment - * @param last default selected file - * @param mimeType can be text/plain for example - * @param requestCode used to identify the result coming back from storage browser onActivityResult() in your + * @param mimeType can be text/plain for example + * @param suggestedName a filename desirable for the file to be saved + * @param requestCode used to identify the result coming back from storage browser onActivityResult() in your */ @TargetApi(Build.VERSION_CODES.KITKAT) - public static void saveDocument(Fragment fragment, Uri last, String mimeType, int requestCode) { + public static void saveDocument(Fragment fragment, String mimeType, String suggestedName, int requestCode) { Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); intent.addCategory(Intent.CATEGORY_OPENABLE); - intent.setData(last); intent.setType(mimeType); + intent.putExtra("android.content.extra.SHOW_ADVANCED", true); // Note: This is not documented, but works + intent.putExtra(Intent.EXTRA_TITLE, suggestedName); fragment.startActivityForResult(intent, requestCode); } - private static Intent buildFileIntent(String filename, String mimeType) { - Intent intent = new Intent(Intent.ACTION_GET_CONTENT); - intent.addCategory(Intent.CATEGORY_OPENABLE); + public static String getFilename(Context context, Uri uri) { + String filename = null; + try { + Cursor cursor = context.getContentResolver().query(uri, new String[]{OpenableColumns.DISPLAY_NAME}, null, null, null); - intent.setData(Uri.parse("file://" + filename)); - intent.setType(mimeType); + if (cursor != null) { + if (cursor.moveToNext()) { + filename = cursor.getString(0); + } + cursor.close(); + } + } catch (Exception ignored) { + // This happens in rare cases (eg: document deleted since selection) and should not cause a failure + } + if (filename == null) { + String[] split = uri.toString().split("/"); + filename = split[split.length - 1]; + } + return filename; + } - return intent; + public static long getFileSize(Context context, Uri uri) { + return getFileSize(context, uri, -1); + } + + public static long getFileSize(Context context, Uri uri, long def) { + long size = def; + try { + Cursor cursor = context.getContentResolver().query(uri, new String[]{OpenableColumns.SIZE}, null, null, null); + + if (cursor != null) { + if (cursor.moveToNext()) { + size = cursor.getLong(0); + } + cursor.close(); + } + } catch (Exception ignored) { + // This happens in rare cases (eg: document deleted since selection) and should not cause a failure + } + return size; } /** - * Get a file path from a Uri. - *

- * from https://github.com/iPaulPro/aFileChooser/blob/master/aFileChooser/src/com/ipaulpro/ - * afilechooser/utils/FileUtils.java - * - * @param context - * @param uri - * @return - * @author paulburke + * Retrieve thumbnail of file, document api feature and thus KitKat only */ - public static String getPath(Context context, Uri uri) { - Log.d(Constants.TAG + " File -", - "Authority: " + uri.getAuthority() + ", Fragment: " + uri.getFragment() - + ", Port: " + uri.getPort() + ", Query: " + uri.getQuery() + ", Scheme: " - + uri.getScheme() + ", Host: " + uri.getHost() + ", Segments: " - + uri.getPathSegments().toString()); - - if ("content".equalsIgnoreCase(uri.getScheme())) { - String[] projection = {"_data"}; - Cursor cursor = context.getContentResolver().query(uri, projection, null, null, null); - try { - if (cursor != null && cursor.moveToFirst()) { - int columnIndex = cursor.getColumnIndexOrThrow("_data"); - return cursor.getString(columnIndex); - } - } catch (Exception e) { - // Eat it - } finally { - if (cursor != null) { - cursor.close(); - } - } - } else if ("file".equalsIgnoreCase(uri.getScheme())) { - return uri.getPath(); + public static Bitmap getThumbnail(Context context, Uri uri, Point size) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + return DocumentsContract.getDocumentThumbnail(context.getContentResolver(), uri, size, null); + } else { + return null; } + } - return null; + public static String readableFileSize(long size) { + if (size <= 0) return "0"; + final String[] units = new String[]{"B", "KB", "MB", "GB", "TB"}; + int digitGroups = (int) (Math.log10(size) / Math.log10(1024)); + return new DecimalFormat("#,##0.#").format(size / Math.pow(1024, digitGroups)) + " " + units[digitGroups]; + } + + public static interface FileDialogCallback { + public void onFileSelected(File file, boolean checked); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/Preferences.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/Preferences.java index a060092a3..491709354 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/Preferences.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/Preferences.java @@ -21,6 +21,7 @@ package org.sufficientlysecure.keychain.helper; import android.content.Context; import android.content.SharedPreferences; +import org.spongycastle.bcpg.CompressionAlgorithmTags; import org.spongycastle.bcpg.HashAlgorithmTags; import org.spongycastle.openpgp.PGPEncryptedData; import org.sufficientlysecure.keychain.Constants; @@ -99,7 +100,7 @@ public class Preferences { public int getDefaultMessageCompression() { return mSharedPreferences.getInt(Constants.Pref.DEFAULT_MESSAGE_COMPRESSION, - Constants.choice.compression.zlib); + CompressionAlgorithmTags.ZLIB); } public void setDefaultMessageCompression(int value) { @@ -110,7 +111,7 @@ public class Preferences { public int getDefaultFileCompression() { return mSharedPreferences.getInt(Constants.Pref.DEFAULT_FILE_COMPRESSION, - Constants.choice.compression.none); + CompressionAlgorithmTags.UNCOMPRESSED); } public void setDefaultFileCompression(int value) { @@ -170,7 +171,8 @@ public class Preferences { editor.commit(); } - public void updateKeyServers() { + public void updatePreferences() { + // migrate keyserver to hkps if (mSharedPreferences.getInt(Constants.Pref.KEY_SERVERS_DEFAULT_VERSION, 0) != Constants.Defaults.KEY_SERVERS_VERSION) { String[] servers = getKeyServers(); @@ -186,6 +188,11 @@ public class Preferences { .putInt(Constants.Pref.KEY_SERVERS_DEFAULT_VERSION, Constants.Defaults.KEY_SERVERS_VERSION) .commit(); } + + // migrate old uncompressed constant to new one + if (mSharedPreferences.getInt(Constants.Pref.DEFAULT_FILE_COMPRESSION, 0) == 0x21070001) { + setDefaultFileCompression(CompressionAlgorithmTags.UNCOMPRESSED); + } } public void setConcealPgpApplication(boolean conceal) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/TlsHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/TlsHelper.java index 4b09af04d..8bbe73676 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/TlsHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/TlsHelper.java @@ -18,12 +18,10 @@ package org.sufficientlysecure.keychain.helper; import android.content.res.AssetManager; + import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.util.Log; -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManagerFactory; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -34,10 +32,16 @@ import java.security.KeyManagementException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; -import java.security.cert.*; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; import java.util.HashMap; import java.util.Map; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManagerFactory; + public class TlsHelper { public static class TlsHelperException extends Exception { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java index 41f1e6997..f617be62a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java @@ -18,11 +18,6 @@ package org.sufficientlysecure.keychain.keyimport; -import de.measite.minidns.Client; -import de.measite.minidns.Question; -import de.measite.minidns.Record; -import de.measite.minidns.record.SRV; - import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.helper.TlsHelper; import org.sufficientlysecure.keychain.pgp.PgpHelper; @@ -45,6 +40,11 @@ import java.util.TimeZone; import java.util.regex.Matcher; import java.util.regex.Pattern; +import de.measite.minidns.Client; +import de.measite.minidns.Question; +import de.measite.minidns.Record; +import de.measite.minidns.record.SRV; + public class HkpKeyserver extends Keyserver { private static class HttpError extends Exception { private static final long serialVersionUID = 1718783705229428893L; @@ -251,14 +251,14 @@ public class HkpKeyserver extends Keyserver { data = query(request); } catch (HttpError e) { if (e.getData() != null) { - Log.d(Constants.TAG, "returned error data: " + e.getData().toLowerCase(Locale.US)); + Log.d(Constants.TAG, "returned error data: " + e.getData().toLowerCase(Locale.ENGLISH)); - if (e.getData().toLowerCase(Locale.US).contains("no keys found")) { + if (e.getData().toLowerCase(Locale.ENGLISH).contains("no keys found")) { // NOTE: This is also a 404 error for some keyservers! return results; - } else if (e.getData().toLowerCase(Locale.US).contains("too many")) { + } else if (e.getData().toLowerCase(Locale.ENGLISH).contains("too many")) { throw new TooManyResponsesException(); - } else if (e.getData().toLowerCase(Locale.US).contains("insufficient")) { + } else if (e.getData().toLowerCase(Locale.ENGLISH).contains("insufficient")) { throw new QueryTooShortException(); } else if (e.getCode() == 404) { // NOTE: handle this 404 at last, maybe it was a "no keys found" error @@ -285,7 +285,7 @@ public class HkpKeyserver extends Keyserver { // group 1 contains the full fingerprint (v4) or the long key id if available // see http://bit.ly/1d4bxbk and http://bit.ly/1gD1wwr - String fingerprintOrKeyId = matcher.group(1).toLowerCase(Locale.US); + String fingerprintOrKeyId = matcher.group(1).toLowerCase(Locale.ENGLISH); if (fingerprintOrKeyId.length() > 16) { entry.setFingerprintHex(fingerprintOrKeyId); entry.setKeyIdHex("0x" + fingerprintOrKeyId.substring(fingerprintOrKeyId.length() diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ParcelableKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ParcelableKeyRing.java index 066c51a13..8609a7082 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ParcelableKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ParcelableKeyRing.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.sufficientlysecure.keychain.keyimport; import android.os.Parcel; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java index ee0dfefa4..1da66872d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.sufficientlysecure.keychain.pgp; import org.spongycastle.openpgp.PGPKeyRing; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKey.java index 981caad49..ce6498df1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKey.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.sufficientlysecure.keychain.pgp; import org.spongycastle.openpgp.PGPPublicKey; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java index 70288dceb..972e45c2e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.sufficientlysecure.keychain.pgp; import org.spongycastle.bcpg.ArmoredOutputStream; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java index ff82da07a..e39924f7e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.sufficientlysecure.keychain.pgp; import org.spongycastle.bcpg.HashAlgorithmTags; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKeyRing.java index 7d3e26000..4f74a2336 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKeyRing.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.sufficientlysecure.keychain.pgp; import org.spongycastle.bcpg.S2K; @@ -6,7 +23,6 @@ import org.spongycastle.openpgp.PGPKeyRing; import org.spongycastle.openpgp.PGPObjectFactory; import org.spongycastle.openpgp.PGPPrivateKey; import org.spongycastle.openpgp.PGPPublicKey; -import org.spongycastle.openpgp.PGPPublicKeyRing; import org.spongycastle.openpgp.PGPSecretKey; import org.spongycastle.openpgp.PGPSecretKeyRing; import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java index 7d11a20d3..35020b815 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.sufficientlysecure.keychain.pgp; import android.text.TextUtils; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/NullProgressable.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/NullProgressable.java index 68312dca3..5ef4f7998 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/NullProgressable.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/NullProgressable.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.sufficientlysecure.keychain.pgp; /** diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpConversionHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpConversionHelper.java deleted file mode 100644 index 3a5a96fbb..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpConversionHelper.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2012-2014 Dominik Schürmann - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.pgp; - -import org.spongycastle.openpgp.PGPObjectFactory; -import org.spongycastle.openpgp.PGPSecretKey; -import org.spongycastle.openpgp.PGPSecretKeyRing; -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.util.Log; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Iterator; - -public class PgpConversionHelper { - - /** - * Convert from byte[] to ArrayList - * - * @param keysBytes - * @return - */ - public static ArrayList BytesToPGPSecretKeyList(byte[] keysBytes) { - PGPObjectFactory factory = new PGPObjectFactory(keysBytes); - Object obj = null; - ArrayList keys = new ArrayList(); - try { - while ((obj = factory.nextObject()) != null) { - PGPSecretKey secKey = null; - if (obj instanceof PGPSecretKey) { - secKey = (PGPSecretKey) obj; - if (secKey == null) { - Log.e(Constants.TAG, "No keys given!"); - } - keys.add(new UncachedSecretKey(secKey)); - } else if (obj instanceof PGPSecretKeyRing) { //master keys are sent as keyrings - PGPSecretKeyRing keyRing = null; - keyRing = (PGPSecretKeyRing) obj; - if (keyRing == null) { - Log.e(Constants.TAG, "No keys given!"); - } - @SuppressWarnings("unchecked") - Iterator itr = keyRing.getSecretKeys(); - while (itr.hasNext()) { - keys.add(new UncachedSecretKey(itr.next())); - } - } - } - } catch (IOException e) { - } - - return keys; - } - - /** - * Convert from byte[] to PGPSecretKey - *

- * Singles keys are encoded as keyRings with one single key in it by Bouncy Castle - * - * @param keyBytes - * @return - */ - public static UncachedSecretKey BytesToPGPSecretKey(byte[] keyBytes) { - PGPObjectFactory factory = new PGPObjectFactory(keyBytes); - Object obj = null; - try { - obj = factory.nextObject(); - } catch (IOException e) { - Log.e(Constants.TAG, "Error while converting to PGPSecretKey!", e); - } - PGPSecretKey secKey = null; - if (obj instanceof PGPSecretKey) { - if ((secKey = (PGPSecretKey) obj) == null) { - Log.e(Constants.TAG, "No keys given!"); - } - } else if (obj instanceof PGPSecretKeyRing) { //master keys are sent as keyrings - PGPSecretKeyRing keyRing = null; - if ((keyRing = (PGPSecretKeyRing) obj) == null) { - Log.e(Constants.TAG, "No keys given!"); - } - secKey = keyRing.getSecretKey(); - } - - return new UncachedSecretKey(secKey); - } - -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java index 20bb1f97c..459b80be2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java @@ -24,7 +24,7 @@ import android.text.Spannable; import android.text.SpannableStringBuilder; import android.text.style.ForegroundColorSpan; -import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.bcpg.PublicKeyAlgorithmTags; import org.spongycastle.util.encoders.Hex; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; @@ -37,9 +37,6 @@ import java.util.Locale; public class PgpKeyHelper { - /** - * TODO: Only used in HkpKeyServer. Get rid of this one! - */ public static String getAlgorithmInfo(int algorithm) { return getAlgorithmInfo(null, algorithm, 0); } @@ -55,25 +52,25 @@ public class PgpKeyHelper { String algorithmStr; switch (algorithm) { - case PGPPublicKey.RSA_ENCRYPT: - case PGPPublicKey.RSA_GENERAL: - case PGPPublicKey.RSA_SIGN: { + case PublicKeyAlgorithmTags.RSA_ENCRYPT: + case PublicKeyAlgorithmTags.RSA_GENERAL: + case PublicKeyAlgorithmTags.RSA_SIGN: { algorithmStr = "RSA"; break; } - case PGPPublicKey.DSA: { + case PublicKeyAlgorithmTags.DSA: { algorithmStr = "DSA"; break; } - case PGPPublicKey.ELGAMAL_ENCRYPT: - case PGPPublicKey.ELGAMAL_GENERAL: { + case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT: + case PublicKeyAlgorithmTags.ELGAMAL_GENERAL: { algorithmStr = "ElGamal"; break; } - case PGPPublicKey.ECDSA: - case PGPPublicKey.ECDH: { + case PublicKeyAlgorithmTags.ECDSA: + case PublicKeyAlgorithmTags.ECDH: { algorithmStr = "ECC"; break; } @@ -82,7 +79,6 @@ public class PgpKeyHelper { if (context != null) { algorithmStr = context.getResources().getString(R.string.unknown_algorithm); } else { - // TODO algorithmStr = "unknown"; } break; @@ -104,7 +100,7 @@ public class PgpKeyHelper { * @return */ public static String convertFingerprintToHex(byte[] fingerprint) { - String hexString = Hex.toHexString(fingerprint).toLowerCase(Locale.US); + String hexString = Hex.toHexString(fingerprint).toLowerCase(Locale.ENGLISH); return hexString; } @@ -133,7 +129,7 @@ public class PgpKeyHelper { } private static String convertKeyIdToHex32bit(long keyId) { - String hexString = Long.toHexString(keyId & 0xffffffffL).toLowerCase(Locale.US); + String hexString = Long.toHexString(keyId & 0xffffffffL).toLowerCase(Locale.ENGLISH); while (hexString.length() < 8) { hexString = "0" + hexString; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java index 19b0d81b7..bb692555e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java @@ -20,6 +20,7 @@ package org.sufficientlysecure.keychain.pgp; import org.spongycastle.bcpg.CompressionAlgorithmTags; import org.spongycastle.bcpg.HashAlgorithmTags; +import org.spongycastle.bcpg.PublicKeyAlgorithmTags; import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags; import org.spongycastle.bcpg.sig.KeyFlags; import org.spongycastle.jce.spec.ElGamalParameterSpec; @@ -138,7 +139,7 @@ public class PgpKeyOperation { KeyPairGenerator keyGen; switch (algorithmChoice) { - case Constants.choice.algorithm.dsa: { + case PublicKeyAlgorithmTags.DSA: { progress(R.string.progress_generating_dsa, 30); keyGen = KeyPairGenerator.getInstance("DSA", Constants.BOUNCY_CASTLE_PROVIDER_NAME); keyGen.initialize(keySize, new SecureRandom()); @@ -146,7 +147,7 @@ public class PgpKeyOperation { break; } - case Constants.choice.algorithm.elgamal: { + case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT: { progress(R.string.progress_generating_elgamal, 30); keyGen = KeyPairGenerator.getInstance("ElGamal", Constants.BOUNCY_CASTLE_PROVIDER_NAME); BigInteger p = Primes.getBestPrime(keySize); @@ -159,7 +160,7 @@ public class PgpKeyOperation { break; } - case Constants.choice.algorithm.rsa: { + case PublicKeyAlgorithmTags.RSA_GENERAL: { progress(R.string.progress_generating_rsa, 30); keyGen = KeyPairGenerator.getInstance("RSA", Constants.BOUNCY_CASTLE_PROVIDER_NAME); keyGen.initialize(keySize, new SecureRandom()); @@ -217,7 +218,7 @@ public class PgpKeyOperation { return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); } - if (add.mAlgorithm == Constants.choice.algorithm.elgamal) { + if (add.mAlgorithm == PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT) { log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_MASTER_ELGAMAL, indent); return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); } @@ -356,7 +357,7 @@ public class PgpKeyOperation { progress(R.string.progress_modify_adduid, (i-1) * (100 / saveParcel.mAddUserIds.size())); String userId = saveParcel.mAddUserIds.get(i); - log.add(LogLevel.INFO, LogType.MSG_MF_UID_ADD, indent); + log.add(LogLevel.INFO, LogType.MSG_MF_UID_ADD, indent, userId); if (userId.equals("")) { log.add(LogLevel.ERROR, LogType.MSG_MF_UID_ERROR_EMPTY, indent+1); @@ -768,7 +769,7 @@ public class PgpKeyOperation { PGPSignatureSubpacketGenerator unhashedPacketsGen = new PGPSignatureSubpacketGenerator(); // If this key can sign, we need a primary key binding signature - if ((flags & KeyFlags.SIGN_DATA) != 0) { + if ((flags & KeyFlags.SIGN_DATA) > 0) { // cross-certify signing keys PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator(); subHashedPacketsGen.setSignatureCreationTime(false, todayDate); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java index 09c28e7c5..67eced699 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java @@ -20,6 +20,7 @@ package org.sufficientlysecure.keychain.pgp; import org.spongycastle.bcpg.ArmoredOutputStream; import org.spongycastle.bcpg.BCPGOutputStream; +import org.spongycastle.bcpg.CompressionAlgorithmTags; import org.spongycastle.openpgp.PGPCompressedDataGenerator; import org.spongycastle.openpgp.PGPEncryptedDataGenerator; import org.spongycastle.openpgp.PGPException; @@ -116,7 +117,7 @@ public class PgpSignEncrypt { // optional private Progressable mProgressable = null; private boolean mEnableAsciiArmorOutput = false; - private int mCompressionId = Constants.choice.compression.none; + private int mCompressionId = CompressionAlgorithmTags.UNCOMPRESSED; private long[] mEncryptionMasterKeyIds = null; private String mSymmetricPassphrase = null; private int mSymmetricEncryptionAlgorithm = 0; @@ -264,7 +265,7 @@ public class PgpSignEncrypt { boolean enableSignature = mSignatureMasterKeyId != Constants.key.none; boolean enableEncryption = ((mEncryptionMasterKeyIds != null && mEncryptionMasterKeyIds.length > 0) || mSymmetricPassphrase != null); - boolean enableCompression = (mCompressionId != Constants.choice.compression.none); + boolean enableCompression = (mCompressionId != CompressionAlgorithmTags.UNCOMPRESSED); Log.d(Constants.TAG, "enableSignature:" + enableSignature + "\nenableEncryption:" + enableEncryption diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java index d29f19d67..18ddaa0ec 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.sufficientlysecure.keychain.pgp; import org.spongycastle.bcpg.ArmoredOutputStream; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java index 4a03d942b..341ca6d04 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.sufficientlysecure.keychain.pgp; import org.spongycastle.bcpg.sig.KeyFlags; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedSecretKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedSecretKey.java index 0e14a7fd3..8dc28c2b3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedSecretKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedSecretKey.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.sufficientlysecure.keychain.pgp; import org.spongycastle.bcpg.sig.KeyFlags; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java index 07fb4fb9e..ebd110dc5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.sufficientlysecure.keychain.pgp; import org.spongycastle.bcpg.SignatureSubpacket; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java index aa0207a6a..e076fd9cc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.sufficientlysecure.keychain.provider; import android.database.Cursor; @@ -201,7 +218,7 @@ public class CachedPublicKeyRing extends KeyRing { } private Cursor getSubkeys() throws PgpGeneralException { - Uri keysUri = KeychainContract.Keys.buildKeysUri(Long.toString(extractOrGetMasterKeyId())); + Uri keysUri = KeychainContract.Keys.buildKeysUri(extractOrGetMasterKeyId()); return mProviderHelper.getContentResolver().query(keysUri, null, null, null, null); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java index 483f762f7..56168847f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java @@ -172,8 +172,8 @@ public class KeychainContract { return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).build(); } - public static Uri buildPublicKeyRingUri(String masterKeyId) { - return CONTENT_URI.buildUpon().appendPath(masterKeyId).appendPath(PATH_PUBLIC).build(); + public static Uri buildPublicKeyRingUri(long masterKeyId) { + return CONTENT_URI.buildUpon().appendPath(Long.toString(masterKeyId)).appendPath(PATH_PUBLIC).build(); } public static Uri buildPublicKeyRingUri(Uri uri) { @@ -184,8 +184,8 @@ public class KeychainContract { return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).build(); } - public static Uri buildSecretKeyRingUri(String masterKeyId) { - return CONTENT_URI.buildUpon().appendPath(masterKeyId).appendPath(PATH_SECRET).build(); + public static Uri buildSecretKeyRingUri(long masterKeyId) { + return CONTENT_URI.buildUpon().appendPath(Long.toString(masterKeyId)).appendPath(PATH_SECRET).build(); } public static Uri buildSecretKeyRingUri(Uri uri) { @@ -210,8 +210,8 @@ public class KeychainContract { public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.org.sufficientlysecure.keychain.provider.keychain.keys"; - public static Uri buildKeysUri(String masterKeyId) { - return CONTENT_URI.buildUpon().appendPath(masterKeyId).appendPath(PATH_KEYS).build(); + public static Uri buildKeysUri(long masterKeyId) { + return CONTENT_URI.buildUpon().appendPath(Long.toString(masterKeyId)).appendPath(PATH_KEYS).build(); } public static Uri buildKeysUri(Uri uri) { @@ -237,8 +237,8 @@ public class KeychainContract { public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.org.sufficientlysecure.keychain.provider.user_ids"; - public static Uri buildUserIdsUri(String masterKeyId) { - return CONTENT_URI.buildUpon().appendPath(masterKeyId).appendPath(PATH_USER_IDS).build(); + public static Uri buildUserIdsUri(long masterKeyId) { + return CONTENT_URI.buildUpon().appendPath(Long.toString(masterKeyId)).appendPath(PATH_USER_IDS).build(); } public static Uri buildUserIdsUri(Uri uri) { @@ -304,12 +304,14 @@ public class KeychainContract { public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon() .appendPath(BASE_KEY_RINGS).build(); - public static Uri buildCertsUri(String masterKeyId) { - return CONTENT_URI.buildUpon().appendPath(masterKeyId).appendPath(PATH_CERTS).build(); + public static Uri buildCertsUri(long masterKeyId) { + return CONTENT_URI.buildUpon().appendPath(Long.toString(masterKeyId)).appendPath(PATH_CERTS).build(); } - public static Uri buildCertsSpecificUri(String masterKeyId, String rank, String certifier) { - return CONTENT_URI.buildUpon().appendPath(masterKeyId).appendPath(PATH_CERTS).appendPath(rank).appendPath(certifier).build(); + public static Uri buildCertsSpecificUri(long masterKeyId, long rank, long certifier) { + return CONTENT_URI.buildUpon().appendPath(Long.toString(masterKeyId)) + .appendPath(PATH_CERTS).appendPath(Long.toString(rank)) + .appendPath(Long.toString(certifier)).build(); } public static Uri buildCertsUri(Uri uri) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java index 7a63ec3d7..3a859f505 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java @@ -165,8 +165,8 @@ public class KeychainDatabase extends SQLiteOpenHelper { // make sure this is only done once, on the first instance! boolean iAmIt = false; - synchronized(KeychainDatabase.class) { - if(!KeychainDatabase.apgHack) { + synchronized (KeychainDatabase.class) { + if (!KeychainDatabase.apgHack) { iAmIt = true; KeychainDatabase.apgHack = true; } @@ -316,42 +316,37 @@ public class KeychainDatabase extends SQLiteOpenHelper { } private static void copy(File in, File out) throws IOException { - FileInputStream ss = new FileInputStream(in); - FileOutputStream ds = new FileOutputStream(out); + FileInputStream is = new FileInputStream(in); + FileOutputStream os = new FileOutputStream(out); byte[] buf = new byte[512]; - while (ss.available() > 0) { - int count = ss.read(buf, 0, 512); - ds.write(buf, 0, count); + while (is.available() > 0) { + int count = is.read(buf, 0, 512); + os.write(buf, 0, count); } } - public static void debugRead(Context context) throws IOException { + public static void debugBackup(Context context, boolean restore) throws IOException { if (!Constants.DEBUG) { return; } - File in = context.getDatabasePath("debug.db"); - File out = context.getDatabasePath("openkeychain.db"); + + File in; + File out; + if (restore) { + in = context.getDatabasePath("debug_backup.db"); + out = context.getDatabasePath(DATABASE_NAME); + } else { + in = context.getDatabasePath(DATABASE_NAME); + out = context.getDatabasePath("debug_backup.db"); + out.createNewFile(); + } if (!in.canRead()) { throw new IOException("Cannot read " + in.getName()); } - if (!out.canRead()) { + if (!out.canWrite()) { throw new IOException("Cannot write " + out.getName()); } copy(in, out); } - public static void debugWrite(Context context) throws IOException { - if (!Constants.DEBUG) { - return; - } - File in = context.getDatabasePath("openkeychain.db"); - File out = context.getDatabasePath("debug.db"); - if (!in.canRead()) { - throw new IOException("Cannot read " + in.getName()); - } - if (!out.canRead()) { - throw new IOException("Cannot write " + out.getName()); - } - copy(in, out); - } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java index aa85577e0..6111a4ef4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java @@ -28,17 +28,14 @@ import android.os.RemoteException; import android.support.v4.util.LongSparseArray; import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKey; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing; import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.NullProgressable; -import org.sufficientlysecure.keychain.pgp.Progressable; -import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKey; -import org.sufficientlysecure.keychain.service.OperationResultParcel.LogType; -import org.sufficientlysecure.keychain.service.OperationResultParcel.LogLevel; -import org.sufficientlysecure.keychain.service.OperationResultParcel.OperationLog; import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; +import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.pgp.UncachedPublicKey; import org.sufficientlysecure.keychain.pgp.WrappedSignature; @@ -51,6 +48,9 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds; import org.sufficientlysecure.keychain.remote.AccountSettings; import org.sufficientlysecure.keychain.remote.AppSettings; +import org.sufficientlysecure.keychain.service.OperationResultParcel.LogLevel; +import org.sufficientlysecure.keychain.service.OperationResultParcel.LogType; +import org.sufficientlysecure.keychain.service.OperationResultParcel.OperationLog; import org.sufficientlysecure.keychain.service.OperationResults.SaveKeyringResult; import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.util.Log; @@ -299,7 +299,7 @@ public class ProviderHelper { return SaveKeyringResult.RESULT_ERROR; } - Uri uri = KeyRingData.buildPublicKeyRingUri(Long.toString(masterKeyId)); + Uri uri = KeyRingData.buildPublicKeyRingUri(masterKeyId); operations.add(ContentProviderOperation.newInsert(uri).withValues(values).build()); } @@ -307,7 +307,7 @@ public class ProviderHelper { progress.setProgress(LogType.MSG_IP_INSERT_SUBKEYS.getMsgId(), 40, 100); mIndent += 1; { // insert subkeys - Uri uri = Keys.buildKeysUri(Long.toString(masterKeyId)); + Uri uri = Keys.buildKeysUri(masterKeyId); int rank = 0; for (CanonicalizedPublicKey key : keyRing.publicKeyIterator()) { long keyId = key.getKeyId(); @@ -498,7 +498,7 @@ public class ProviderHelper { try { // delete old version of this keyRing, which also deletes all keys and userIds on cascade int deleted = mContentResolver.delete( - KeyRingData.buildPublicKeyRingUri(Long.toString(masterKeyId)), null, null); + KeyRingData.buildPublicKeyRingUri(masterKeyId), null, null); if (deleted > 0) { log(LogLevel.DEBUG, LogType.MSG_IP_DELETE_OLD_OK); result |= SaveKeyringResult.UPDATED; @@ -567,7 +567,7 @@ public class ProviderHelper { values.put(KeyRingData.MASTER_KEY_ID, masterKeyId); values.put(KeyRingData.KEY_RING_DATA, keyRing.getEncoded()); // insert new version of this keyRing - Uri uri = KeyRingData.buildSecretKeyRingUri(Long.toString(masterKeyId)); + Uri uri = KeyRingData.buildSecretKeyRingUri(masterKeyId); if (mContentResolver.insert(uri, values) == null) { log(LogLevel.ERROR, LogType.MSG_IS_DB_EXCEPTION); return SaveKeyringResult.RESULT_ERROR; @@ -579,7 +579,7 @@ public class ProviderHelper { } { - Uri uri = Keys.buildKeysUri(Long.toString(masterKeyId)); + Uri uri = Keys.buildKeysUri(masterKeyId); // first, mark all keys as not available ContentValues values = new ContentValues(); @@ -836,7 +836,7 @@ public class ProviderHelper { values.put(Certs.VERIFIED, verified); values.put(Certs.DATA, cert.getEncoded()); - Uri uri = Certs.buildCertsUri(Long.toString(masterKeyId)); + Uri uri = Certs.buildCertsUri(masterKeyId); return ContentProviderOperation.newInsert(uri).withValues(values).build(); } @@ -853,7 +853,7 @@ public class ProviderHelper { values.put(UserIds.IS_REVOKED, item.isRevoked); values.put(UserIds.RANK, rank); - Uri uri = UserIds.buildUserIdsUri(Long.toString(masterKeyId)); + Uri uri = UserIds.buildUserIdsUri(masterKeyId); return ContentProviderOperation.newInsert(uri).withValues(values).build(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java new file mode 100644 index 000000000..87c0cc0a6 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.provider; + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.MatrixCursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.net.Uri; +import android.os.ParcelFileDescriptor; +import android.provider.OpenableColumns; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.util.DatabaseUtil; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; + +public class TemporaryStorageProvider extends ContentProvider { + + private static final String DB_NAME = "tempstorage.db"; + private static final String TABLE_FILES = "files"; + private static final String COLUMN_ID = "id"; + private static final String COLUMN_NAME = "name"; + private static final String COLUMN_TIME = "time"; + private static final Uri BASE_URI = Uri.parse("content://org.sufficientlysecure.keychain.tempstorage/"); + private static final int DB_VERSION = 1; + + public static Uri createFile(Context context, String targetName) { + ContentValues contentValues = new ContentValues(); + contentValues.put(COLUMN_NAME, targetName); + return context.getContentResolver().insert(BASE_URI, contentValues); + } + + public static int cleanUp(Context context) { + return context.getContentResolver().delete(BASE_URI, COLUMN_TIME + "< ?", + new String[]{Long.toString(System.currentTimeMillis() - Constants.TEMPFILE_TTL)}); + } + + private class TemporaryStorageDatabase extends SQLiteOpenHelper { + + public TemporaryStorageDatabase(Context context) { + super(context, DB_NAME, null, DB_VERSION); + } + + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_FILES + " (" + + COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + + COLUMN_NAME + " TEXT, " + + COLUMN_TIME + " INTEGER" + + ");"); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + + } + } + + private TemporaryStorageDatabase db; + + private File getFile(Uri uri) throws FileNotFoundException { + try { + return getFile(Integer.parseInt(uri.getLastPathSegment())); + } catch (NumberFormatException e) { + throw new FileNotFoundException(); + } + } + + private File getFile(int id) { + return new File(getContext().getCacheDir(), "temp/" + id); + } + + @Override + public boolean onCreate() { + db = new TemporaryStorageDatabase(getContext()); + return new File(getContext().getCacheDir(), "temp").mkdirs(); + } + + @Override + public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { + File file; + try { + file = getFile(uri); + } catch (FileNotFoundException e) { + return null; + } + Cursor fileName = db.getReadableDatabase().query(TABLE_FILES, new String[]{COLUMN_NAME}, COLUMN_ID + "=?", + new String[]{uri.getLastPathSegment()}, null, null, null); + if (fileName != null) { + if (fileName.moveToNext()) { + MatrixCursor cursor = + new MatrixCursor(new String[]{OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE, "_data"}); + cursor.newRow().add(fileName.getString(0)).add(file.length()).add(file.getAbsolutePath()); + fileName.close(); + return cursor; + } + fileName.close(); + } + return null; + } + + @Override + public String getType(Uri uri) { + // Note: If we can find a files mime type, we can decrypt it to temp storage and open it after + // encryption. The mime type is needed, else UI really sucks and some apps break. + return "*/*"; + } + + @Override + public Uri insert(Uri uri, ContentValues values) { + if (!values.containsKey(COLUMN_TIME)) { + values.put(COLUMN_TIME, System.currentTimeMillis()); + } + int insert = (int) db.getWritableDatabase().insert(TABLE_FILES, null, values); + try { + getFile(insert).createNewFile(); + } catch (IOException e) { + return null; + } + return Uri.withAppendedPath(BASE_URI, Long.toString(insert)); + } + + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) { + if (uri.getLastPathSegment() != null) { + selection = DatabaseUtil.concatenateWhere(selection, COLUMN_ID + "=?"); + selectionArgs = DatabaseUtil.appendSelectionArgs(selectionArgs, new String[]{uri.getLastPathSegment()}); + } + Cursor files = db.getReadableDatabase().query(TABLE_FILES, new String[]{COLUMN_ID}, selection, + selectionArgs, null, null, null); + if (files != null) { + while (files.moveToNext()) { + getFile(files.getInt(0)).delete(); + } + files.close(); + return db.getWritableDatabase().delete(TABLE_FILES, selection, selectionArgs); + } + return 0; + } + + @Override + public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { + throw new UnsupportedOperationException("Update not supported"); + } + + @Override + public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { + return openFileHelper(uri, mode); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AccountSettings.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AccountSettings.java index a25ecded6..d6013b49d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AccountSettings.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AccountSettings.java @@ -17,6 +17,7 @@ package org.sufficientlysecure.keychain.remote; +import org.spongycastle.bcpg.CompressionAlgorithmTags; import org.spongycastle.bcpg.HashAlgorithmTags; import org.spongycastle.openpgp.PGPEncryptedData; import org.sufficientlysecure.keychain.Constants; @@ -39,7 +40,7 @@ public class AccountSettings { // defaults: this.mEncryptionAlgorithm = PGPEncryptedData.AES_256; this.mHashAlgorithm = HashAlgorithmTags.SHA512; - this.mCompression = Constants.choice.compression.zlib; + this.mCompression = CompressionAlgorithmTags.ZLIB; } public String getAccountName() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsFragment.java index 2ba792f9a..8468f5eca 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsFragment.java @@ -26,9 +26,9 @@ import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.AdapterView.OnItemSelectedListener; +import android.widget.Button; import android.widget.Spinner; import android.widget.TextView; -import android.widget.Button; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java index 6c4d59a77..43ed2dad0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java @@ -23,7 +23,13 @@ import android.content.AbstractThreadedSyncAdapter; import android.content.ContentProviderClient; import android.content.Intent; import android.content.SyncResult; -import android.os.*; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.Messenger; + import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.KeychainApplication; import org.sufficientlysecure.keychain.helper.ContactHelper; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/DummyAccountService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/DummyAccountService.java index 008502ce7..41ff6d02b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/DummyAccountService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/DummyAccountService.java @@ -29,6 +29,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.widget.Toast; + import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.util.Log; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java index 7250a861d..a7115a53d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java @@ -24,7 +24,6 @@ import android.net.Uri; import android.os.Bundle; import android.os.Message; import android.os.Messenger; -import android.os.Parcel; import android.os.RemoteException; import org.sufficientlysecure.keychain.Constants; @@ -32,7 +31,6 @@ import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.helper.FileHelper; import org.sufficientlysecure.keychain.helper.OtherHelper; import org.sufficientlysecure.keychain.helper.Preferences; -import org.sufficientlysecure.keychain.util.FileImportCache; import org.sufficientlysecure.keychain.keyimport.HkpKeyserver; import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry; import org.sufficientlysecure.keychain.keyimport.KeybaseKeyserver; @@ -56,6 +54,7 @@ import org.sufficientlysecure.keychain.provider.KeychainDatabase; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.OperationResults.EditKeyResult; import org.sufficientlysecure.keychain.service.OperationResults.ImportKeyResult; +import org.sufficientlysecure.keychain.util.FileImportCache; import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.ProgressScaler; @@ -63,11 +62,9 @@ import org.sufficientlysecure.keychain.util.ProgressScaler; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; -import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.List; @@ -110,8 +107,10 @@ public class KeychainIntentService extends IntentService public static final String SOURCE = "source"; // possible targets: public static final int IO_BYTES = 1; - public static final int IO_FILE = 2; // This was misleadingly TARGET_URI before! - public static final int IO_URI = 3; + public static final int IO_URI = 2; + public static final int IO_URIS = 3; + + public static final String SELECTED_URI = "selected_uri"; // encrypt public static final String ENCRYPT_SIGNATURE_KEY_ID = "secret_key_id"; @@ -121,8 +120,10 @@ public class KeychainIntentService extends IntentService public static final String ENCRYPT_MESSAGE_BYTES = "message_bytes"; public static final String ENCRYPT_INPUT_FILE = "input_file"; public static final String ENCRYPT_INPUT_URI = "input_uri"; + public static final String ENCRYPT_INPUT_URIS = "input_uris"; public static final String ENCRYPT_OUTPUT_FILE = "output_file"; public static final String ENCRYPT_OUTPUT_URI = "output_uri"; + public static final String ENCRYPT_OUTPUT_URIS = "output_uris"; public static final String ENCRYPT_SYMMETRIC_PASSPHRASE = "passphrase"; // decrypt/verify @@ -142,6 +143,7 @@ public class KeychainIntentService extends IntentService // export key public static final String EXPORT_OUTPUT_STREAM = "export_output_stream"; public static final String EXPORT_FILENAME = "export_filename"; + public static final String EXPORT_URI = "export_uri"; public static final String EXPORT_SECRET = "export_secret"; public static final String EXPORT_ALL = "export_all"; public static final String EXPORT_KEY_RING_MASTER_KEY_ID = "export_key_ring_id"; @@ -226,6 +228,7 @@ public class KeychainIntentService extends IntentService try { /* Input */ int source = data.get(SOURCE) != null ? data.getInt(SOURCE) : data.getInt(TARGET); + Bundle resultData = new Bundle(); long signatureKeyId = data.getLong(ENCRYPT_SIGNATURE_KEY_ID); String symmetricPassphrase = data.getString(ENCRYPT_SYMMETRIC_PASSPHRASE); @@ -233,44 +236,48 @@ public class KeychainIntentService extends IntentService boolean useAsciiArmor = data.getBoolean(ENCRYPT_USE_ASCII_ARMOR); long encryptionKeyIds[] = data.getLongArray(ENCRYPT_ENCRYPTION_KEYS_IDS); int compressionId = data.getInt(ENCRYPT_COMPRESSION_ID); - InputData inputData = createEncryptInputData(data); - OutputStream outStream = createCryptOutputStream(data); + int urisCount = data.containsKey(ENCRYPT_INPUT_URIS) ? data.getParcelableArrayList(ENCRYPT_INPUT_URIS).size() : 1; + for (int i = 0; i < urisCount; i++) { + data.putInt(SELECTED_URI, i); + InputData inputData = createEncryptInputData(data); + OutputStream outStream = createCryptOutputStream(data); - /* Operation */ - PgpSignEncrypt.Builder builder = - new PgpSignEncrypt.Builder( - new ProviderHelper(this), - PgpHelper.getFullVersion(this), - inputData, outStream); - builder.setProgressable(this); + /* Operation */ + PgpSignEncrypt.Builder builder = + new PgpSignEncrypt.Builder( + new ProviderHelper(this), + PgpHelper.getFullVersion(this), + inputData, outStream); + builder.setProgressable(this); - builder.setEnableAsciiArmorOutput(useAsciiArmor) - .setCompressionId(compressionId) - .setSymmetricEncryptionAlgorithm( - Preferences.getPreferences(this).getDefaultEncryptionAlgorithm()) - .setEncryptionMasterKeyIds(encryptionKeyIds) - .setSymmetricPassphrase(symmetricPassphrase) - .setSignatureMasterKeyId(signatureKeyId) - .setEncryptToSigner(true) - .setSignatureHashAlgorithm( - Preferences.getPreferences(this).getDefaultHashAlgorithm()) - .setSignaturePassphrase( - PassphraseCacheService.getCachedPassphrase(this, signatureKeyId)); + builder.setEnableAsciiArmorOutput(useAsciiArmor) + .setCompressionId(compressionId) + .setSymmetricEncryptionAlgorithm( + Preferences.getPreferences(this).getDefaultEncryptionAlgorithm()) + .setEncryptionMasterKeyIds(encryptionKeyIds) + .setSymmetricPassphrase(symmetricPassphrase) + .setSignatureMasterKeyId(signatureKeyId) + .setEncryptToSigner(true) + .setSignatureHashAlgorithm( + Preferences.getPreferences(this).getDefaultHashAlgorithm()) + .setSignaturePassphrase( + PassphraseCacheService.getCachedPassphrase(this, signatureKeyId)); + + // this assumes that the bytes are cleartext (valid for current implementation!) + if (source == IO_BYTES) { + builder.setCleartextInput(true); + } + + builder.build().execute(); + + outStream.close(); + + /* Output */ + + finalizeEncryptOutputStream(data, resultData, outStream); - // this assumes that the bytes are cleartext (valid for current implementation!) - if (source == IO_BYTES) { - builder.setCleartextInput(true); } - builder.build().execute(); - - outStream.close(); - - /* Output */ - - Bundle resultData = new Bundle(); - finalizeEncryptOutputStream(data, resultData, outStream); - OtherHelper.logDebugBundle(resultData, "resultData"); sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData); @@ -415,13 +422,16 @@ public class KeychainIntentService extends IntentService boolean exportSecret = data.getBoolean(EXPORT_SECRET, false); long[] masterKeyIds = data.getLongArray(EXPORT_KEY_RING_MASTER_KEY_ID); String outputFile = data.getString(EXPORT_FILENAME); + Uri outputUri = data.getParcelable(EXPORT_URI); // If not exporting all keys get the masterKeyIds of the keys to export from the intent boolean exportAll = data.getBoolean(EXPORT_ALL); - // check if storage is ready - if (!FileHelper.isStorageMounted(outputFile)) { - throw new PgpGeneralException(getString(R.string.error_external_storage_not_ready)); + if (outputFile != null) { + // check if storage is ready + if (!FileHelper.isStorageMounted(outputFile)) { + throw new PgpGeneralException(getString(R.string.error_external_storage_not_ready)); + } } ArrayList publicMasterKeyIds = new ArrayList(); @@ -453,12 +463,19 @@ public class KeychainIntentService extends IntentService } } + OutputStream outStream; + if (outputFile != null) { + outStream = new FileOutputStream(outputFile); + } else { + outStream = getContentResolver().openOutputStream(outputUri); + } + PgpImportExport pgpImportExport = new PgpImportExport(this, this, this); Bundle resultData = pgpImportExport .exportKeyRings(publicMasterKeyIds, secretMasterKeyIds, - new FileOutputStream(outputFile)); + outStream); - if (mIsCanceled) { + if (mIsCanceled && outputFile != null) { new File(outputFile).delete(); } @@ -694,22 +711,17 @@ public class KeychainIntentService extends IntentService byte[] bytes = data.getByteArray(bytesName); return new InputData(new ByteArrayInputStream(bytes), bytes.length); - case IO_FILE: /* encrypting file */ - String inputFile = data.getString(ENCRYPT_INPUT_FILE); - - // check if storage is ready - if (!FileHelper.isStorageMounted(inputFile)) { - throw new PgpGeneralException(getString(R.string.error_external_storage_not_ready)); - } - - return new InputData(new FileInputStream(inputFile), new File(inputFile).length()); - case IO_URI: /* encrypting content uri */ Uri providerUri = data.getParcelable(ENCRYPT_INPUT_URI); // InputStream - InputStream in = getContentResolver().openInputStream(providerUri); - return new InputData(in, 0); + return new InputData(getContentResolver().openInputStream(providerUri), FileHelper.getFileSize(this, providerUri, 0)); + + case IO_URIS: + providerUri = data.getParcelableArrayList(ENCRYPT_INPUT_URIS).get(data.getInt(SELECTED_URI)); + + // InputStream + return new InputData(getContentResolver().openInputStream(providerUri), FileHelper.getFileSize(this, providerUri, 0)); default: throw new PgpGeneralException("No target choosen!"); @@ -722,23 +734,16 @@ public class KeychainIntentService extends IntentService case IO_BYTES: return new ByteArrayOutputStream(); - case IO_FILE: - String outputFile = data.getString(ENCRYPT_OUTPUT_FILE); - - // check if storage is ready - if (!FileHelper.isStorageMounted(outputFile)) { - throw new PgpGeneralException( - getString(R.string.error_external_storage_not_ready)); - } - - // OutputStream - return new FileOutputStream(outputFile); - case IO_URI: Uri providerUri = data.getParcelable(ENCRYPT_OUTPUT_URI); return getContentResolver().openOutputStream(providerUri); + case IO_URIS: + providerUri = data.getParcelableArrayList(ENCRYPT_OUTPUT_URIS).get(data.getInt(SELECTED_URI)); + + return getContentResolver().openOutputStream(providerUri); + default: throw new PgpGeneralException("No target choosen!"); } @@ -758,12 +763,9 @@ public class KeychainIntentService extends IntentService case IO_BYTES: byte output[] = ((ByteArrayOutputStream) outStream).toByteArray(); resultData.putByteArray(bytesName, output); - break; - case IO_FILE: - // nothing, file was written, just send okay and verification bundle - break; case IO_URI: + case IO_URIS: // nothing, output was written, just send okay and verification bundle break; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResultParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResultParcel.java index c27b3f6da..d7d98fd68 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResultParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResultParcel.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.sufficientlysecure.keychain.service; import android.app.Activity; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResults.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResults.java index 11829e7b8..543b83edb 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResults.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResults.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.sufficientlysecure.keychain.service; import android.app.Activity; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java index 97d92dbf7..e813ca26c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java @@ -34,9 +34,8 @@ import android.os.IBinder; import android.os.Message; import android.os.Messenger; import android.os.RemoteException; - -import android.support.v4.util.LongSparseArray; import android.support.v4.app.NotificationCompat; +import android.support.v4.util.LongSparseArray; import org.spongycastle.bcpg.S2K; import org.sufficientlysecure.keychain.Constants; @@ -396,8 +395,8 @@ public class PassphraseCacheService extends Service { } else { // Fallback, since expandable notifications weren't available back then builder.setSmallIcon(R.drawable.ic_launcher) - .setContentTitle(String.format(getString(R.string.passp_cache_notif_n_keys, - mPassphraseCache.size()))) + .setContentTitle(String.format(getString(R.string.passp_cache_notif_n_keys), + mPassphraseCache.size())) .setContentText(getString(R.string.passp_cache_notif_click_to_clear)); Intent intent = new Intent(getApplicationContext(), PassphraseCacheService.class); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java index 5e90b396e..490a8e738 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.sufficientlysecure.keychain.service; import android.os.Parcel; @@ -6,16 +23,17 @@ import android.os.Parcelable; import java.io.Serializable; import java.util.ArrayList; -/** This class is a a transferable representation for a collection of changes +/** + * This class is a a transferable representation for a collection of changes * to be done on a keyring. - * + *

* This class should include all types of operations supported in the backend. - * + *

* All changes are done in a differential manner. Besides the two key * identification attributes, all attributes may be null, which indicates no * change to the keyring. This is also the reason why boxed values are used * instead of primitives in the subclasses. - * + *

* Application of operations in the backend should be fail-fast, which means an * error in any included operation (for example revocation of a non-existent * subkey) will cause the operation as a whole to fail. @@ -65,12 +83,23 @@ public class SaveKeyringParcel implements Parcelable { public int mKeysize; public int mFlags; public Long mExpiry; + public SubkeyAdd(int algorithm, int keysize, int flags, Long expiry) { mAlgorithm = algorithm; mKeysize = keysize; mFlags = flags; mExpiry = expiry; } + + @Override + public String toString() { + String out = "mAlgorithm: " + mAlgorithm + ", "; + out += "mKeysize: " + mKeysize + ", "; + out += "mFlags: " + mFlags; + out += "mExpiry: " + mExpiry; + + return out; + } } public static class SubkeyChange implements Serializable { @@ -78,11 +107,46 @@ public class SaveKeyringParcel implements Parcelable { public Integer mFlags; // this is a long unix timestamp, in seconds (NOT MILLISECONDS!) public Long mExpiry; + + public SubkeyChange(long keyId) { + mKeyId = keyId; + } + public SubkeyChange(long keyId, Integer flags, Long expiry) { mKeyId = keyId; mFlags = flags; mExpiry = expiry; } + + @Override + public String toString() { + String out = "mKeyId: " + mKeyId + ", "; + out += "mFlags: " + mFlags + ", "; + out += "mExpiry: " + mExpiry; + + return out; + } + } + + public SubkeyChange getSubkeyChange(long keyId) { + for (SubkeyChange subkeyChange : mChangeSubKeys) { + if (subkeyChange.mKeyId == keyId) { + return subkeyChange; + } + } + return null; + } + + public SubkeyChange getOrCreateSubkeyChange(long keyId) { + SubkeyChange foundSubkeyChange = getSubkeyChange(keyId); + if (foundSubkeyChange != null) { + return foundSubkeyChange; + } else { + // else, create a new one + SubkeyChange newSubkeyChange = new SubkeyChange(keyId); + mChangeSubKeys.add(newSubkeyChange); + return newSubkeyChange; + } } public SaveKeyringParcel(Parcel source) { @@ -104,7 +168,7 @@ public class SaveKeyringParcel implements Parcelable { @Override public void writeToParcel(Parcel destination, int flags) { destination.writeInt(mMasterKeyId == null ? 0 : 1); - if(mMasterKeyId != null) { + if (mMasterKeyId != null) { destination.writeLong(mMasterKeyId); } destination.writeByteArray(mFingerprint); @@ -136,4 +200,17 @@ public class SaveKeyringParcel implements Parcelable { return 0; } + @Override + public String toString() { + String out = "mMasterKeyId: " + mMasterKeyId + "\n"; + out += "mNewPassphrase: " + mNewPassphrase + "\n"; + out += "mAddUserIds: " + mAddUserIds + "\n"; + out += "mAddSubKeys: " + mAddSubKeys + "\n"; + out += "mChangeSubKeys: " + mChangeSubKeys + "\n"; + out += "mChangePrimaryUserId: " + mChangePrimaryUserId + "\n"; + out += "mRevokeUserIds: " + mRevokeUserIds + "\n"; + out += "mRevokeSubKeys: " + mRevokeSubKeys; + + return out; + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java index 662ba4ce1..773be816a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java @@ -31,8 +31,8 @@ import android.view.ViewGroup; import android.widget.CheckBox; import android.widget.TextView; +import org.spongycastle.bcpg.PublicKeyAlgorithmTags; import org.spongycastle.bcpg.sig.KeyFlags; -import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.helper.Preferences; import org.sufficientlysecure.keychain.pgp.KeyRing; @@ -169,11 +169,12 @@ public class CreateKeyFinalFragment extends Fragment { Bundle data = new Bundle(); SaveKeyringParcel parcel = new SaveKeyringParcel(); - parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Constants.choice.algorithm.rsa, 4096, KeyFlags.CERTIFY_OTHER, null)); - parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Constants.choice.algorithm.rsa, 4096, KeyFlags.SIGN_DATA, null)); - parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Constants.choice.algorithm.rsa, 4096, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, null)); + parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(PublicKeyAlgorithmTags.RSA_GENERAL, 4096, KeyFlags.CERTIFY_OTHER, null)); + parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(PublicKeyAlgorithmTags.RSA_GENERAL, 4096, KeyFlags.SIGN_DATA, null)); + parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(PublicKeyAlgorithmTags.RSA_GENERAL, 4096, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, null)); String userId = KeyRing.createUserId(mName, mEmail, null); parcel.mAddUserIds.add(userId); + parcel.mChangePrimaryUserId = userId; parcel.mNewPassphrase = mPassphrase; // get selected key entries diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java index dba742268..830b9a279 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java @@ -23,11 +23,9 @@ import android.net.Uri; import android.os.Bundle; import android.support.v4.view.PagerTabStrip; import android.support.v4.view.ViewPager; -import android.widget.Toast; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.helper.FileHelper; import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter; import org.sufficientlysecure.keychain.util.Log; @@ -114,7 +112,7 @@ public class DecryptActivity extends DrawerActivity { } else { // Binary via content provider (could also be files) // override uri to get stream from send - uri = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM); + uri = intent.getParcelableExtra(Intent.EXTRA_STREAM); action = ACTION_DECRYPT; } } else if (Intent.ACTION_VIEW.equals(action)) { @@ -122,6 +120,7 @@ public class DecryptActivity extends DrawerActivity { // override action action = ACTION_DECRYPT; + mFileFragmentBundle.putBoolean(DecryptFileFragment.ARG_FROM_VIEW_INTENT, true); } String textData = extras.getString(EXTRA_TEXT); @@ -155,21 +154,8 @@ public class DecryptActivity extends DrawerActivity { } } } else if (ACTION_DECRYPT.equals(action) && uri != null) { - // get file path from uri - String path = FileHelper.getPath(this, uri); - - if (path != null) { - mFileFragmentBundle.putString(DecryptFileFragment.ARG_FILENAME, path); - mSwitchToTab = PAGER_TAB_FILE; - } else { - Log.e(Constants.TAG, - "Direct binary data without actual file in filesystem is not supported. " + - "Please use the Remote Service API!"); - Toast.makeText(this, R.string.error_only_files_are_supported, Toast.LENGTH_LONG) - .show(); - // end activity - finish(); - } + mFileFragmentBundle.putParcelable(DecryptFileFragment.ARG_URI, uri); + mSwitchToTab = PAGER_TAB_FILE; } else if (ACTION_DECRYPT.equals(action)) { Log.e(Constants.TAG, "Include the extra 'text' or an Uri with setData() in your Intent!"); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFileFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFileFragment.java index 56dfdbd95..c33b1489a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFileFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFileFragment.java @@ -20,19 +20,16 @@ package org.sufficientlysecure.keychain.ui; import android.app.Activity; import android.app.ProgressDialog; import android.content.Intent; -import android.database.Cursor; import android.net.Uri; +import android.os.Build; import android.os.Bundle; -import android.os.Handler; import android.os.Message; import android.os.Messenger; -import android.provider.OpenableColumns; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.CheckBox; -import android.widget.EditText; -import android.widget.ImageButton; +import android.widget.TextView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; @@ -40,31 +37,28 @@ import org.sufficientlysecure.keychain.helper.FileHelper; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyResult; import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; -import org.sufficientlysecure.keychain.util.Notify; import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment; -import org.sufficientlysecure.keychain.ui.dialog.FileDialogFragment; import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.Notify; import java.io.File; public class DecryptFileFragment extends DecryptFragment { - public static final String ARG_FILENAME = "filename"; + public static final String ARG_URI = "uri"; + public static final String ARG_FROM_VIEW_INTENT = "view_intent"; - private static final int RESULT_CODE_FILE = 0x00007003; + private static final int REQUEST_CODE_INPUT = 0x00007003; + private static final int REQUEST_CODE_OUTPUT = 0x00007007; // view - private EditText mFilename; + private TextView mFilename; private CheckBox mDeleteAfter; - private ImageButton mBrowse; private View mDecryptButton; - private String mInputFilename = null; + // model private Uri mInputUri = null; - private String mOutputFilename = null; private Uri mOutputUri = null; - private FileDialogFragment mFileDialog; - /** * Inflate the layout for this fragment */ @@ -72,17 +66,16 @@ public class DecryptFileFragment extends DecryptFragment { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.decrypt_file_fragment, container, false); - mFilename = (EditText) view.findViewById(R.id.decrypt_file_filename); - mBrowse = (ImageButton) view.findViewById(R.id.decrypt_file_browse); + mFilename = (TextView) view.findViewById(R.id.decrypt_file_filename); mDeleteAfter = (CheckBox) view.findViewById(R.id.decrypt_file_delete_after_decryption); mDecryptButton = view.findViewById(R.id.decrypt_file_action_decrypt); - mBrowse.setOnClickListener(new View.OnClickListener() { + view.findViewById(R.id.decrypt_file_browse).setOnClickListener(new View.OnClickListener() { public void onClick(View v) { - if (Constants.KITKAT) { - FileHelper.openDocument(DecryptFileFragment.this, mInputUri, "*/*", RESULT_CODE_FILE); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + FileHelper.openDocument(DecryptFileFragment.this, "*/*", REQUEST_CODE_INPUT); } else { - FileHelper.openFile(DecryptFileFragment.this, mFilename.getText().toString(), "*/*", - RESULT_CODE_FILE); + FileHelper.openFile(DecryptFileFragment.this, mInputUri, "*/*", + REQUEST_CODE_INPUT); } } }); @@ -100,74 +93,47 @@ public class DecryptFileFragment extends DecryptFragment { public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - String filename = getArguments().getString(ARG_FILENAME); - if (filename != null) { - mFilename.setText(filename); - } + setInputUri(getArguments().getParcelable(ARG_URI)); } - private String guessOutputFilename() { - File file = new File(mInputFilename); - String filename = file.getName(); - if (filename.endsWith(".asc") || filename.endsWith(".gpg") || filename.endsWith(".pgp")) { - filename = filename.substring(0, filename.length() - 4); - } - return Constants.Path.APP_DIR + "/" + filename; - } - - private void decryptAction() { - String currentFilename = mFilename.getText().toString(); - if (mInputFilename == null || !mInputFilename.equals(currentFilename)) { + private void setInputUri(Uri inputUri) { + if (inputUri == null) { mInputUri = null; - mInputFilename = mFilename.getText().toString(); - } - - if (mInputUri == null) { - mOutputFilename = guessOutputFilename(); - } - - if (mInputFilename.equals("")) { - Notify.showNotify(getActivity(), R.string.no_file_selected, Notify.Style.ERROR); + mFilename.setText(""); return; } - if (mInputUri == null && mInputFilename.startsWith("file")) { - File file = new File(mInputFilename); - if (!file.exists() || !file.isFile()) { - Notify.showNotify(getActivity(), getString(R.string.error_message, - getString(R.string.error_file_not_found)), Notify.Style.ERROR); - return; - } + mInputUri = inputUri; + mFilename.setText(FileHelper.getFilename(getActivity(), mInputUri)); + } + + private void decryptAction() { + if (mInputUri == null) { + Notify.showNotify(getActivity(), R.string.no_file_selected, Notify.Style.ERROR); + return; } askForOutputFilename(); } + private String removeEncryptedAppend(String name) { + if (name.endsWith(".asc") || name.endsWith(".gpg") || name.endsWith(".pgp")) { + return name.substring(0, name.length() - 4); + } + return name; + } + private void askForOutputFilename() { - // Message is received after passphrase is cached - Handler returnHandler = new Handler() { - @Override - public void handleMessage(Message message) { - if (message.what == FileDialogFragment.MESSAGE_OKAY) { - Bundle data = message.getData(); - if (data.containsKey(FileDialogFragment.MESSAGE_DATA_URI)) { - mOutputUri = data.getParcelable(FileDialogFragment.MESSAGE_DATA_URI); - } else { - mOutputFilename = data.getString(FileDialogFragment.MESSAGE_DATA_FILENAME); - } - decryptStart(null); - } - } - }; - - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(returnHandler); - - mFileDialog = FileDialogFragment.newInstance(messenger, - getString(R.string.title_decrypt_to_file), - getString(R.string.specify_file_to_decrypt_to), mOutputFilename, null); - - mFileDialog.show(getActivity().getSupportFragmentManager(), "fileDialog"); + String targetName = removeEncryptedAppend(FileHelper.getFilename(getActivity(), mInputUri)); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { + File file = new File(mInputUri.getPath()); + File parentDir = file.exists() ? file.getParentFile() : Constants.Path.APP_DIR; + File targetFile = new File(parentDir, targetName); + FileHelper.saveFile(this, getString(R.string.title_decrypt_to_file), + getString(R.string.specify_file_to_decrypt_to), targetFile, REQUEST_CODE_OUTPUT); + } else { + FileHelper.saveDocument(this, "*/*", targetName, REQUEST_CODE_OUTPUT); + } } @Override @@ -183,25 +149,13 @@ public class DecryptFileFragment extends DecryptFragment { intent.setAction(KeychainIntentService.ACTION_DECRYPT_VERIFY); // data - Log.d(Constants.TAG, "mInputFilename=" + mInputFilename + ", mOutputFilename=" - + mOutputFilename + ",mInputUri=" + mInputUri + ", mOutputUri=" - + mOutputUri); + Log.d(Constants.TAG, "mInputUri=" + mInputUri + ", mOutputUri=" + mOutputUri); - if (mInputUri != null) { - data.putInt(KeychainIntentService.SOURCE, KeychainIntentService.IO_URI); - data.putParcelable(KeychainIntentService.ENCRYPT_INPUT_URI, mInputUri); - } else { - data.putInt(KeychainIntentService.SOURCE, KeychainIntentService.IO_FILE); - data.putString(KeychainIntentService.ENCRYPT_INPUT_FILE, mInputFilename); - } + data.putInt(KeychainIntentService.SOURCE, KeychainIntentService.IO_URI); + data.putParcelable(KeychainIntentService.ENCRYPT_INPUT_URI, mInputUri); - if (mOutputUri != null) { - data.putInt(KeychainIntentService.TARGET, KeychainIntentService.IO_URI); - data.putParcelable(KeychainIntentService.ENCRYPT_OUTPUT_URI, mOutputUri); - } else { - data.putInt(KeychainIntentService.TARGET, KeychainIntentService.IO_FILE); - data.putString(KeychainIntentService.ENCRYPT_OUTPUT_FILE, mOutputFilename); - } + data.putInt(KeychainIntentService.TARGET, KeychainIntentService.IO_URI); + data.putParcelable(KeychainIntentService.ENCRYPT_OUTPUT_URI, mOutputUri); data.putString(KeychainIntentService.DECRYPT_PASSPHRASE, passphrase); @@ -232,15 +186,19 @@ public class DecryptFileFragment extends DecryptFragment { if (mDeleteAfter.isChecked()) { // Create and show dialog to delete original file - DeleteFileDialogFragment deleteFileDialog; - if (mInputUri != null) { - deleteFileDialog = DeleteFileDialogFragment.newInstance(mInputUri); - } else { - deleteFileDialog = DeleteFileDialogFragment - .newInstance(mInputFilename); - } + DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment.newInstance(mInputUri); deleteFileDialog.show(getActivity().getSupportFragmentManager(), "deleteDialog"); + setInputUri(null); } + + /* + // A future open after decryption feature + if () { + Intent viewFile = new Intent(Intent.ACTION_VIEW); + viewFile.setData(mOutputUri); + startActivity(viewFile); + } + */ } } } @@ -260,28 +218,17 @@ public class DecryptFileFragment extends DecryptFragment { @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode) { - case RESULT_CODE_FILE: { + case REQUEST_CODE_INPUT: { if (resultCode == Activity.RESULT_OK && data != null) { - if (Constants.KITKAT) { - mInputUri = data.getData(); - Cursor cursor = getActivity().getContentResolver().query(mInputUri, new String[]{OpenableColumns.DISPLAY_NAME}, null, null, null); - if (cursor != null) { - if (cursor.moveToNext()) { - mInputFilename = cursor.getString(0); - mFilename.setText(mInputFilename); - } - cursor.close(); - } - } else { - try { - String path = FileHelper.getPath(getActivity(), data.getData()); - Log.d(Constants.TAG, "path=" + path); - - mFilename.setText(path); - } catch (NullPointerException e) { - Log.e(Constants.TAG, "Nullpointer while retrieving path!"); - } - } + setInputUri(data.getData()); + } + return; + } + case REQUEST_CODE_OUTPUT: { + // This happens after output file was selected, so start our operation + if (resultCode == Activity.RESULT_OK && data != null) { + mOutputUri = data.getData(); + decryptStart(null); } return; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java index d4235b82b..211a20ec8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java @@ -25,11 +25,11 @@ import android.os.Message; import android.support.v4.app.Fragment; import android.view.View; import android.view.View.OnClickListener; +import android.widget.Button; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.TextView; -import android.widget.Button; import org.openintents.openpgp.OpenPgpSignatureResult; import org.sufficientlysecure.keychain.R; @@ -38,7 +38,7 @@ import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyResult; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment; -public class DecryptFragment extends Fragment { +public abstract class DecryptFragment extends Fragment { private static final int RESULT_CODE_LOOKUP_KEY = 0x00007006; protected long mSignatureKeyId = 0; @@ -217,8 +217,6 @@ public class DecryptFragment extends Fragment { * * @param passphrase */ - protected void decryptStart(String passphrase) { - - } + protected abstract void decryptStart(String passphrase); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DrawerActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DrawerActivity.java index 586442bb0..0e8d6f39d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DrawerActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DrawerActivity.java @@ -35,9 +35,9 @@ import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.ArrayAdapter; +import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; -import android.widget.ImageView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java index b76755bb2..5be196a45 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java @@ -37,7 +37,6 @@ import android.widget.AdapterView; import android.widget.ListView; import android.widget.Toast; -import org.spongycastle.bcpg.sig.KeyFlags; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; @@ -57,17 +56,15 @@ import org.sufficientlysecure.keychain.ui.adapter.SubkeysAdapter; import org.sufficientlysecure.keychain.ui.adapter.SubkeysAddedAdapter; import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter; import org.sufficientlysecure.keychain.ui.adapter.UserIdsAddedAdapter; +import org.sufficientlysecure.keychain.ui.dialog.AddSubkeyDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.AddUserIdDialogFragment; -import org.sufficientlysecure.keychain.ui.dialog.ChangeExpiryDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.EditSubkeyDialogFragment; +import org.sufficientlysecure.keychain.ui.dialog.EditSubkeyExpiryDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.EditUserIdDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment; import org.sufficientlysecure.keychain.util.Log; -import java.util.ArrayList; -import java.util.Date; - public class EditKeyFragment extends LoaderFragment implements LoaderManager.LoaderCallbacks { @@ -95,8 +92,8 @@ public class EditKeyFragment extends LoaderFragment implements private Uri mDataUri; private SaveKeyringParcel mSaveKeyringParcel; - private String mPrimaryUserId; + private String mPrimaryUserId; private String mCurrentPassphrase; /** @@ -215,8 +212,7 @@ public class EditKeyFragment extends LoaderFragment implements mUserIdsList.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView parent, View view, int position, long id) { - String userId = mUserIdsAdapter.getUserId(position); - editUserId(userId); + editUserId(position); } }); @@ -230,8 +226,7 @@ public class EditKeyFragment extends LoaderFragment implements mSubkeysList.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView parent, View view, int position, long id) { - long keyId = mSubkeysAdapter.getKeyId(position); - editSubkey(keyId); + editSubkey(position); } }); @@ -320,7 +315,11 @@ public class EditKeyFragment extends LoaderFragment implements setPassphraseDialog.show(getActivity().getSupportFragmentManager(), "setPassphraseDialog"); } - private void editUserId(final String userId) { + private void editUserId(final int position) { + final String userId = mUserIdsAdapter.getUserId(position); + final boolean isRevoked = mUserIdsAdapter.getIsRevoked(position); + final boolean isRevokedPending = mUserIdsAdapter.getIsRevokedPending(position); + Handler returnHandler = new Handler() { @Override public void handleMessage(Message message) { @@ -353,20 +352,21 @@ public class EditKeyFragment extends LoaderFragment implements DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() { public void run() { EditUserIdDialogFragment dialogFragment = - EditUserIdDialogFragment.newInstance(messenger); - + EditUserIdDialogFragment.newInstance(messenger, isRevoked, isRevokedPending); dialogFragment.show(getActivity().getSupportFragmentManager(), "editUserIdDialog"); } }); } - private void editSubkey(final long keyId) { + private void editSubkey(final int position) { + final long keyId = mSubkeysAdapter.getKeyId(position); + Handler returnHandler = new Handler() { @Override public void handleMessage(Message message) { switch (message.what) { case EditSubkeyDialogFragment.MESSAGE_CHANGE_EXPIRY: - editSubkeyExpiry(keyId); + editSubkeyExpiry(position); break; case EditSubkeyDialogFragment.MESSAGE_REVOKE: // toggle @@ -394,19 +394,19 @@ public class EditKeyFragment extends LoaderFragment implements }); } - private void editSubkeyExpiry(final long keyId) { + private void editSubkeyExpiry(final int position) { + final long keyId = mSubkeysAdapter.getKeyId(position); + final Long creationDate = mSubkeysAdapter.getCreationDate(position); + final Long expiryDate = mSubkeysAdapter.getExpiryDate(position); + Handler returnHandler = new Handler() { @Override public void handleMessage(Message message) { switch (message.what) { - case ChangeExpiryDialogFragment.MESSAGE_NEW_EXPIRY_DATE: - // toggle -// if (mSaveKeyringParcel.changePrimaryUserId != null -// && mSaveKeyringParcel.changePrimaryUserId.equals(userId)) { -// mSaveKeyringParcel.changePrimaryUserId = null; -// } else { -// mSaveKeyringParcel.changePrimaryUserId = userId; -// } + case EditSubkeyExpiryDialogFragment.MESSAGE_NEW_EXPIRY_DATE: + Long expiry = (Long) message.getData(). + getSerializable(EditSubkeyExpiryDialogFragment.MESSAGE_DATA_EXPIRY_DATE); + mSaveKeyringParcel.getOrCreateSubkeyChange(keyId).mExpiry = expiry; break; } getLoaderManager().getLoader(LOADER_ID_SUBKEYS).forceLoad(); @@ -418,8 +418,8 @@ public class EditKeyFragment extends LoaderFragment implements DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() { public void run() { - ChangeExpiryDialogFragment dialogFragment = - ChangeExpiryDialogFragment.newInstance(messenger, new Date(), new Date()); + EditSubkeyExpiryDialogFragment dialogFragment = + EditSubkeyExpiryDialogFragment.newInstance(messenger, creationDate, expiryDate); dialogFragment.show(getActivity().getSupportFragmentManager(), "editSubkeyExpiryDialog"); } @@ -453,8 +453,21 @@ public class EditKeyFragment extends LoaderFragment implements } private void addSubkey() { - // default values - mSubkeysAddedAdapter.add(new SaveKeyringParcel.SubkeyAdd(Constants.choice.algorithm.rsa, 4096, KeyFlags.SIGN_DATA, null)); + boolean willBeMasterKey = mSubkeysAdapter.getCount() == 0 + && mSubkeysAddedAdapter.getCount() == 0; + + AddSubkeyDialogFragment addSubkeyDialogFragment = + AddSubkeyDialogFragment.newInstance(willBeMasterKey); + addSubkeyDialogFragment + .setOnAlgorithmSelectedListener( + new AddSubkeyDialogFragment.OnAlgorithmSelectedListener() { + @Override + public void onAlgorithmSelected(SaveKeyringParcel.SubkeyAdd newSubkey) { + mSubkeysAddedAdapter.add(newSubkey); + } + } + ); + addSubkeyDialogFragment.show(getActivity().getSupportFragmentManager(), "addSubkeyDialog"); } private void cachePassphraseForEdit() { @@ -479,9 +492,7 @@ public class EditKeyFragment extends LoaderFragment implements } private void save(String passphrase) { - Log.d(Constants.TAG, "mSaveKeyringParcel.mAddUserIds: " + mSaveKeyringParcel.mAddUserIds); - Log.d(Constants.TAG, "mSaveKeyringParcel.mNewPassphrase: " + mSaveKeyringParcel.mNewPassphrase); - Log.d(Constants.TAG, "mSaveKeyringParcel.mRevokeUserIds: " + mSaveKeyringParcel.mRevokeUserIds); + Log.d(Constants.TAG, "mSaveKeyringParcel:\n" + mSaveKeyringParcel.toString()); // Message is received after importing is done in KeychainIntentService KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler( diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java index cc69148c1..94f828b48 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java @@ -18,23 +18,46 @@ package org.sufficientlysecure.keychain.ui; +import android.app.ProgressDialog; import android.content.Intent; +import android.content.pm.LabeledIntent; +import android.content.pm.ResolveInfo; import android.net.Uri; +import android.os.Build; import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.os.Messenger; +import android.os.Parcelable; +import android.support.v4.app.Fragment; import android.support.v4.view.PagerTabStrip; import android.support.v4.view.ViewPager; -import android.widget.Toast; +import android.view.Menu; +import android.view.MenuItem; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.helper.FileHelper; +import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; +import org.sufficientlysecure.keychain.helper.Preferences; +import org.sufficientlysecure.keychain.pgp.KeyRing; +import org.sufficientlysecure.keychain.service.KeychainIntentService; +import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; +import org.sufficientlysecure.keychain.service.PassphraseCacheService; import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter; +import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment; +import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment; import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.Notify; -public class EncryptActivity extends DrawerActivity implements - EncryptSymmetricFragment.OnSymmetricKeySelection, - EncryptAsymmetricFragment.OnAsymmetricKeySelection, - EncryptActivityInterface { +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class EncryptActivity extends DrawerActivity implements EncryptActivityInterface { /* Intents */ public static final String ACTION_ENCRYPT = Constants.INTENT_PREFIX + "ENCRYPT"; @@ -51,7 +74,7 @@ public class EncryptActivity extends DrawerActivity implements // view ViewPager mViewPagerMode; - PagerTabStrip mPagerTabStripMode; + //PagerTabStrip mPagerTabStripMode; PagerTabStripAdapter mTabsAdapterMode; ViewPager mViewPagerContent; PagerTabStrip mPagerTabStripContent; @@ -72,37 +95,27 @@ public class EncryptActivity extends DrawerActivity implements // model used by message and file fragments private long mEncryptionKeyIds[] = null; + private String mEncryptionUserIds[] = null; private long mSigningKeyId = Constants.key.none; - private String mPassphrase; - private String mPassphraseAgain; + private String mPassphrase = ""; + private boolean mUseArmor; + private boolean mDeleteAfterEncrypt = false; + private boolean mShareAfterEncrypt = false; + private ArrayList mInputUris; + private ArrayList mOutputUris; + private String mMessage = ""; - @Override - public void onSigningKeySelected(long signingKeyId) { - mSigningKeyId = signingKeyId; - } - - @Override - public void onEncryptionKeysSelected(long[] encryptionKeyIds) { - mEncryptionKeyIds = encryptionKeyIds; - } - - @Override - public void onPassphraseUpdate(String passphrase) { - mPassphrase = passphrase; - } - - @Override - public void onPassphraseAgainUpdate(String passphrase) { - mPassphraseAgain = passphrase; - } - - @Override public boolean isModeSymmetric() { - if (PAGER_MODE_SYMMETRIC == mViewPagerMode.getCurrentItem()) { - return true; - } else { - return false; - } + return PAGER_MODE_SYMMETRIC == mViewPagerMode.getCurrentItem(); + } + + public boolean isContentMessage() { + return PAGER_CONTENT_MESSAGE == mViewPagerContent.getCurrentItem(); + } + + @Override + public boolean isUseArmor() { + return mUseArmor; } @Override @@ -116,19 +129,352 @@ public class EncryptActivity extends DrawerActivity implements } @Override - public String getPassphrase() { - return mPassphrase; + public String[] getEncryptionUsers() { + return mEncryptionUserIds; } @Override - public String getPassphraseAgain() { - return mPassphraseAgain; + public void setSignatureKey(long signatureKey) { + mSigningKeyId = signatureKey; + notifyUpdate(); } + @Override + public void setEncryptionKeys(long[] encryptionKeys) { + mEncryptionKeyIds = encryptionKeys; + notifyUpdate(); + } + + @Override + public void setEncryptionUsers(String[] encryptionUsers) { + mEncryptionUserIds = encryptionUsers; + notifyUpdate(); + } + + @Override + public void setPassphrase(String passphrase) { + mPassphrase = passphrase; + } + + @Override + public ArrayList getInputUris() { + if (mInputUris == null) mInputUris = new ArrayList(); + return mInputUris; + } + + @Override + public ArrayList getOutputUris() { + if (mOutputUris == null) mOutputUris = new ArrayList(); + return mOutputUris; + } + + @Override + public void setInputUris(ArrayList uris) { + mInputUris = uris; + notifyUpdate(); + } + + @Override + public void setOutputUris(ArrayList uris) { + mOutputUris = uris; + notifyUpdate(); + } + + @Override + public String getMessage() { + return mMessage; + } + + @Override + public void setMessage(String message) { + mMessage = message; + } + + @Override + public void notifyUpdate() { + for (Fragment fragment : getSupportFragmentManager().getFragments()) { + if (fragment instanceof EncryptActivityInterface.UpdateListener) { + ((UpdateListener) fragment).onNotifyUpdate(); + } + } + } + + @Override + public void startEncrypt(boolean share) { + mShareAfterEncrypt = share; + startEncrypt(); + } + + public void startEncrypt() { + if (!inputIsValid()) { + // Notify was created by inputIsValid. + return; + } + + // Send all information needed to service to edit key in other thread + Intent intent = new Intent(this, KeychainIntentService.class); + intent.setAction(KeychainIntentService.ACTION_ENCRYPT_SIGN); + intent.putExtra(KeychainIntentService.EXTRA_DATA, createEncryptBundle()); + + // Message is received after encrypting is done in KeychainIntentService + KeychainIntentServiceHandler serviceHandler = new KeychainIntentServiceHandler(this, + getString(R.string.progress_encrypting), ProgressDialog.STYLE_HORIZONTAL) { + public void handleMessage(Message message) { + // handle messages by standard KeychainIntentServiceHandler first + super.handleMessage(message); + + if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { + if (!isContentMessage()) { + Notify.showNotify(EncryptActivity.this, R.string.encrypt_sign_successful, Notify.Style.INFO); + + if (mDeleteAfterEncrypt) { + for (Uri inputUri : mInputUris) { + DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment.newInstance(inputUri); + deleteFileDialog.show(getSupportFragmentManager(), "deleteDialog"); + } + mInputUris.clear(); + notifyUpdate(); + } + } + + if (mShareAfterEncrypt) { + // Share encrypted message/file + startActivity(sendWithChooserExcludingEncrypt(message)); + } else if (isContentMessage()) { + // Copy to clipboard + copyToClipboard(message); + Notify.showNotify(EncryptActivity.this, + R.string.encrypt_sign_clipboard_successful, Notify.Style.INFO); + } + } + } + }; + // Create a new Messenger for the communication back + Messenger messenger = new Messenger(serviceHandler); + intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger); + + // show progress dialog + serviceHandler.showProgressDialog(this); + + // start service with intent + startService(intent); + } + + private Bundle createEncryptBundle() { + // fill values for this action + Bundle data = new Bundle(); + + if (isContentMessage()) { + data.putInt(KeychainIntentService.TARGET, KeychainIntentService.IO_BYTES); + data.putByteArray(KeychainIntentService.ENCRYPT_MESSAGE_BYTES, mMessage.getBytes()); + } else { + data.putInt(KeychainIntentService.SOURCE, KeychainIntentService.IO_URIS); + data.putParcelableArrayList(KeychainIntentService.ENCRYPT_INPUT_URIS, mInputUris); + + data.putInt(KeychainIntentService.TARGET, KeychainIntentService.IO_URIS); + data.putParcelableArrayList(KeychainIntentService.ENCRYPT_OUTPUT_URIS, mOutputUris); + } + + // Always use armor for messages + data.putBoolean(KeychainIntentService.ENCRYPT_USE_ASCII_ARMOR, mUseArmor || isContentMessage()); + + // TODO: Only default compression right now... + int compressionId = Preferences.getPreferences(this).getDefaultMessageCompression(); + data.putInt(KeychainIntentService.ENCRYPT_COMPRESSION_ID, compressionId); + + if (isModeSymmetric()) { + Log.d(Constants.TAG, "Symmetric encryption enabled!"); + String passphrase = mPassphrase; + if (passphrase.length() == 0) { + passphrase = null; + } + data.putString(KeychainIntentService.ENCRYPT_SYMMETRIC_PASSPHRASE, passphrase); + } else { + data.putLong(KeychainIntentService.ENCRYPT_SIGNATURE_KEY_ID, mSigningKeyId); + data.putLongArray(KeychainIntentService.ENCRYPT_ENCRYPTION_KEYS_IDS, mEncryptionKeyIds); + } + return data; + } + + private void copyToClipboard(Message message) { + ClipboardReflection.copyToClipboard(this, new String(message.getData().getByteArray(KeychainIntentService.RESULT_BYTES))); + } + + /** + * Create Intent Chooser but exclude OK's EncryptActivity. + *

+ * Put together from some stackoverflow posts... + * + * @param message + * @return + */ + private Intent sendWithChooserExcludingEncrypt(Message message) { + Intent prototype = createSendIntent(message); + + String title = isContentMessage() ? getString(R.string.title_share_message) + : getString(R.string.title_share_file); + + // fallback on Android 2.3, otherwise we would get weird results + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + return Intent.createChooser(prototype, title); + } + + // prevent recursion aka Inception :P + String[] blacklist = new String[]{Constants.PACKAGE_NAME + ".ui.EncryptActivity"}; + + List targetedShareIntents = new ArrayList(); + + List resInfoList = getPackageManager().queryIntentActivities(prototype, 0); + List resInfoListFiltered = new ArrayList(); + if (!resInfoList.isEmpty()) { + for (ResolveInfo resolveInfo : resInfoList) { + // do not add blacklisted ones + if (resolveInfo.activityInfo == null || Arrays.asList(blacklist).contains(resolveInfo.activityInfo.name)) + continue; + + resInfoListFiltered.add(resolveInfo); + } + + if (!resInfoListFiltered.isEmpty()) { + // sorting for nice readability + Collections.sort(resInfoListFiltered, new Comparator() { + @Override + public int compare(ResolveInfo first, ResolveInfo second) { + String firstName = first.loadLabel(getPackageManager()).toString(); + String secondName = second.loadLabel(getPackageManager()).toString(); + return firstName.compareToIgnoreCase(secondName); + } + }); + + // create the custom intent list + for (ResolveInfo resolveInfo : resInfoListFiltered) { + Intent targetedShareIntent = (Intent) prototype.clone(); + targetedShareIntent.setPackage(resolveInfo.activityInfo.packageName); + targetedShareIntent.setClassName(resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name); + + LabeledIntent lIntent = new LabeledIntent(targetedShareIntent, + resolveInfo.activityInfo.packageName, + resolveInfo.loadLabel(getPackageManager()), + resolveInfo.activityInfo.icon); + targetedShareIntents.add(lIntent); + } + + // Create chooser with only one Intent in it + Intent chooserIntent = Intent.createChooser(targetedShareIntents.remove(targetedShareIntents.size() - 1), title); + // append all other Intents + chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, targetedShareIntents.toArray(new Parcelable[]{})); + return chooserIntent; + } + + } + + // fallback to Android's default chooser + return Intent.createChooser(prototype, title); + } + + private Intent createSendIntent(Message message) { + Intent sendIntent; + if (isContentMessage()) { + sendIntent = new Intent(Intent.ACTION_SEND); + sendIntent.setType("text/plain"); + sendIntent.putExtra(Intent.EXTRA_TEXT, new String(message.getData().getByteArray(KeychainIntentService.RESULT_BYTES))); + } else { + // file + if (mOutputUris.size() == 1) { + sendIntent = new Intent(Intent.ACTION_SEND); + sendIntent.putExtra(Intent.EXTRA_STREAM, mOutputUris.get(0)); + } else { + sendIntent = new Intent(Intent.ACTION_SEND_MULTIPLE); + sendIntent.putExtra(Intent.EXTRA_STREAM, mOutputUris); + } + sendIntent.setType("application/pgp-encrypted"); + } + if (!isModeSymmetric() && mEncryptionUserIds != null) { + Set users = new HashSet(); + for (String user : mEncryptionUserIds) { + String[] userId = KeyRing.splitUserId(user); + if (userId[1] != null) { + users.add(userId[1]); + } + } + sendIntent.putExtra(Intent.EXTRA_EMAIL, users.toArray(new String[users.size()])); + } + return sendIntent; + } + + private boolean inputIsValid() { + if (isContentMessage()) { + if (mMessage == null) { + Notify.showNotify(this, R.string.error_message, Notify.Style.ERROR); + return false; + } + } else { + // file checks + + if (mInputUris.isEmpty()) { + Notify.showNotify(this, R.string.no_file_selected, Notify.Style.ERROR); + return false; + } else if (mInputUris.size() > 1 && !mShareAfterEncrypt) { + // This should be impossible... + return false; + } else if (mInputUris.size() != mOutputUris.size()) { + // This as well + return false; + } + } + + if (isModeSymmetric()) { + // symmetric encryption checks + + + if (mPassphrase == null) { + Notify.showNotify(this, R.string.passphrases_do_not_match, Notify.Style.ERROR); + return false; + } + if (mPassphrase.isEmpty()) { + Notify.showNotify(this, R.string.passphrase_must_not_be_empty, Notify.Style.ERROR); + return false; + } + + } else { + // asymmetric encryption checks + + boolean gotEncryptionKeys = (mEncryptionKeyIds != null + && mEncryptionKeyIds.length > 0); + + // Files must be encrypted, only text can be signed-only right now + if (!gotEncryptionKeys && !isContentMessage()) { + Notify.showNotify(this, R.string.select_encryption_key, Notify.Style.ERROR); + return false; + } + + if (!gotEncryptionKeys && mSigningKeyId == 0) { + Notify.showNotify(this, R.string.select_encryption_or_signature_key, Notify.Style.ERROR); + return false; + } + + if (mSigningKeyId != 0 && PassphraseCacheService.getCachedPassphrase(this, mSigningKeyId) == null) { + PassphraseDialogFragment.show(this, mSigningKeyId, + new Handler() { + @Override + public void handleMessage(Message message) { + if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) { + // restart + startEncrypt(); + } + } + } + ); + + return false; + } + } + return true; + } private void initView() { mViewPagerMode = (ViewPager) findViewById(R.id.encrypt_pager_mode); - mPagerTabStripMode = (PagerTabStrip) findViewById(R.id.encrypt_pager_tab_strip_mode); + //mPagerTabStripMode = (PagerTabStrip) findViewById(R.id.encrypt_pager_tab_strip_mode); mViewPagerContent = (ViewPager) findViewById(R.id.encrypt_pager_content); mPagerTabStripContent = (PagerTabStrip) findViewById(R.id.encrypt_pager_tab_strip_content); @@ -165,8 +511,43 @@ public class EncryptActivity extends DrawerActivity implements mTabsAdapterContent.addTab(EncryptMessageFragment.class, mMessageFragmentBundle, getString(R.string.label_message)); mTabsAdapterContent.addTab(EncryptFileFragment.class, - mFileFragmentBundle, getString(R.string.label_file)); + mFileFragmentBundle, getString(R.string.label_files)); mViewPagerContent.setCurrentItem(mSwitchToContent); + + mUseArmor = Preferences.getPreferences(this).getDefaultAsciiArmor(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.encrypt_activity, menu); + menu.findItem(R.id.check_use_armor).setChecked(mUseArmor); + return super.onCreateOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.isCheckable()) { + item.setChecked(!item.isChecked()); + } + switch (item.getItemId()) { + case R.id.check_use_symmetric: + mSwitchToMode = item.isChecked() ? PAGER_MODE_SYMMETRIC : PAGER_MODE_ASYMMETRIC; + + mViewPagerMode.setCurrentItem(mSwitchToMode); + notifyUpdate(); + break; + case R.id.check_use_armor: + mUseArmor = item.isChecked(); + notifyUpdate(); + break; + case R.id.check_delete_after_encrypt: + mDeleteAfterEncrypt = item.isChecked(); + notifyUpdate(); + break; + default: + return super.onOptionsItemSelected(item); + } + return true; } /** @@ -178,17 +559,21 @@ public class EncryptActivity extends DrawerActivity implements String action = intent.getAction(); Bundle extras = intent.getExtras(); String type = intent.getType(); - Uri uri = intent.getData(); + ArrayList uris = new ArrayList(); if (extras == null) { extras = new Bundle(); } + if (intent.getData() != null) { + uris.add(intent.getData()); + } + /* * Android's Action */ if (Intent.ACTION_SEND.equals(action) && type != null) { - // When sending to APG Encrypt via share menu + // When sending to OpenKeychain Encrypt via share menu if ("text/plain".equals(type)) { // Plain text String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT); @@ -201,14 +586,19 @@ public class EncryptActivity extends DrawerActivity implements } } else { // Files via content provider, override uri and action - uri = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM); + uris.clear(); + uris.add(intent.getParcelableExtra(Intent.EXTRA_STREAM)); action = ACTION_ENCRYPT; } } + if (Intent.ACTION_SEND_MULTIPLE.equals(action) && type != null) { + uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM); + action = ACTION_ENCRYPT; + } + if (extras.containsKey(EXTRA_ASCII_ARMOR)) { - boolean requestAsciiArmor = extras.getBoolean(EXTRA_ASCII_ARMOR, true); - mFileFragmentBundle.putBoolean(EncryptFileFragment.ARG_ASCII_ARMOR, requestAsciiArmor); + mUseArmor = extras.getBoolean(EXTRA_ASCII_ARMOR, true); } String textData = extras.getString(EXTRA_TEXT); @@ -230,25 +620,10 @@ public class EncryptActivity extends DrawerActivity implements // encrypt text based on given extra mMessageFragmentBundle.putString(EncryptMessageFragment.ARG_TEXT, textData); mSwitchToContent = PAGER_CONTENT_MESSAGE; - } else if (ACTION_ENCRYPT.equals(action) && uri != null) { + } else if (ACTION_ENCRYPT.equals(action) && uris != null && !uris.isEmpty()) { // encrypt file based on Uri - - // get file path from uri - String path = FileHelper.getPath(this, uri); - - if (path != null) { - mFileFragmentBundle.putString(EncryptFileFragment.ARG_FILENAME, path); - mSwitchToContent = PAGER_CONTENT_FILE; - } else { - Log.e(Constants.TAG, - "Direct binary data without actual file in filesystem is not supported " + - "by Intents. Please use the Remote Service API!" - ); - Toast.makeText(this, R.string.error_only_files_are_supported, - Toast.LENGTH_LONG).show(); - // end activity - finish(); - } + mFileFragmentBundle.putParcelableArrayList(EncryptFileFragment.ARG_URIS, uris); + mSwitchToContent = PAGER_CONTENT_FILE; } else if (ACTION_ENCRYPT.equals(action)) { Log.e(Constants.TAG, "Include the extra 'text' or an Uri with setData() in your Intent!"); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivityInterface.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivityInterface.java index 0786b3a16..54fe369a7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivityInterface.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivityInterface.java @@ -17,14 +17,41 @@ package org.sufficientlysecure.keychain.ui; +import android.net.Uri; + +import java.util.ArrayList; + public interface EncryptActivityInterface { - public boolean isModeSymmetric(); + public interface UpdateListener { + void onNotifyUpdate(); + } + + public boolean isUseArmor(); public long getSignatureKey(); public long[] getEncryptionKeys(); + public String[] getEncryptionUsers(); + public void setSignatureKey(long signatureKey); + public void setEncryptionKeys(long[] encryptionKeys); + public void setEncryptionUsers(String[] encryptionUsers); - public String getPassphrase(); - public String getPassphraseAgain(); + public void setPassphrase(String passphrase); + // ArrayList on purpose as only those are parcelable + public ArrayList getInputUris(); + public ArrayList getOutputUris(); + public void setInputUris(ArrayList uris); + public void setOutputUris(ArrayList uris); + + public String getMessage(); + public void setMessage(String message); + + /** + * Call this to notify the UI for changes done on the array lists or arrays, + * automatically called if setter is used + */ + public void notifyUpdate(); + + public void startEncrypt(boolean share); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java index eb807792b..3de617ca0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java @@ -18,16 +18,25 @@ package org.sufficientlysecure.keychain.ui; import android.app.Activity; -import android.content.Intent; +import android.content.Context; +import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.support.v4.app.Fragment; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; +import android.support.v4.widget.CursorAdapter; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.CheckBox; +import android.widget.AdapterView; +import android.widget.BaseAdapter; +import android.widget.Spinner; +import android.widget.SpinnerAdapter; import android.widget.TextView; -import android.widget.Button; + +import com.tokenautocomplete.TokenCompleteTextView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; @@ -37,61 +46,52 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.ui.widget.EncryptKeyCompletionView; import org.sufficientlysecure.keychain.util.Log; -import org.sufficientlysecure.keychain.util.Notify; -import java.util.Vector; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; -public class EncryptAsymmetricFragment extends Fragment { +public class EncryptAsymmetricFragment extends Fragment implements EncryptActivityInterface.UpdateListener { public static final String ARG_SIGNATURE_KEY_ID = "signature_key_id"; public static final String ARG_ENCRYPTION_KEY_IDS = "encryption_key_ids"; - public static final int REQUEST_CODE_PUBLIC_KEYS = 0x00007001; - public static final int REQUEST_CODE_SECRET_KEYS = 0x00007002; - ProviderHelper mProviderHelper; - OnAsymmetricKeySelection mKeySelectionListener; - // view - private Button mSelectKeysButton; - private CheckBox mSign; - private TextView mMainUserId; - private TextView mMainUserIdRest; + private Spinner mSign; + private EncryptKeyCompletionView mEncryptKeyView; + private SelectSignKeyCursorAdapter mSignAdapter = new SelectSignKeyCursorAdapter(); // model - private long mSecretKeyId = Constants.key.none; - private long mEncryptionKeyIds[] = null; + private EncryptActivityInterface mEncryptInterface; - // Container Activity must implement this interface - public interface OnAsymmetricKeySelection { - public void onSigningKeySelected(long signingKeyId); + @Override + public void onNotifyUpdate() { - public void onEncryptionKeysSelected(long[] encryptionKeyIds); } @Override public void onAttach(Activity activity) { super.onAttach(activity); try { - mKeySelectionListener = (OnAsymmetricKeySelection) activity; + mEncryptInterface = (EncryptActivityInterface) activity; } catch (ClassCastException e) { - throw new ClassCastException(activity.toString() + " must implement OnAsymmetricKeySelection"); + throw new ClassCastException(activity.toString() + " must implement EncryptActivityInterface"); } } private void setSignatureKeyId(long signatureKeyId) { - mSecretKeyId = signatureKeyId; - // update key selection in EncryptActivity - mKeySelectionListener.onSigningKeySelected(signatureKeyId); - updateView(); + mEncryptInterface.setSignatureKey(signatureKeyId); } private void setEncryptionKeyIds(long[] encryptionKeyIds) { - mEncryptionKeyIds = encryptionKeyIds; - // update key selection in EncryptActivity - mKeySelectionListener.onEncryptionKeysSelected(encryptionKeyIds); - updateView(); + mEncryptInterface.setEncryptionKeys(encryptionKeyIds); + } + + private void setEncryptionUserIds(String[] encryptionUserIds) { + mEncryptInterface.setEncryptionUsers(encryptionUserIds); } /** @@ -101,25 +101,21 @@ public class EncryptAsymmetricFragment extends Fragment { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.encrypt_asymmetric_fragment, container, false); - mSelectKeysButton = (Button) view.findViewById(R.id.btn_selectEncryptKeys); - mSign = (CheckBox) view.findViewById(R.id.sign); - mMainUserId = (TextView) view.findViewById(R.id.mainUserId); - mMainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest); - mSelectKeysButton.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - selectPublicKeys(); - } - }); - mSign.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - CheckBox checkBox = (CheckBox) v; - if (checkBox.isChecked()) { - selectSecretKey(); - } else { - setSignatureKeyId(Constants.key.none); - } + mSign = (Spinner) view.findViewById(R.id.sign); + mSign.setAdapter(mSignAdapter); + mSign.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + setSignatureKeyId(parent.getAdapter().getItemId(position)); + } + + @Override + public void onNothingSelected(AdapterView parent) { + setSignatureKeyId(Constants.key.none); } }); + mEncryptKeyView = (EncryptKeyCompletionView) view.findViewById(R.id.recipient_list); + mEncryptKeyView.setThreshold(1); // Start working from first character return view; } @@ -135,6 +131,65 @@ public class EncryptAsymmetricFragment extends Fragment { // preselect keys given by arguments (given by Intent to EncryptActivity) preselectKeys(signatureKeyId, encryptionKeyIds, mProviderHelper); + + getLoaderManager().initLoader(1, null, new LoaderManager.LoaderCallbacks() { + @Override + public Loader onCreateLoader(int id, Bundle args) { + // This is called when a new Loader needs to be created. This + // sample only has one Loader, so we don't care about the ID. + Uri baseUri = KeyRings.buildUnifiedKeyRingsUri(); + + // These are the rows that we will retrieve. + String[] projection = new String[]{ + KeyRings._ID, + KeyRings.MASTER_KEY_ID, + KeyRings.KEY_ID, + KeyRings.USER_ID, + KeyRings.EXPIRY, + KeyRings.IS_REVOKED, + // can certify info only related to master key + KeyRings.CAN_CERTIFY, + // has sign may be any subkey + KeyRings.HAS_SIGN, + KeyRings.HAS_ANY_SECRET, + KeyRings.HAS_SECRET + }; + + String where = KeyRings.HAS_ANY_SECRET + " = 1"; + + // Now create and return a CursorLoader that will take care of + // creating a Cursor for the data being displayed. + return new CursorLoader(getActivity(), baseUri, projection, where, null, null); + /*return new CursorLoader(getActivity(), KeyRings.buildUnifiedKeyRingsUri(), + new String[]{KeyRings.USER_ID, KeyRings.KEY_ID, KeyRings.MASTER_KEY_ID, KeyRings.HAS_ANY_SECRET}, SIGN_KEY_SELECTION, + null, null);*/ + } + + @Override + public void onLoadFinished(Loader loader, Cursor data) { + mSignAdapter.swapCursor(data); + } + + @Override + public void onLoaderReset(Loader loader) { + mSignAdapter.swapCursor(null); + } + }); + mEncryptKeyView.setTokenListener(new TokenCompleteTextView.TokenListener() { + @Override + public void onTokenAdded(Object token) { + if (token instanceof EncryptKeyCompletionView.EncryptionKey) { + updateEncryptionKeys(); + } + } + + @Override + public void onTokenRemoved(Object token) { + if (token instanceof EncryptKeyCompletionView.EncryptionKey) { + updateEncryptionKeys(); + } + } + }); } /** @@ -161,117 +216,125 @@ public class EncryptAsymmetricFragment extends Fragment { } if (preselectedEncryptionKeyIds != null) { - Vector goodIds = new Vector(); - for (int i = 0; i < preselectedEncryptionKeyIds.length; ++i) { + for (long preselectedId : preselectedEncryptionKeyIds) { try { - long id = providerHelper.getCachedPublicKeyRing( - KeyRings.buildUnifiedKeyRingsFindBySubkeyUri( - preselectedEncryptionKeyIds[i]) - ).getMasterKeyId(); - goodIds.add(id); + CachedPublicKeyRing ring = providerHelper.getCachedPublicKeyRing( + KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(preselectedId)); + mEncryptKeyView.addObject(mEncryptKeyView.new EncryptionKey(ring)); } catch (PgpGeneralException e) { Log.e(Constants.TAG, "key not found!", e); } } - if (goodIds.size() > 0) { - long[] keyIds = new long[goodIds.size()]; - for (int i = 0; i < goodIds.size(); ++i) { - keyIds[i] = goodIds.get(i); - } - setEncryptionKeyIds(keyIds); - } + updateEncryptionKeys(); } } - private void updateView() { - if (mEncryptionKeyIds == null || mEncryptionKeyIds.length == 0) { - mSelectKeysButton.setText(getString(R.string.select_keys_button_default)); - } else { - mSelectKeysButton.setText(getResources().getQuantityString( - R.plurals.select_keys_button, mEncryptionKeyIds.length, - mEncryptionKeyIds.length)); + private void updateEncryptionKeys() { + List objects = mEncryptKeyView.getObjects(); + List keyIds = new ArrayList(); + List userIds = new ArrayList(); + for (Object object : objects) { + if (object instanceof EncryptKeyCompletionView.EncryptionKey) { + keyIds.add(((EncryptKeyCompletionView.EncryptionKey) object).getKeyId()); + userIds.add(((EncryptKeyCompletionView.EncryptionKey) object).getUserId()); + } + } + long[] keyIdsArr = new long[keyIds.size()]; + Iterator iterator = keyIds.iterator(); + for (int i = 0; i < keyIds.size(); i++) { + keyIdsArr[i] = iterator.next(); + } + setEncryptionKeyIds(keyIdsArr); + setEncryptionUserIds(userIds.toArray(new String[userIds.size()])); + } + + private class SelectSignKeyCursorAdapter extends BaseAdapter implements SpinnerAdapter { + private CursorAdapter inner; + private int mIndexUserId; + private int mIndexKeyId; + private int mIndexMasterKeyId; + + public SelectSignKeyCursorAdapter() { + inner = new CursorAdapter(null, null, 0) { + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + return getActivity().getLayoutInflater().inflate(R.layout.encrypt_asymmetric_signkey, null); + } + + @Override + public void bindView(View view, Context context, Cursor cursor) { + String[] userId = KeyRing.splitUserId(cursor.getString(mIndexUserId)); + ((TextView) view.findViewById(android.R.id.title)).setText(userId[2] == null ? userId[0] : (userId[0] + " (" + userId[2] + ")")); + ((TextView) view.findViewById(android.R.id.text1)).setText(userId[1]); + ((TextView) view.findViewById(android.R.id.text2)).setText(PgpKeyHelper.convertKeyIdToHex(cursor.getLong(mIndexKeyId))); + } + + @Override + public long getItemId(int position) { + mCursor.moveToPosition(position); + return mCursor.getLong(mIndexMasterKeyId); + } + }; } - if (mSecretKeyId == Constants.key.none) { - mSign.setChecked(false); - mMainUserId.setText(""); - mMainUserIdRest.setText(""); - } else { - // See if we can get a user_id from a unified query - try { - String[] userIdSplit = mProviderHelper.getCachedPublicKeyRing( - KeyRings.buildUnifiedKeyRingUri(mSecretKeyId)).getSplitPrimaryUserIdWithFallback(); + public Cursor swapCursor(Cursor newCursor) { + if (newCursor == null) return inner.swapCursor(null); - if (userIdSplit[0] != null) { - mMainUserId.setText(userIdSplit[0]); + mIndexKeyId = newCursor.getColumnIndex(KeyRings.KEY_ID); + mIndexUserId = newCursor.getColumnIndex(KeyRings.USER_ID); + mIndexMasterKeyId = newCursor.getColumnIndex(KeyRings.MASTER_KEY_ID); + if (newCursor.moveToFirst()) { + do { + if (newCursor.getLong(mIndexMasterKeyId) == mEncryptInterface.getSignatureKey()) { + mSign.setSelection(newCursor.getPosition() + 1); + } + } while (newCursor.moveToNext()); + } + return inner.swapCursor(newCursor); + } + + @Override + public int getCount() { + return inner.getCount() + 1; + } + + @Override + public Object getItem(int position) { + if (position == 0) return null; + return inner.getItem(position - 1); + } + + @Override + public long getItemId(int position) { + if (position == 0) return Constants.key.none; + return inner.getItemId(position - 1); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + View v = getDropDownView(position, convertView, parent); + v.findViewById(android.R.id.text1).setVisibility(View.GONE); + return v; + } + + @Override + public View getDropDownView(int position, View convertView, ViewGroup parent) { + View v; + if (position == 0) { + if (convertView == null) { + v = inner.newView(null, null, parent); } else { - mMainUserId.setText(R.string.user_id_no_name); + v = convertView; } - if (userIdSplit[1] != null) { - mMainUserIdRest.setText(userIdSplit[1]); - } else { - mMainUserIdRest.setText(getString(R.string.label_key_id) + ": " - + PgpKeyHelper.convertKeyIdToHex(mSecretKeyId)); - } - } catch (PgpGeneralException e) { - Notify.showNotify(getActivity(), "Key not found! This is a bug!", Notify.Style.ERROR); - } - mSign.setChecked(true); - } - } - - private void selectPublicKeys() { - Intent intent = new Intent(getActivity(), SelectPublicKeyActivity.class); - Vector keyIds = new Vector(); - if (mEncryptionKeyIds != null && mEncryptionKeyIds.length > 0) { - for (int i = 0; i < mEncryptionKeyIds.length; ++i) { - keyIds.add(mEncryptionKeyIds[i]); - } - } - long[] initialKeyIds = null; - if (keyIds.size() > 0) { - initialKeyIds = new long[keyIds.size()]; - for (int i = 0; i < keyIds.size(); ++i) { - initialKeyIds[i] = keyIds.get(i); - } - } - intent.putExtra(SelectPublicKeyActivity.EXTRA_SELECTED_MASTER_KEY_IDS, initialKeyIds); - startActivityForResult(intent, REQUEST_CODE_PUBLIC_KEYS); - } - - private void selectSecretKey() { - Intent intent = new Intent(getActivity(), SelectSecretKeyActivity.class); - intent.putExtra(SelectSecretKeyActivity.EXTRA_FILTER_SIGN, true); - startActivityForResult(intent, REQUEST_CODE_SECRET_KEYS); - } - - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - switch (requestCode) { - case REQUEST_CODE_PUBLIC_KEYS: { - if (resultCode == Activity.RESULT_OK) { - Bundle bundle = data.getExtras(); - setEncryptionKeyIds(bundle - .getLongArray(SelectPublicKeyActivity.RESULT_EXTRA_MASTER_KEY_IDS)); - } - break; - } - - case REQUEST_CODE_SECRET_KEYS: { - if (resultCode == Activity.RESULT_OK) { - Uri uriMasterKey = data.getData(); - setSignatureKeyId(Long.valueOf(uriMasterKey.getLastPathSegment())); - } else { - setSignatureKeyId(Constants.key.none); - } - break; - } - - default: { - super.onActivityResult(requestCode, resultCode, data); - - break; + ((TextView) v.findViewById(android.R.id.title)).setText("None"); + v.findViewById(android.R.id.text1).setVisibility(View.GONE); + v.findViewById(android.R.id.text2).setVisibility(View.GONE); + } else { + v = inner.getView(position - 1, convertView, parent); + v.findViewById(android.R.id.text1).setVisibility(View.VISIBLE); + v.findViewById(android.R.id.text2).setVisibility(View.VISIBLE); } + return v; } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFileFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFileFragment.java index 345e38a0e..8a9e17020 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFileFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFileFragment.java @@ -17,66 +17,50 @@ package org.sufficientlysecure.keychain.ui; +import android.annotation.TargetApi; import android.app.Activity; -import android.app.ProgressDialog; import android.content.Intent; -import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.Point; import android.net.Uri; +import android.os.Build; import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.os.Messenger; -import android.provider.OpenableColumns; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.CheckBox; -import android.widget.EditText; -import android.widget.ImageButton; -import android.widget.Spinner; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.TextView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.helper.FileHelper; -import org.sufficientlysecure.keychain.helper.Preferences; -import org.sufficientlysecure.keychain.service.KeychainIntentService; -import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; -import org.sufficientlysecure.keychain.service.PassphraseCacheService; -import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment; -import org.sufficientlysecure.keychain.ui.dialog.FileDialogFragment; -import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment; -import org.sufficientlysecure.keychain.util.Choice; -import org.sufficientlysecure.keychain.util.Log; -import org.sufficientlysecure.keychain.util.Notify; +import org.sufficientlysecure.keychain.helper.OtherHelper; +import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider; import java.io.File; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; -public class EncryptFileFragment extends Fragment { - public static final String ARG_FILENAME = "filename"; - public static final String ARG_ASCII_ARMOR = "ascii_armor"; +public class EncryptFileFragment extends Fragment implements EncryptActivityInterface.UpdateListener { + public static final String ARG_URIS = "uris"; - private static final int REQUEST_CODE_FILE = 0x00007003; + private static final int REQUEST_CODE_INPUT = 0x00007003; + private static final int REQUEST_CODE_OUTPUT = 0x00007007; private EncryptActivityInterface mEncryptInterface; // view - private CheckBox mAsciiArmor = null; - private Spinner mFileCompression = null; - private EditText mFilename = null; - private CheckBox mDeleteAfter = null; - private CheckBox mShareAfter = null; - private ImageButton mBrowse = null; + private View mAddView; + private View mShareFile; private View mEncryptFile; - - private FileDialogFragment mFileDialog; - - // model - private String mInputFilename = null; - private Uri mInputUri = null; - private String mOutputFilename = null; - private Uri mOutputUri = null; + private ListView mSelectedFiles; + private SelectedFilesAdapter mAdapter = new SelectedFilesAdapter(); + private final Map thumbnailCache = new HashMap(); @Override public void onAttach(Activity activity) { @@ -99,52 +83,27 @@ public class EncryptFileFragment extends Fragment { mEncryptFile.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - encryptClicked(); + encryptClicked(false); } }); - - mFilename = (EditText) view.findViewById(R.id.filename); - mBrowse = (ImageButton) view.findViewById(R.id.btn_browse); - mBrowse.setOnClickListener(new View.OnClickListener() { + mShareFile = view.findViewById(R.id.action_encrypt_share); + mShareFile.setOnClickListener(new View.OnClickListener() { + @Override public void onClick(View v) { - if (Constants.KITKAT) { - FileHelper.openDocument(EncryptFileFragment.this, mInputUri, "*/*", REQUEST_CODE_FILE); - } else { - FileHelper.openFile(EncryptFileFragment.this, mFilename.getText().toString(), "*/*", - REQUEST_CODE_FILE); - } + encryptClicked(true); } }); - mFileCompression = (Spinner) view.findViewById(R.id.fileCompression); - Choice[] choices = new Choice[]{ - new Choice(Constants.choice.compression.none, getString(R.string.choice_none) + " (" - + getString(R.string.compression_fast) + ")"), - new Choice(Constants.choice.compression.zip, "ZIP (" - + getString(R.string.compression_fast) + ")"), - new Choice(Constants.choice.compression.zlib, "ZLIB (" - + getString(R.string.compression_fast) + ")"), - new Choice(Constants.choice.compression.bzip2, "BZIP2 (" - + getString(R.string.compression_very_slow) + ")"), - }; - ArrayAdapter adapter = new ArrayAdapter(getActivity(), - android.R.layout.simple_spinner_item, choices); - adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - mFileCompression.setAdapter(adapter); - - int defaultFileCompression = Preferences.getPreferences(getActivity()).getDefaultFileCompression(); - for (int i = 0; i < choices.length; ++i) { - if (choices[i].getId() == defaultFileCompression) { - mFileCompression.setSelection(i); - break; + mAddView = inflater.inflate(R.layout.file_list_entry_add, null); + mAddView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + addInputUri(); } - } - - mDeleteAfter = (CheckBox) view.findViewById(R.id.deleteAfterEncryption); - mShareAfter = (CheckBox) view.findViewById(R.id.shareAfterEncryption); - - mAsciiArmor = (CheckBox) view.findViewById(R.id.asciiArmor); - mAsciiArmor.setChecked(Preferences.getPreferences(getActivity()).getDefaultAsciiArmor()); + }); + mSelectedFiles = (ListView) view.findViewById(R.id.selected_files_list); + mSelectedFiles.addFooterView(mAddView); + mSelectedFiles.setAdapter(mAdapter); return view; } @@ -153,267 +112,128 @@ public class EncryptFileFragment extends Fragment { public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - String filename = getArguments().getString(ARG_FILENAME); - if (filename != null) { - mFilename.setText(filename); - } - boolean asciiArmor = getArguments().getBoolean(ARG_ASCII_ARMOR); - if (asciiArmor) { - mAsciiArmor.setChecked(asciiArmor); + addInputUris(getArguments().getParcelableArrayList(ARG_URIS)); + } + + private void addInputUri() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + FileHelper.openDocument(EncryptFileFragment.this, "*/*", true, REQUEST_CODE_INPUT); + } else { + FileHelper.openFile(EncryptFileFragment.this, mEncryptInterface.getInputUris().isEmpty() ? + null : mEncryptInterface.getInputUris().get(mEncryptInterface.getInputUris().size() - 1), + "*/*", REQUEST_CODE_INPUT); } } - /** - * Guess output filename based on input path - * - * @param path - * @return Suggestion for output filename - */ - private String guessOutputFilename(String path) { - // output in the same directory but with additional ending - File file = new File(path); - String ending = (mAsciiArmor.isChecked() ? ".asc" : ".gpg"); - String outputFilename = file.getParent() + File.separator + file.getName() + ending; - - return outputFilename; - } - - private void showOutputFileDialog() { - // Message is received after file is selected - Handler returnHandler = new Handler() { - @Override - public void handleMessage(Message message) { - if (message.what == FileDialogFragment.MESSAGE_OKAY) { - Bundle data = message.getData(); - if (data.containsKey(FileDialogFragment.MESSAGE_DATA_URI)) { - mOutputUri = data.getParcelable(FileDialogFragment.MESSAGE_DATA_URI); - } else { - mOutputFilename = data.getString(FileDialogFragment.MESSAGE_DATA_FILENAME); - } - encryptStart(); - } + private void addInputUris(List uris) { + if (uris != null) { + for (Uri uri : uris) { + addInputUri(uri); } - }; - - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(returnHandler); - - mFileDialog = FileDialogFragment.newInstance(messenger, - getString(R.string.title_encrypt_to_file), - getString(R.string.specify_file_to_encrypt_to), mOutputFilename, null); - - mFileDialog.show(getActivity().getSupportFragmentManager(), "fileDialog"); + } } - private void encryptClicked() { - String currentFilename = mFilename.getText().toString(); - if (mInputFilename == null || !mInputFilename.equals(currentFilename)) { - mInputUri = null; - mInputFilename = mFilename.getText().toString(); - } - - if (mInputUri == null) { - mOutputFilename = guessOutputFilename(mInputFilename); - } - - if (mInputFilename.equals("")) { - Notify.showNotify(getActivity(), R.string.no_file_selected, Notify.Style.ERROR); + private void addInputUri(Uri inputUri) { + if (inputUri == null) { return; } - if (mInputUri == null && !mInputFilename.startsWith("content")) { - File file = new File(mInputFilename); - if (!file.exists() || !file.isFile()) { - Notify.showNotify( - getActivity(), - getString(R.string.error_message, - getString(R.string.error_file_not_found)), Notify.Style.ERROR - ); - return; - } - } + mEncryptInterface.getInputUris().add(inputUri); + mEncryptInterface.notifyUpdate(); + mSelectedFiles.requestFocus(); - if (mEncryptInterface.isModeSymmetric()) { - // symmetric encryption + /** + * We hide the encrypt to file button if multiple files are selected. + * + * With Android L it will be possible to select a target directory for multiple files, so we might want to + * change this later + */ - boolean gotPassphrase = (mEncryptInterface.getPassphrase() != null - && mEncryptInterface.getPassphrase().length() != 0); - if (!gotPassphrase) { - Notify.showNotify(getActivity(), R.string.passphrase_must_not_be_empty, Notify.Style.ERROR) - ; - return; - } - - if (!mEncryptInterface.getPassphrase().equals(mEncryptInterface.getPassphraseAgain())) { - Notify.showNotify(getActivity(), R.string.passphrases_do_not_match, Notify.Style.ERROR); - return; - } + if (mEncryptInterface.getInputUris().size() > 1) { + mEncryptFile.setVisibility(View.GONE); } else { - // asymmetric encryption - - boolean gotEncryptionKeys = (mEncryptInterface.getEncryptionKeys() != null - && mEncryptInterface.getEncryptionKeys().length > 0); - - if (!gotEncryptionKeys) { - Notify.showNotify(getActivity(), R.string.select_encryption_key, Notify.Style.ERROR); - return; - } - - if (!gotEncryptionKeys && mEncryptInterface.getSignatureKey() == 0) { - Notify.showNotify(getActivity(), R.string.select_encryption_or_signature_key, - Notify.Style.ERROR); - return; - } - - if (mEncryptInterface.getSignatureKey() != 0 && - PassphraseCacheService.getCachedPassphrase(getActivity(), - mEncryptInterface.getSignatureKey()) == null) { - PassphraseDialogFragment.show(getActivity(), mEncryptInterface.getSignatureKey(), - new Handler() { - @Override - public void handleMessage(Message message) { - if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) { - showOutputFileDialog(); - } - } - }); - - return; - } + mEncryptFile.setVisibility(View.VISIBLE); } - - showOutputFileDialog(); } - private void encryptStart() { - // Send all information needed to service to edit key in other thread - Intent intent = new Intent(getActivity(), KeychainIntentService.class); + private void delInputUri(int position) { + mEncryptInterface.getInputUris().remove(position); + mEncryptInterface.notifyUpdate(); + mSelectedFiles.requestFocus(); - intent.setAction(KeychainIntentService.ACTION_ENCRYPT_SIGN); - - // fill values for this action - Bundle data = new Bundle(); - - Log.d(Constants.TAG, "mInputFilename=" + mInputFilename + ", mOutputFilename=" - + mOutputFilename + ",mInputUri=" + mInputUri + ", mOutputUri=" - + mOutputUri); - - if (mInputUri != null) { - data.putInt(KeychainIntentService.SOURCE, KeychainIntentService.IO_URI); - data.putParcelable(KeychainIntentService.ENCRYPT_INPUT_URI, mInputUri); + if (mEncryptInterface.getInputUris().size() > 1) { + mEncryptFile.setVisibility(View.GONE); } else { - data.putInt(KeychainIntentService.SOURCE, KeychainIntentService.IO_FILE); - data.putString(KeychainIntentService.ENCRYPT_INPUT_FILE, mInputFilename); + mEncryptFile.setVisibility(View.VISIBLE); } + } - if (mOutputUri != null) { - data.putInt(KeychainIntentService.TARGET, KeychainIntentService.IO_URI); - data.putParcelable(KeychainIntentService.ENCRYPT_OUTPUT_URI, mOutputUri); + private void showOutputFileDialog() { + if (mEncryptInterface.getInputUris().size() > 1 || mEncryptInterface.getInputUris().isEmpty()) { + throw new IllegalStateException(); + } + Uri inputUri = mEncryptInterface.getInputUris().get(0); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { + File file = new File(inputUri.getPath()); + File parentDir = file.exists() ? file.getParentFile() : Constants.Path.APP_DIR; + String targetName = FileHelper.getFilename(getActivity(), inputUri) + + (mEncryptInterface.isUseArmor() ? ".asc" : ".gpg"); + File targetFile = new File(parentDir, targetName); + FileHelper.saveFile(this, getString(R.string.title_encrypt_to_file), + getString(R.string.specify_file_to_encrypt_to), targetFile, REQUEST_CODE_OUTPUT); } else { - data.putInt(KeychainIntentService.TARGET, KeychainIntentService.IO_FILE); - data.putString(KeychainIntentService.ENCRYPT_OUTPUT_FILE, mOutputFilename); + FileHelper.saveDocument(this, "*/*", FileHelper.getFilename(getActivity(), inputUri) + + (mEncryptInterface.isUseArmor() ? ".asc" : ".gpg"), REQUEST_CODE_OUTPUT); } + } - if (mEncryptInterface.isModeSymmetric()) { - Log.d(Constants.TAG, "Symmetric encryption enabled!"); - String passphrase = mEncryptInterface.getPassphrase(); - if (passphrase.length() == 0) { - passphrase = null; + private void encryptClicked(boolean share) { + if (share) { + mEncryptInterface.getOutputUris().clear(); + for (Uri uri : mEncryptInterface.getInputUris()) { + String targetName = FileHelper.getFilename(getActivity(), uri) + + (mEncryptInterface.isUseArmor() ? ".asc" : ".gpg"); + mEncryptInterface.getOutputUris().add(TemporaryStorageProvider.createFile(getActivity(), targetName)); } - data.putString(KeychainIntentService.ENCRYPT_SYMMETRIC_PASSPHRASE, passphrase); - } else { - data.putLong(KeychainIntentService.ENCRYPT_SIGNATURE_KEY_ID, - mEncryptInterface.getSignatureKey()); - data.putLongArray(KeychainIntentService.ENCRYPT_ENCRYPTION_KEYS_IDS, - mEncryptInterface.getEncryptionKeys()); + mEncryptInterface.startEncrypt(true); + } else if (mEncryptInterface.getInputUris().size() == 1) { + showOutputFileDialog(); } + } - boolean useAsciiArmor = mAsciiArmor.isChecked(); - data.putBoolean(KeychainIntentService.ENCRYPT_USE_ASCII_ARMOR, useAsciiArmor); - - int compressionId = ((Choice) mFileCompression.getSelectedItem()).getId(); - data.putInt(KeychainIntentService.ENCRYPT_COMPRESSION_ID, compressionId); - - intent.putExtra(KeychainIntentService.EXTRA_DATA, data); - - // Message is received after encrypting is done in KeychainIntentService - KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(getActivity(), - getString(R.string.progress_encrypting), ProgressDialog.STYLE_HORIZONTAL) { - public void handleMessage(Message message) { - // handle messages by standard KeychainIntentServiceHandler first - super.handleMessage(message); - - if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { - Notify.showNotify(getActivity(), R.string.encrypt_sign_successful, - Notify.Style.INFO); - - if (mDeleteAfter.isChecked()) { - // Create and show dialog to delete original file - DeleteFileDialogFragment deleteFileDialog; - if (mInputUri != null) { - deleteFileDialog = DeleteFileDialogFragment.newInstance(mInputUri); - } else { - deleteFileDialog = DeleteFileDialogFragment - .newInstance(mInputFilename); - } - deleteFileDialog.show(getActivity().getSupportFragmentManager(), "deleteDialog"); - } - - if (mShareAfter.isChecked()) { - // Share encrypted file - Intent sendFileIntent = new Intent(Intent.ACTION_SEND); - sendFileIntent.setType("*/*"); - if (mOutputUri != null) { - sendFileIntent.putExtra(Intent.EXTRA_STREAM, mOutputUri); - } else { - sendFileIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse(mOutputFilename)); - } - startActivity(Intent.createChooser(sendFileIntent, - getString(R.string.title_share_file))); - } - } + @TargetApi(Build.VERSION_CODES.KITKAT) + public boolean handleClipData(Intent data) { + if (data.getClipData() != null && data.getClipData().getItemCount() > 0) { + for (int i = 0; i < data.getClipData().getItemCount(); i++) { + Uri uri = data.getClipData().getItemAt(i).getUri(); + if (uri != null) addInputUri(uri); } - }; - - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(saveHandler); - intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger); - - // show progress dialog - saveHandler.showProgressDialog(getActivity()); - - // start service with intent - getActivity().startService(intent); + return true; + } + return false; } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode) { - case REQUEST_CODE_FILE: { + case REQUEST_CODE_INPUT: { if (resultCode == Activity.RESULT_OK && data != null) { - if (Constants.KITKAT) { - mInputUri = data.getData(); - Cursor cursor = getActivity().getContentResolver().query(mInputUri, new String[]{OpenableColumns.DISPLAY_NAME}, null, null, null); - if (cursor != null) { - if (cursor.moveToNext()) { - mInputFilename = cursor.getString(0); - mFilename.setText(mInputFilename); - } - cursor.close(); - } - } else { - try { - String path = FileHelper.getPath(getActivity(), data.getData()); - Log.d(Constants.TAG, "path=" + path); - - mFilename.setText(path); - } catch (NullPointerException e) { - Log.e(Constants.TAG, "Nullpointer while retrieving path!"); - } + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT || !handleClipData(data)) { + addInputUri(data.getData()); } } return; } + case REQUEST_CODE_OUTPUT: { + // This happens after output file was selected, so start our operation + if (resultCode == Activity.RESULT_OK && data != null) { + mEncryptInterface.getOutputUris().clear(); + mEncryptInterface.getOutputUris().add(data.getData()); + mEncryptInterface.notifyUpdate(); + mEncryptInterface.startEncrypt(false); + } + return; + } default: { super.onActivityResult(requestCode, resultCode, data); @@ -422,4 +242,68 @@ public class EncryptFileFragment extends Fragment { } } } + + @Override + public void onNotifyUpdate() { + // Clear cache if needed + for (Uri uri : new HashSet(thumbnailCache.keySet())) { + if (!mEncryptInterface.getInputUris().contains(uri)) { + thumbnailCache.remove(uri); + } + } + + mAdapter.notifyDataSetChanged(); + } + + private class SelectedFilesAdapter extends BaseAdapter { + @Override + public int getCount() { + return mEncryptInterface.getInputUris().size(); + } + + @Override + public Object getItem(int position) { + return mEncryptInterface.getInputUris().get(position); + } + + @Override + public long getItemId(int position) { + return getItem(position).hashCode(); + } + + @Override + public View getView(final int position, View convertView, ViewGroup parent) { + Uri inputUri = mEncryptInterface.getInputUris().get(position); + View view; + if (convertView == null) { + view = getActivity().getLayoutInflater().inflate(R.layout.file_list_entry, null); + } else { + view = convertView; + } + ((TextView) view.findViewById(R.id.filename)).setText(FileHelper.getFilename(getActivity(), inputUri)); + long size = FileHelper.getFileSize(getActivity(), inputUri); + if (size == -1) { + ((TextView) view.findViewById(R.id.filesize)).setText(""); + } else { + ((TextView) view.findViewById(R.id.filesize)).setText(FileHelper.readableFileSize(size)); + } + view.findViewById(R.id.action_remove_file_from_list).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + delInputUri(position); + } + }); + int px = OtherHelper.dpToPx(getActivity(), 48); + if (!thumbnailCache.containsKey(inputUri)) { + thumbnailCache.put(inputUri, FileHelper.getThumbnail(getActivity(), inputUri, new Point(px, px))); + } + Bitmap bitmap = thumbnailCache.get(inputUri); + if (bitmap != null) { + ((ImageView) view.findViewById(R.id.thumbnail)).setImageBitmap(bitmap); + } else { + ((ImageView) view.findViewById(R.id.thumbnail)).setImageResource(R.drawable.ic_doc_generic_am); + } + return view; + } + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptMessageFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptMessageFragment.java index e1760b4ed..6d753088b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptMessageFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptMessageFragment.java @@ -18,28 +18,16 @@ package org.sufficientlysecure.keychain.ui; import android.app.Activity; -import android.app.ProgressDialog; -import android.content.Intent; import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.os.Messenger; import android.support.v4.app.Fragment; +import android.text.Editable; +import android.text.TextWatcher; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; -import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; -import org.sufficientlysecure.keychain.helper.Preferences; -import org.sufficientlysecure.keychain.service.KeychainIntentService; -import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; -import org.sufficientlysecure.keychain.service.PassphraseCacheService; -import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment; -import org.sufficientlysecure.keychain.util.Log; -import org.sufficientlysecure.keychain.util.Notify; public class EncryptMessageFragment extends Fragment { public static final String ARG_TEXT = "text"; @@ -69,18 +57,34 @@ public class EncryptMessageFragment extends Fragment { View view = inflater.inflate(R.layout.encrypt_message_fragment, container, false); mMessage = (TextView) view.findViewById(R.id.message); + mMessage.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + + } + + @Override + public void afterTextChanged(Editable s) { + mEncryptInterface.setMessage(s.toString()); + } + }); mEncryptClipboard = view.findViewById(R.id.action_encrypt_clipboard); mEncryptShare = view.findViewById(R.id.action_encrypt_share); mEncryptClipboard.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - encryptClicked(true); + mEncryptInterface.startEncrypt(false); } }); mEncryptShare.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - encryptClicked(false); + mEncryptInterface.startEncrypt(true); } }); @@ -92,7 +96,7 @@ public class EncryptMessageFragment extends Fragment { public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - String text = getArguments().getString(ARG_TEXT); + String text = mEncryptInterface.getMessage(); if (text != null) { mMessage.setText(text); } @@ -117,138 +121,4 @@ public class EncryptMessageFragment extends Fragment { return message; } - - private void encryptClicked(final boolean toClipboard) { - if (mEncryptInterface.isModeSymmetric()) { - // symmetric encryption - - boolean gotPassphrase = (mEncryptInterface.getPassphrase() != null - && mEncryptInterface.getPassphrase().length() != 0); - if (!gotPassphrase) { - Notify.showNotify(getActivity(), R.string.passphrase_must_not_be_empty, Notify.Style.ERROR); - return; - } - - if (!mEncryptInterface.getPassphrase().equals(mEncryptInterface.getPassphraseAgain())) { - Notify.showNotify(getActivity(), R.string.passphrases_do_not_match, Notify.Style.ERROR); - return; - } - - } else { - // asymmetric encryption - - boolean gotEncryptionKeys = (mEncryptInterface.getEncryptionKeys() != null - && mEncryptInterface.getEncryptionKeys().length > 0); - - if (!gotEncryptionKeys && mEncryptInterface.getSignatureKey() == 0) { - Notify.showNotify(getActivity(), R.string.select_encryption_or_signature_key, - Notify.Style.ERROR); - return; - } - - if (mEncryptInterface.getSignatureKey() != 0 && - PassphraseCacheService.getCachedPassphrase(getActivity(), - mEncryptInterface.getSignatureKey()) == null) { - PassphraseDialogFragment.show(getActivity(), mEncryptInterface.getSignatureKey(), - new Handler() { - @Override - public void handleMessage(Message message) { - if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) { - encryptStart(toClipboard); - } - } - }); - - return; - } - } - - encryptStart(toClipboard); - } - - private void encryptStart(final boolean toClipboard) { - // Send all information needed to service to edit key in other thread - Intent intent = new Intent(getActivity(), KeychainIntentService.class); - - intent.setAction(KeychainIntentService.ACTION_ENCRYPT_SIGN); - - // fill values for this action - Bundle data = new Bundle(); - - data.putInt(KeychainIntentService.TARGET, KeychainIntentService.IO_BYTES); - - String message = mMessage.getText().toString(); - - if (mEncryptInterface.isModeSymmetric()) { - Log.d(Constants.TAG, "Symmetric encryption enabled!"); - String passphrase = mEncryptInterface.getPassphrase(); - if (passphrase.length() == 0) { - passphrase = null; - } - data.putString(KeychainIntentService.ENCRYPT_SYMMETRIC_PASSPHRASE, passphrase); - } else { - data.putLong(KeychainIntentService.ENCRYPT_SIGNATURE_KEY_ID, - mEncryptInterface.getSignatureKey()); - data.putLongArray(KeychainIntentService.ENCRYPT_ENCRYPTION_KEYS_IDS, - mEncryptInterface.getEncryptionKeys()); - - boolean signOnly = (mEncryptInterface.getEncryptionKeys() == null - || mEncryptInterface.getEncryptionKeys().length == 0); - if (signOnly) { - message = fixBadCharactersForGmail(message); - } - } - - data.putByteArray(KeychainIntentService.ENCRYPT_MESSAGE_BYTES, message.getBytes()); - - data.putBoolean(KeychainIntentService.ENCRYPT_USE_ASCII_ARMOR, true); - - int compressionId = Preferences.getPreferences(getActivity()).getDefaultMessageCompression(); - data.putInt(KeychainIntentService.ENCRYPT_COMPRESSION_ID, compressionId); - - intent.putExtra(KeychainIntentService.EXTRA_DATA, data); - - // Message is received after encrypting is done in KeychainIntentService - KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(getActivity(), - getString(R.string.progress_encrypting), ProgressDialog.STYLE_HORIZONTAL) { - public void handleMessage(Message message) { - // handle messages by standard KeychainIntentServiceHandler first - super.handleMessage(message); - - if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { - // get returned data bundle - Bundle data = message.getData(); - - String output = new String(data.getByteArray(KeychainIntentService.RESULT_BYTES)); - Log.d(Constants.TAG, "output: " + output); - - if (toClipboard) { - ClipboardReflection.copyToClipboard(getActivity(), output); - Notify.showNotify(getActivity(), - R.string.encrypt_sign_clipboard_successful, Notify.Style.INFO); - } else { - Intent sendIntent = new Intent(Intent.ACTION_SEND); - - // Type is set to text/plain so that encrypted messages can - // be sent with Whatsapp, Hangouts, SMS etc... - sendIntent.setType("text/plain"); - - sendIntent.putExtra(Intent.EXTRA_TEXT, output); - startActivity(Intent.createChooser(sendIntent, - getString(R.string.title_share_with))); - } - } - } - }; - - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(saveHandler); - intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger); - - // show progress dialog - saveHandler.showProgressDialog(getActivity()); - - // start service with intent - getActivity().startService(intent); - } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptSymmetricFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptSymmetricFragment.java index 8efa07953..86731b162 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptSymmetricFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptSymmetricFragment.java @@ -29,27 +29,20 @@ import android.widget.EditText; import org.sufficientlysecure.keychain.R; -public class EncryptSymmetricFragment extends Fragment { +public class EncryptSymmetricFragment extends Fragment implements EncryptActivityInterface.UpdateListener { - OnSymmetricKeySelection mPassphraseUpdateListener; + EncryptActivityInterface mEncryptInterface; private EditText mPassphrase; private EditText mPassphraseAgain; - // Container Activity must implement this interface - public interface OnSymmetricKeySelection { - public void onPassphraseUpdate(String passphrase); - - public void onPassphraseAgainUpdate(String passphrase); - } - @Override public void onAttach(Activity activity) { super.onAttach(activity); try { - mPassphraseUpdateListener = (OnSymmetricKeySelection) activity; + mEncryptInterface = (EncryptActivityInterface) activity; } catch (ClassCastException e) { - throw new ClassCastException(activity.toString() + " must implement OnSymmetricKeySelection"); + throw new ClassCastException(activity.toString() + " must implement EncryptActivityInterface"); } } @@ -62,7 +55,7 @@ public class EncryptSymmetricFragment extends Fragment { mPassphrase = (EditText) view.findViewById(R.id.passphrase); mPassphraseAgain = (EditText) view.findViewById(R.id.passphraseAgain); - mPassphrase.addTextChangedListener(new TextWatcher() { + TextWatcher textWatcher = new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @@ -74,25 +67,21 @@ public class EncryptSymmetricFragment extends Fragment { @Override public void afterTextChanged(Editable s) { // update passphrase in EncryptActivity - mPassphraseUpdateListener.onPassphraseUpdate(s.toString()); + if (mPassphrase.getText().toString().equals(mPassphraseAgain.getText().toString())) { + mEncryptInterface.setPassphrase(s.toString()); + } else { + mEncryptInterface.setPassphrase(null); + } } - }); - mPassphraseAgain.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - } - - @Override - public void afterTextChanged(Editable s) { - // update passphrase in EncryptActivity - mPassphraseUpdateListener.onPassphraseAgainUpdate(s.toString()); - } - }); + }; + mPassphrase.addTextChangedListener(textWatcher); + mPassphraseAgain.addTextChangedListener(textWatcher); return view; } + + @Override + public void onNotifyUpdate() { + + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java index 4a606a1b3..255290de3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java @@ -40,7 +40,6 @@ import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.helper.OtherHelper; import org.sufficientlysecure.keychain.helper.Preferences; -import org.sufficientlysecure.keychain.util.FileImportCache; import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; @@ -49,6 +48,7 @@ import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; import org.sufficientlysecure.keychain.service.OperationResults.ImportKeyResult; import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter; import org.sufficientlysecure.keychain.ui.widget.SlidingTabLayout; +import org.sufficientlysecure.keychain.util.FileImportCache; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Notify; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java index ce885c419..cb53647f6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java @@ -66,7 +66,7 @@ public class ImportKeysFileFragment extends Fragment { // open .asc or .gpg files // setting it to text/plain prevents Cyanogenmod's file manager from selecting asc // or gpg types! - FileHelper.openFile(ImportKeysFileFragment.this, Constants.Path.APP_DIR + "/", + FileHelper.openFile(ImportKeysFileFragment.this, Uri.fromFile(Constants.Path.APP_DIR), "*/*", REQUEST_CODE_FILE); } }); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java index 50ff5c753..7a6e78a7d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java @@ -94,22 +94,22 @@ public class KeyListActivity extends DrawerActivity { case R.id.menu_key_list_debug_read: try { - KeychainDatabase.debugRead(this); - Notify.showNotify(this, "Restored Notify.Style backup", Notify.Style.INFO); + KeychainDatabase.debugBackup(this, true); + Notify.showNotify(this, "Restored debug_backup.db", Notify.Style.INFO); getContentResolver().notifyChange(KeychainContract.KeyRings.CONTENT_URI, null); } catch (IOException e) { Log.e(Constants.TAG, "IO Error", e); - Notify.showNotify(this, "IO Notify.Style: " + e.getMessage(), Notify.Style.ERROR); + Notify.showNotify(this, "IO Error " + e.getMessage(), Notify.Style.ERROR); } return true; case R.id.menu_key_list_debug_write: try { - KeychainDatabase.debugWrite(this); - Notify.showNotify(this, "Backup Notify.Style", Notify.Style.INFO); + KeychainDatabase.debugBackup(this, false); + Notify.showNotify(this, "Backup to debug_backup.db completed", Notify.Style.INFO); } catch(IOException e) { Log.e(Constants.TAG, "IO Error", e); - Notify.showNotify(this, "IO Notify.Style: " + e.getMessage(), Notify.Style.ERROR); + Notify.showNotify(this, "IO Error: " + e.getMessage(), Notify.Style.ERROR); } return true; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java index aa17aea3d..3c97b1128 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java @@ -47,10 +47,10 @@ import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.AbsListView.MultiChoiceModeListener; import android.widget.AdapterView; +import android.widget.Button; import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; -import android.widget.Button; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; @@ -176,8 +176,8 @@ public class KeyListFragment extends LoaderFragment case R.id.menu_key_list_multi_export: { ids = mAdapter.getCurrentSelectedMasterKeyIds(); ExportHelper mExportHelper = new ExportHelper((ActionBarActivity) getActivity()); - mExportHelper.showExportKeysDialog( - ids, Constants.Path.APP_DIR_FILE, mAdapter.isAnySecretSelected()); + mExportHelper.showExportKeysDialog(ids, Constants.Path.APP_DIR_FILE, + mAdapter.isAnySecretSelected()); break; } case R.id.menu_key_list_multi_select_all: { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java index 43de6774b..0e948bf7f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java @@ -41,9 +41,7 @@ import org.sufficientlysecure.keychain.service.OperationResultParcel.LogEntryPar import org.sufficientlysecure.keychain.service.OperationResultParcel.LogLevel; import org.sufficientlysecure.keychain.util.Log; -import java.util.ArrayList; import java.util.HashMap; -import java.util.List; public class LogDisplayFragment extends ListFragment implements OnTouchListener { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesActivity.java index 283b79b13..a6561dfad 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesActivity.java @@ -27,6 +27,7 @@ import android.preference.PreferenceActivity; import android.preference.PreferenceFragment; import android.preference.PreferenceScreen; +import org.spongycastle.bcpg.CompressionAlgorithmTags; import org.spongycastle.bcpg.HashAlgorithmTags; import org.spongycastle.openpgp.PGPEncryptedData; import org.sufficientlysecure.keychain.Constants; @@ -88,10 +89,10 @@ public class PreferencesActivity extends PreferenceActivity { (IntegerListPreference) findPreference(Constants.Pref.DEFAULT_ENCRYPTION_ALGORITHM)); int[] valueIds = new int[]{ - Constants.choice.compression.none, - Constants.choice.compression.zip, - Constants.choice.compression.zlib, - Constants.choice.compression.bzip2, + CompressionAlgorithmTags.UNCOMPRESSED, + CompressionAlgorithmTags.ZIP, + CompressionAlgorithmTags.ZLIB, + CompressionAlgorithmTags.BZIP2, }; String[] entries = new String[]{ getString(R.string.choice_none) + " (" + getString(R.string.compression_fast) + ")", @@ -229,10 +230,10 @@ public class PreferencesActivity extends PreferenceActivity { (IntegerListPreference) findPreference(Constants.Pref.DEFAULT_ENCRYPTION_ALGORITHM)); int[] valueIds = new int[]{ - Constants.choice.compression.none, - Constants.choice.compression.zip, - Constants.choice.compression.zlib, - Constants.choice.compression.bzip2, + CompressionAlgorithmTags.UNCOMPRESSED, + CompressionAlgorithmTags.ZIP, + CompressionAlgorithmTags.ZLIB, + CompressionAlgorithmTags.BZIP2, }; String[] entries = new String[]{ diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewCertActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewCertActivity.java index 5201b5df8..341e11d1d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewCertActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewCertActivity.java @@ -34,8 +34,8 @@ import android.widget.TextView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; +import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.pgp.WrappedSignature; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java index 44a51a75f..28f7b8bf5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -42,7 +42,6 @@ import android.support.v7.app.ActionBarActivity; import android.view.Menu; import android.view.MenuItem; import android.view.View; -import android.view.Window; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; @@ -57,9 +56,9 @@ import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.OperationResultParcel; import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter; +import org.sufficientlysecure.keychain.ui.widget.SlidingTabLayout; import org.sufficientlysecure.keychain.ui.widget.SlidingTabLayout.TabColorizer; import org.sufficientlysecure.keychain.util.Log; -import org.sufficientlysecure.keychain.ui.widget.SlidingTabLayout; import org.sufficientlysecure.keychain.util.Notify; import java.util.Date; @@ -103,7 +102,6 @@ public class ViewKeyActivity extends ActionBarActivity implements @Override protected void onCreate(Bundle savedInstanceState) { - requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); super.onCreate(savedInstanceState); mExportHelper = new ExportHelper(this); @@ -296,8 +294,7 @@ public class ViewKeyActivity extends ActionBarActivity implements exportHelper.showExportKeysDialog( new long[]{(Long) data.get(KeychainContract.KeyRings.MASTER_KEY_ID)}, - Constants.Path.APP_DIR_FILE, - ((Long) data.get(KeychainContract.KeyRings.HAS_SECRET) == 1) + Constants.Path.APP_DIR_FILE, ((Long) data.get(KeychainContract.KeyRings.HAS_SECRET) == 1) ); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyCertsFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyCertsFragment.java index e98562690..5a55b0dad 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyCertsFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyCertsFragment.java @@ -150,7 +150,7 @@ public class ViewKeyCertsFragment extends LoaderFragment Intent viewIntent = new Intent(getActivity(), ViewCertActivity.class); viewIntent.setData(Certs.buildCertsSpecificUri( - Long.toString(masterKeyId), Long.toString(rank), Long.toString(certifierId))); + masterKeyId, rank, certifierId)); startActivity(viewIntent); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyShareFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyShareFragment.java index 54ab76464..ae0bea5e9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyShareFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyShareFragment.java @@ -49,7 +49,6 @@ import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; import org.sufficientlysecure.keychain.provider.ProviderHelper; -import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException; import org.sufficientlysecure.keychain.ui.dialog.ShareNfcDialogFragment; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Notify; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java index 7a55f9aaa..1f809cc51 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java @@ -111,7 +111,7 @@ public class ImportKeysAdapter extends ArrayAdapter { convertView = mInflater.inflate(R.layout.import_keys_list_entry, null); holder.mainUserId = (TextView) convertView.findViewById(R.id.mainUserId); holder.mainUserIdRest = (TextView) convertView.findViewById(R.id.mainUserIdRest); - holder.keyId = (TextView) convertView.findViewById(R.id.keyId); + holder.keyId = (TextView) convertView.findViewById(R.id.subkey_item_key_id); holder.fingerprint = (TextView) convertView.findViewById(R.id.view_key_fingerprint); holder.algorithm = (TextView) convertView.findViewById(R.id.algorithm); holder.status = (TextView) convertView.findViewById(R.id.status); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java index 4971c535c..04947da93 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java @@ -33,7 +33,6 @@ import java.io.BufferedInputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Iterator; -import java.util.List; public class ImportKeysListLoader extends AsyncTaskLoader>> { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/PagerTabStripAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/PagerTabStripAdapter.java index 3e3098b10..330254a8f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/PagerTabStripAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/PagerTabStripAdapter.java @@ -20,13 +20,8 @@ package org.sufficientlysecure.keychain.ui.adapter; import android.app.Activity; import android.os.Bundle; import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentPagerAdapter; -import android.support.v4.app.FragmentTransaction; import android.support.v7.app.ActionBarActivity; -import android.view.ViewGroup; - -import org.sufficientlysecure.keychain.Constants; import java.util.ArrayList; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java index e69a63c63..0e2177568 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java @@ -152,7 +152,7 @@ abstract public class SelectKeyCursorAdapter extends CursorAdapter { holder.view = view; holder.mainUserId = (TextView) view.findViewById(R.id.mainUserId); holder.mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest); - holder.keyId = (TextView) view.findViewById(R.id.keyId); + holder.keyId = (TextView) view.findViewById(R.id.subkey_item_key_id); holder.status = (TextView) view.findViewById(R.id.status); holder.selected = (CheckBox) view.findViewById(R.id.selected); view.setTag(holder); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java index c2a882fdb..d457e75bd 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java @@ -20,6 +20,7 @@ package org.sufficientlysecure.keychain.ui.adapter; import android.content.Context; import android.content.res.ColorStateList; import android.database.Cursor; +import android.graphics.Typeface; import android.support.v4.widget.CursorAdapter; import android.text.format.DateFormat; import android.view.LayoutInflater; @@ -89,6 +90,20 @@ public class SubkeysAdapter extends CursorAdapter { return mCursor.getLong(INDEX_KEY_ID); } + public long getCreationDate(int position) { + mCursor.moveToPosition(position); + return mCursor.getLong(INDEX_CREATION); + } + + public Long getExpiryDate(int position) { + mCursor.moveToPosition(position); + if (mCursor.isNull(INDEX_EXPIRY)) { + return null; + } else { + return mCursor.getLong(INDEX_EXPIRY); + } + } + @Override public Cursor swapCursor(Cursor newCursor) { hasAnySecret = false; @@ -106,15 +121,18 @@ public class SubkeysAdapter extends CursorAdapter { @Override public void bindView(View view, Context context, Cursor cursor) { - TextView vKeyId = (TextView) view.findViewById(R.id.keyId); - TextView vKeyDetails = (TextView) view.findViewById(R.id.keyDetails); - TextView vKeyExpiry = (TextView) view.findViewById(R.id.keyExpiry); - ImageView vMasterKeyIcon = (ImageView) view.findViewById(R.id.ic_masterKey); - ImageView vCertifyIcon = (ImageView) view.findViewById(R.id.ic_certifyKey); - ImageView vEncryptIcon = (ImageView) view.findViewById(R.id.ic_encryptKey); - ImageView vSignIcon = (ImageView) view.findViewById(R.id.ic_signKey); - ImageView vRevokedKeyIcon = (ImageView) view.findViewById(R.id.ic_revokedKey); - ImageView vEditImage = (ImageView) view.findViewById(R.id.user_id_item_edit_image); + TextView vKeyId = (TextView) view.findViewById(R.id.subkey_item_key_id); + TextView vKeyDetails = (TextView) view.findViewById(R.id.subkey_item_details); + TextView vKeyExpiry = (TextView) view.findViewById(R.id.subkey_item_expiry); + ImageView vCertifyIcon = (ImageView) view.findViewById(R.id.subkey_item_ic_certify); + ImageView vEncryptIcon = (ImageView) view.findViewById(R.id.subkey_item_ic_encrypt); + ImageView vSignIcon = (ImageView) view.findViewById(R.id.subkey_item_ic_sign); + ImageView vRevokedIcon = (ImageView) view.findViewById(R.id.subkey_item_ic_revoked); + ImageView vEditImage = (ImageView) view.findViewById(R.id.subkey_item_edit_image); + + // not used: + ImageView deleteImage = (ImageView) view.findViewById(R.id.subkey_item_delete_button); + deleteImage.setVisibility(View.GONE); long keyId = cursor.getLong(INDEX_KEY_ID); String keyIdStr = PgpKeyHelper.convertKeyIdToHex(keyId); @@ -133,14 +151,26 @@ public class SubkeysAdapter extends CursorAdapter { vKeyDetails.setText(algorithmStr); } + boolean isMasterKey = cursor.getInt(INDEX_RANK) == 0; + if (isMasterKey) { + vKeyId.setTypeface(null, Typeface.BOLD); + } else { + vKeyId.setTypeface(null, Typeface.NORMAL); + } + // Set icons according to properties - vMasterKeyIcon.setVisibility(cursor.getInt(INDEX_RANK) == 0 ? View.VISIBLE : View.INVISIBLE); vCertifyIcon.setVisibility(cursor.getInt(INDEX_CAN_CERTIFY) != 0 ? View.VISIBLE : View.GONE); vEncryptIcon.setVisibility(cursor.getInt(INDEX_CAN_ENCRYPT) != 0 ? View.VISIBLE : View.GONE); vSignIcon.setVisibility(cursor.getInt(INDEX_CAN_SIGN) != 0 ? View.VISIBLE : View.GONE); + // TODO: missing icon for authenticate boolean isRevoked = cursor.getInt(INDEX_IS_REVOKED) > 0; + Date expiryDate = null; + if (!cursor.isNull(INDEX_EXPIRY)) { + expiryDate = new Date(cursor.getLong(INDEX_EXPIRY) * 1000); + } + // for edit key if (mSaveKeyringParcel != null) { boolean revokeThisSubkey = (mSaveKeyringParcel.mRevokeSubKeys.contains(keyId)); @@ -151,24 +181,22 @@ public class SubkeysAdapter extends CursorAdapter { } } + SaveKeyringParcel.SubkeyChange subkeyChange = mSaveKeyringParcel.getSubkeyChange(keyId); + if (subkeyChange != null) { + if (subkeyChange.mExpiry == null) { + expiryDate = null; + } else { + expiryDate = new Date(subkeyChange.mExpiry * 1000); + } + } + vEditImage.setVisibility(View.VISIBLE); } else { vEditImage.setVisibility(View.GONE); } - if (isRevoked) { - vRevokedKeyIcon.setVisibility(View.VISIBLE); - } else { - vKeyId.setTextColor(mDefaultTextColor); - vKeyDetails.setTextColor(mDefaultTextColor); - vKeyExpiry.setTextColor(mDefaultTextColor); - - vRevokedKeyIcon.setVisibility(View.GONE); - } - boolean isExpired; - if (!cursor.isNull(INDEX_EXPIRY)) { - Date expiryDate = new Date(cursor.getLong(INDEX_EXPIRY) * 1000); + if (expiryDate != null) { isExpired = expiryDate.before(new Date()); vKeyExpiry.setText(context.getString(R.string.label_expiry) + ": " @@ -179,6 +207,16 @@ public class SubkeysAdapter extends CursorAdapter { vKeyExpiry.setText(context.getString(R.string.label_expiry) + ": " + context.getString(R.string.none)); } + if (isRevoked) { + vRevokedIcon.setVisibility(View.VISIBLE); + } else { + vKeyId.setTextColor(mDefaultTextColor); + vKeyDetails.setTextColor(mDefaultTextColor); + vKeyExpiry.setTextColor(mDefaultTextColor); + + vRevokedIcon.setVisibility(View.GONE); + } + // if key is expired or revoked, strike through text boolean isInvalid = isRevoked || isExpired; if (isInvalid) { @@ -195,7 +233,7 @@ public class SubkeysAdapter extends CursorAdapter { public View newView(Context context, Cursor cursor, ViewGroup parent) { View view = mInflater.inflate(R.layout.view_key_subkey_item, null); if (mDefaultTextColor == null) { - TextView keyId = (TextView) view.findViewById(R.id.keyId); + TextView keyId = (TextView) view.findViewById(R.id.subkey_item_key_id); mDefaultTextColor = keyId.getTextColors(); } return view; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAddedAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAddedAdapter.java index 25509fee5..be2e17c63 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAddedAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAddedAdapter.java @@ -17,48 +17,29 @@ package org.sufficientlysecure.keychain.ui.adapter; -import android.annotation.TargetApi; import android.app.Activity; -import android.app.AlertDialog; import android.content.Context; -import android.content.DialogInterface; -import android.os.Build; -import android.text.Editable; -import android.text.TextUtils; -import android.text.TextWatcher; -import android.util.Patterns; +import android.text.format.DateFormat; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.view.inputmethod.InputMethodManager; -import android.widget.AdapterView; import android.widget.ArrayAdapter; -import android.widget.AutoCompleteTextView; -import android.widget.EditText; import android.widget.ImageButton; -import android.widget.Spinner; +import android.widget.ImageView; import android.widget.TextView; -import org.sufficientlysecure.keychain.Constants; +import org.spongycastle.bcpg.sig.KeyFlags; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.helper.ContactHelper; +import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; -import org.sufficientlysecure.keychain.ui.dialog.CreateKeyDialogFragment; -import org.sufficientlysecure.keychain.util.Choice; -import java.util.ArrayList; -import java.util.Arrays; +import java.util.Date; import java.util.List; -import java.util.regex.Matcher; public class SubkeysAddedAdapter extends ArrayAdapter { private LayoutInflater mInflater; private Activity mActivity; - public interface OnAlgorithmSelectedListener { - public void onAlgorithmSelected(Choice algorithmChoice, int keySize); - } - // hold a private reference to the underlying data List private List mData; @@ -70,12 +51,12 @@ public class SubkeysAddedAdapter extends ArrayAdapter parent, View view, int position, long id) { - Choice newKeyAlgorithmChoice = (Choice) holder.mAlgorithmSpinner.getSelectedItem(); - // update referenced model item - holder.mModel.mAlgorithm = newKeyAlgorithmChoice.getId(); - } - - @Override - public void onNothingSelected(AdapterView parent) { - } - }); - - holder.mKeySizeSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - Choice newKeyAlgorithmChoice = (Choice) holder.mAlgorithmSpinner.getSelectedItem(); - int newKeySize = getProperKeyLength(newKeyAlgorithmChoice.getId(), - getSelectedKeyLength(holder.mKeySizeSpinner, holder.mCustomKeyEditText)); - // update referenced model item - holder.mModel.mKeysize = newKeySize; - } - - @Override - public void onNothingSelected(AdapterView parent) { - } - }); - holder.vDelete.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -136,226 +98,44 @@ public class SubkeysAddedAdapter extends ArrayAdapter choices = new ArrayList(); - choices.add(new Choice(Constants.choice.algorithm.dsa, mActivity.getResources().getString( - R.string.dsa))); - if (!wouldBeMasterKey) { - choices.add(new Choice(Constants.choice.algorithm.elgamal, mActivity.getResources().getString( - R.string.elgamal))); + if (holder.mModel.mExpiry != null) { + Date expiryDate = new Date(holder.mModel.mExpiry * 1000); + + holder.vKeyExpiry.setText(getContext().getString(R.string.label_expiry) + ": " + + DateFormat.getDateFormat(getContext()).format(expiryDate)); + } else { + holder.vKeyExpiry.setText(getContext().getString(R.string.label_expiry) + ": " + + getContext().getString(R.string.none)); } - choices.add(new Choice(Constants.choice.algorithm.rsa, mActivity.getResources().getString( - R.string.rsa))); - - ArrayAdapter adapter = new ArrayAdapter(mActivity, - android.R.layout.simple_spinner_item, choices); - adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - holder.mAlgorithmSpinner.setAdapter(adapter); - // make RSA the default - for (int i = 0; i < choices.size(); ++i) { - if (choices.get(i).getId() == Constants.choice.algorithm.rsa) { - holder.mAlgorithmSpinner.setSelection(i); - break; - } + int flags = holder.mModel.mFlags; + if ((flags & KeyFlags.CERTIFY_OTHER) > 0) { + holder.vCertifyIcon.setVisibility(View.VISIBLE); + } else { + holder.vCertifyIcon.setVisibility(View.GONE); } - - // dynamic ArrayAdapter must be created (instead of ArrayAdapter.getFromResource), because it's content may change - ArrayAdapter keySizeAdapter = new ArrayAdapter(mActivity, android.R.layout.simple_spinner_item, - new ArrayList(Arrays.asList(mActivity.getResources().getStringArray(R.array.rsa_key_size_spinner_values)))); - keySizeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - holder.mKeySizeSpinner.setAdapter(keySizeAdapter); - holder.mKeySizeSpinner.setSelection(1); // Default to 4096 for the key length - - holder.mCustomKeyEditText.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - } - - @Override - public void afterTextChanged(Editable s) { -// setOkButtonAvailability(alertDialog); - } - }); - - holder.mKeySizeSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - setCustomKeyVisibility(holder.mKeySizeSpinner, holder.mCustomKeyEditText, - holder.mCustomKeyTextView, holder.mCustomKeyInfoTextView); -// setOkButtonAvailability(alertDialog); - } - - @Override - public void onNothingSelected(AdapterView parent) { - } - }); - - holder.mAlgorithmSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - setKeyLengthSpinnerValuesForAlgorithm(((Choice) parent.getSelectedItem()).getId(), - holder.mKeySizeSpinner, holder.mCustomKeyInfoTextView); - - setCustomKeyVisibility(holder.mKeySizeSpinner, holder.mCustomKeyEditText, - holder.mCustomKeyTextView, holder.mCustomKeyInfoTextView); -// setOkButtonAvailability(alertDialog); - } - - @Override - public void onNothingSelected(AdapterView parent) { - } - }); -// -// holder.vAddress.setText(holder.mModel.address); -// holder.vAddress.setThreshold(1); // Start working from first character -// holder.vAddress.setAdapter(mAutoCompleteEmailAdapter); -// -// holder.vName.setText(holder.mModel.name); -// holder.vName.setThreshold(1); // Start working from first character -// holder.vName.setAdapter(mAutoCompleteNameAdapter); -// -// holder.vComment.setText(holder.mModel.comment); + if ((flags & KeyFlags.SIGN_DATA) > 0) { + holder.vSignIcon.setVisibility(View.VISIBLE); + } else { + holder.vSignIcon.setVisibility(View.GONE); + } + if (((flags & KeyFlags.ENCRYPT_COMMS) > 0) + || ((flags & KeyFlags.ENCRYPT_STORAGE) > 0)) { + holder.vEncryptIcon.setVisibility(View.VISIBLE); + } else { + holder.vEncryptIcon.setVisibility(View.GONE); + } + // TODO: missing icon for authenticate return convertView; } - - private int getSelectedKeyLength(Spinner keySizeSpinner, EditText customKeyEditText) { - final String selectedItemString = (String) keySizeSpinner.getSelectedItem(); - final String customLengthString = mActivity.getResources().getString(R.string.key_size_custom); - final boolean customSelected = customLengthString.equals(selectedItemString); - String keyLengthString = customSelected ? customKeyEditText.getText().toString() : selectedItemString; - int keySize; - try { - keySize = Integer.parseInt(keyLengthString); - } catch (NumberFormatException e) { - keySize = 0; - } - return keySize; - } - - /** - *

RSA

- *

for RSA algorithm, key length must be greater than 1024 (according to - * #102). Possibility to generate keys bigger - * than 8192 bits is currently disabled, because it's almost impossible to generate them on a mobile device (check - * RSA key length plot and - * Cryptographic Key Length Recommendation). Also, key length must be a - * multiplicity of 8.

- *

ElGamal

- *

For ElGamal algorithm, supported key lengths are 1536, 2048, 3072, 4096 or 8192 bits.

- *

DSA

- *

For DSA algorithm key length must be between 512 and 1024. Also, it must me dividable by 64.

- * - * @return correct key length, according to SpongyCastle specification. Returns -1, if key length is - * inappropriate. - */ - private int getProperKeyLength(int algorithmId, int currentKeyLength) { - final int[] elGamalSupportedLengths = {1536, 2048, 3072, 4096, 8192}; - int properKeyLength = -1; - switch (algorithmId) { - case Constants.choice.algorithm.rsa: - if (currentKeyLength > 1024 && currentKeyLength <= 8192) { - properKeyLength = currentKeyLength + ((8 - (currentKeyLength % 8)) % 8); - } - break; - case Constants.choice.algorithm.elgamal: - int[] elGammalKeyDiff = new int[elGamalSupportedLengths.length]; - for (int i = 0; i < elGamalSupportedLengths.length; i++) { - elGammalKeyDiff[i] = Math.abs(elGamalSupportedLengths[i] - currentKeyLength); - } - int minimalValue = Integer.MAX_VALUE; - int minimalIndex = -1; - for (int i = 0; i < elGammalKeyDiff.length; i++) { - if (elGammalKeyDiff[i] <= minimalValue) { - minimalValue = elGammalKeyDiff[i]; - minimalIndex = i; - } - } - properKeyLength = elGamalSupportedLengths[minimalIndex]; - break; - case Constants.choice.algorithm.dsa: - if (currentKeyLength >= 512 && currentKeyLength <= 1024) { - properKeyLength = currentKeyLength + ((64 - (currentKeyLength % 64)) % 64); - } - break; - } - return properKeyLength; - } - - // TODO: make this an error message on the field -// private boolean setOkButtonAvailability(AlertDialog alertDialog) { -// final Choice selectedAlgorithm = (Choice) mAlgorithmSpinner.getSelectedItem(); -// final int selectedKeySize = getSelectedKeyLength(); //Integer.parseInt((String) mKeySizeSpinner.getSelectedItem()); -// final int properKeyLength = getProperKeyLength(selectedAlgorithm.getId(), selectedKeySize); -// alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(properKeyLength > 0); -// } - - private void setCustomKeyVisibility(Spinner keySizeSpinner, EditText customkeyedittext, TextView customKeyTextView, TextView customKeyInfoTextView) { - final String selectedItemString = (String) keySizeSpinner.getSelectedItem(); - final String customLengthString = mActivity.getResources().getString(R.string.key_size_custom); - final boolean customSelected = customLengthString.equals(selectedItemString); - final int visibility = customSelected ? View.VISIBLE : View.GONE; - - customkeyedittext.setVisibility(visibility); - customKeyTextView.setVisibility(visibility); - customKeyInfoTextView.setVisibility(visibility); - - // hide keyboard after setting visibility to gone - if (visibility == View.GONE) { - InputMethodManager imm = (InputMethodManager) - mActivity.getSystemService(mActivity.INPUT_METHOD_SERVICE); - imm.hideSoftInputFromWindow(customkeyedittext.getWindowToken(), 0); - } - } - - private void setKeyLengthSpinnerValuesForAlgorithm(int algorithmId, Spinner keySizeSpinner, TextView customKeyInfoTextView) { - final ArrayAdapter keySizeAdapter = (ArrayAdapter) keySizeSpinner.getAdapter(); - final Object selectedItem = keySizeSpinner.getSelectedItem(); - keySizeAdapter.clear(); - switch (algorithmId) { - case Constants.choice.algorithm.rsa: - replaceArrayAdapterContent(keySizeAdapter, R.array.rsa_key_size_spinner_values); - customKeyInfoTextView.setText(mActivity.getResources().getString(R.string.key_size_custom_info_rsa)); - break; - case Constants.choice.algorithm.elgamal: - replaceArrayAdapterContent(keySizeAdapter, R.array.elgamal_key_size_spinner_values); - customKeyInfoTextView.setText(""); // ElGamal does not support custom key length - break; - case Constants.choice.algorithm.dsa: - replaceArrayAdapterContent(keySizeAdapter, R.array.dsa_key_size_spinner_values); - customKeyInfoTextView.setText(mActivity.getResources().getString(R.string.key_size_custom_info_dsa)); - break; - } - keySizeAdapter.notifyDataSetChanged(); - - // when switching algorithm, try to select same key length as before - for (int i = 0; i < keySizeAdapter.getCount(); i++) { - if (selectedItem.equals(keySizeAdapter.getItem(i))) { - keySizeSpinner.setSelection(i); - break; - } - } - } - - @TargetApi(Build.VERSION_CODES.HONEYCOMB) - private void replaceArrayAdapterContent(ArrayAdapter arrayAdapter, int stringArrayResourceId) { - final String[] spinnerValuesStringArray = mActivity.getResources().getStringArray(stringArrayResourceId); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - arrayAdapter.addAll(spinnerValuesStringArray); - } else { - for (final String value : spinnerValuesStringArray) { - arrayAdapter.add(value); - } - } - } - } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java index ee3341c08..9bf47a387 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java @@ -252,6 +252,26 @@ public class UserIdsAdapter extends CursorAdapter implements AdapterView.OnItemC return mCursor.getString(INDEX_USER_ID); } + public boolean getIsRevoked(int position) { + mCursor.moveToPosition(position); + return mCursor.getInt(INDEX_IS_REVOKED) > 0; + } + + public boolean getIsRevokedPending(int position) { + mCursor.moveToPosition(position); + String userId = mCursor.getString(INDEX_USER_ID); + + boolean isRevokedPending = false; + if (mSaveKeyringParcel != null) { + if (mSaveKeyringParcel.mRevokeUserIds.contains(userId)) { + isRevokedPending = true; + } + + } + + return isRevokedPending; + } + @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { View view = mInflater.inflate(R.layout.view_key_user_id_item, null); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/CreateKeyDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddSubkeyDialogFragment.java similarity index 70% rename from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/CreateKeyDialogFragment.java rename to OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddSubkeyDialogFragment.java index 920743a9b..cb31978e9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/CreateKeyDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddSubkeyDialogFragment.java @@ -27,46 +27,64 @@ import android.support.v4.app.DialogFragment; import android.support.v4.app.FragmentActivity; import android.text.Editable; import android.text.TextWatcher; +import android.text.format.DateUtils; import android.view.LayoutInflater; import android.view.View; import android.view.inputmethod.InputMethodManager; import android.widget.AdapterView; import android.widget.ArrayAdapter; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.DatePicker; import android.widget.EditText; import android.widget.Spinner; +import android.widget.TableRow; import android.widget.TextView; -import org.sufficientlysecure.keychain.Constants; +import org.spongycastle.bcpg.PublicKeyAlgorithmTags; +import org.spongycastle.bcpg.sig.KeyFlags; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.util.Choice; import java.util.ArrayList; import java.util.Arrays; +import java.util.Calendar; +import java.util.Date; +import java.util.TimeZone; -public class CreateKeyDialogFragment extends DialogFragment { +public class AddSubkeyDialogFragment extends DialogFragment { public interface OnAlgorithmSelectedListener { - public void onAlgorithmSelected(Choice algorithmChoice, int keySize); + public void onAlgorithmSelected(SaveKeyringParcel.SubkeyAdd newSubkey); } - private static final String ARG_EDITOR_CHILD_COUNT = "child_count"; + private static final String ARG_WILL_BE_MASTER_KEY = "will_be_master_key"; private OnAlgorithmSelectedListener mAlgorithmSelectedListener; + + private CheckBox mNoExpiryCheckBox; + private TableRow mExpiryRow; + private DatePicker mExpiryDatePicker; private Spinner mAlgorithmSpinner; private Spinner mKeySizeSpinner; private TextView mCustomKeyTextView; private EditText mCustomKeyEditText; private TextView mCustomKeyInfoTextView; + private CheckBox mFlagCertify; + private CheckBox mFlagSign; + private CheckBox mFlagEncrypt; + private CheckBox mFlagAuthenticate; public void setOnAlgorithmSelectedListener(OnAlgorithmSelectedListener listener) { mAlgorithmSelectedListener = listener; } - public static CreateKeyDialogFragment newInstance(int mEditorChildCount) { - CreateKeyDialogFragment frag = new CreateKeyDialogFragment(); + public static AddSubkeyDialogFragment newInstance(boolean willBeMasterKey) { + AddSubkeyDialogFragment frag = new AddSubkeyDialogFragment(); Bundle args = new Bundle(); - args.putInt(ARG_EDITOR_CHILD_COUNT, mEditorChildCount); + args.putBoolean(ARG_WILL_BE_MASTER_KEY, willBeMasterKey); frag.setArguments(args); @@ -78,42 +96,64 @@ public class CreateKeyDialogFragment extends DialogFragment { final FragmentActivity context = getActivity(); final LayoutInflater mInflater; - final int childCount = getArguments().getInt(ARG_EDITOR_CHILD_COUNT); + final boolean willBeMasterKey = getArguments().getBoolean(ARG_WILL_BE_MASTER_KEY); mInflater = context.getLayoutInflater(); CustomAlertDialogBuilder dialog = new CustomAlertDialogBuilder(context); - View view = mInflater.inflate(R.layout.create_key_dialog, null); + View view = mInflater.inflate(R.layout.add_subkey_dialog, null); dialog.setView(view); - dialog.setTitle(R.string.title_create_key); + dialog.setTitle(R.string.title_add_subkey); - boolean wouldBeMasterKey = (childCount == 0); + mNoExpiryCheckBox = (CheckBox) view.findViewById(R.id.add_subkey_no_expiry); + mExpiryRow = (TableRow) view.findViewById(R.id.add_subkey_expiry_row); + mExpiryDatePicker = (DatePicker) view.findViewById(R.id.add_subkey_expiry_date_picker); + mAlgorithmSpinner = (Spinner) view.findViewById(R.id.add_subkey_algorithm); + mKeySizeSpinner = (Spinner) view.findViewById(R.id.add_subkey_size); + mCustomKeyTextView = (TextView) view.findViewById(R.id.add_subkey_custom_key_size_label); + mCustomKeyEditText = (EditText) view.findViewById(R.id.add_subkey_custom_key_size_input); + mCustomKeyInfoTextView = (TextView) view.findViewById(R.id.add_subkey_custom_key_size_info); + mFlagCertify = (CheckBox) view.findViewById(R.id.add_subkey_flag_certify); + mFlagSign = (CheckBox) view.findViewById(R.id.add_subkey_flag_sign); + mFlagEncrypt = (CheckBox) view.findViewById(R.id.add_subkey_flag_encrypt); + mFlagAuthenticate = (CheckBox) view.findViewById(R.id.add_subkey_flag_authenticate); - mAlgorithmSpinner = (Spinner) view.findViewById(R.id.create_key_algorithm); - ArrayList choices = new ArrayList(); - choices.add(new Choice(Constants.choice.algorithm.dsa, getResources().getString( - R.string.dsa))); - if (!wouldBeMasterKey) { - choices.add(new Choice(Constants.choice.algorithm.elgamal, getResources().getString( - R.string.elgamal))); + mNoExpiryCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (isChecked) { + mExpiryRow.setVisibility(View.GONE); + } else { + mExpiryRow.setVisibility(View.VISIBLE); + } + } + }); + + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) { + mExpiryDatePicker.setMinDate(new Date().getTime() + DateUtils.DAY_IN_MILLIS); } - choices.add(new Choice(Constants.choice.algorithm.rsa, getResources().getString( + ArrayList choices = new ArrayList(); + choices.add(new Choice(PublicKeyAlgorithmTags.DSA, getResources().getString( + R.string.dsa))); + if (!willBeMasterKey) { + choices.add(new Choice(PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT, getResources().getString( + R.string.elgamal))); + } + choices.add(new Choice(PublicKeyAlgorithmTags.RSA_GENERAL, getResources().getString( R.string.rsa))); - ArrayAdapter adapter = new ArrayAdapter(context, android.R.layout.simple_spinner_item, choices); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); mAlgorithmSpinner.setAdapter(adapter); // make RSA the default for (int i = 0; i < choices.size(); ++i) { - if (choices.get(i).getId() == Constants.choice.algorithm.rsa) { + if (choices.get(i).getId() == PublicKeyAlgorithmTags.RSA_GENERAL) { mAlgorithmSpinner.setSelection(i); break; } } - mKeySizeSpinner = (Spinner) view.findViewById(R.id.create_key_size); // dynamic ArrayAdapter must be created (instead of ArrayAdapter.getFromResource), because it's content may change ArrayAdapter keySizeAdapter = new ArrayAdapter(context, android.R.layout.simple_spinner_item, new ArrayList(Arrays.asList(getResources().getStringArray(R.array.rsa_key_size_spinner_values)))); @@ -121,9 +161,6 @@ public class CreateKeyDialogFragment extends DialogFragment { mKeySizeSpinner.setAdapter(keySizeAdapter); mKeySizeSpinner.setSelection(1); // Default to 4096 for the key length - mCustomKeyTextView = (TextView) view.findViewById(R.id.custom_key_size_label); - mCustomKeyEditText = (EditText) view.findViewById(R.id.custom_key_size_input); - mCustomKeyInfoTextView = (TextView) view.findViewById(R.id.custom_key_size_info); dialog.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @@ -131,7 +168,39 @@ public class CreateKeyDialogFragment extends DialogFragment { di.dismiss(); Choice newKeyAlgorithmChoice = (Choice) mAlgorithmSpinner.getSelectedItem(); int newKeySize = getProperKeyLength(newKeyAlgorithmChoice.getId(), getSelectedKeyLength()); - mAlgorithmSelectedListener.onAlgorithmSelected(newKeyAlgorithmChoice, newKeySize); + + int flags = 0; + if (mFlagCertify.isChecked()) { + flags |= KeyFlags.CERTIFY_OTHER; + } + if (mFlagSign.isChecked()) { + flags |= KeyFlags.SIGN_DATA; + } + if (mFlagEncrypt.isChecked()) { + flags |= KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE; + } + if (mFlagAuthenticate.isChecked()) { + flags |= KeyFlags.AUTHENTICATION; + } + + Long expiry; + if (mNoExpiryCheckBox.isChecked()) { + expiry = null; + } else { + Calendar selectedCal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + //noinspection ResourceType + selectedCal.set(mExpiryDatePicker.getYear(), + mExpiryDatePicker.getMonth(), mExpiryDatePicker.getDayOfMonth()); + expiry = selectedCal.getTime().getTime() / 1000; + } + + SaveKeyringParcel.SubkeyAdd newSubkey = new SaveKeyringParcel.SubkeyAdd( + newKeyAlgorithmChoice.getId(), + newKeySize, + flags, + expiry + ); + mAlgorithmSelectedListener.onAlgorithmSelected(newSubkey); } } ); @@ -142,7 +211,8 @@ public class CreateKeyDialogFragment extends DialogFragment { public void onClick(DialogInterface di, int id) { di.dismiss(); } - }); + } + ); final AlertDialog alertDialog = dialog.show(); @@ -224,12 +294,12 @@ public class CreateKeyDialogFragment extends DialogFragment { final int[] elGamalSupportedLengths = {1536, 2048, 3072, 4096, 8192}; int properKeyLength = -1; switch (algorithmId) { - case Constants.choice.algorithm.rsa: - if (currentKeyLength > 1024 && currentKeyLength <= 8192) { + case PublicKeyAlgorithmTags.RSA_GENERAL: + if (currentKeyLength > 1024 && currentKeyLength <= 16384) { properKeyLength = currentKeyLength + ((8 - (currentKeyLength % 8)) % 8); } break; - case Constants.choice.algorithm.elgamal: + case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT: int[] elGammalKeyDiff = new int[elGamalSupportedLengths.length]; for (int i = 0; i < elGamalSupportedLengths.length; i++) { elGammalKeyDiff[i] = Math.abs(elGamalSupportedLengths[i] - currentKeyLength); @@ -244,7 +314,7 @@ public class CreateKeyDialogFragment extends DialogFragment { } properKeyLength = elGamalSupportedLengths[minimalIndex]; break; - case Constants.choice.algorithm.dsa: + case PublicKeyAlgorithmTags.DSA: if (currentKeyLength >= 512 && currentKeyLength <= 1024) { properKeyLength = currentKeyLength + ((64 - (currentKeyLength % 64)) % 64); } @@ -283,15 +353,15 @@ public class CreateKeyDialogFragment extends DialogFragment { final Object selectedItem = mKeySizeSpinner.getSelectedItem(); keySizeAdapter.clear(); switch (algorithmId) { - case Constants.choice.algorithm.rsa: + case PublicKeyAlgorithmTags.RSA_GENERAL: replaceArrayAdapterContent(keySizeAdapter, R.array.rsa_key_size_spinner_values); mCustomKeyInfoTextView.setText(getResources().getString(R.string.key_size_custom_info_rsa)); break; - case Constants.choice.algorithm.elgamal: + case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT: replaceArrayAdapterContent(keySizeAdapter, R.array.elgamal_key_size_spinner_values); mCustomKeyInfoTextView.setText(""); // ElGamal does not support custom key length break; - case Constants.choice.algorithm.dsa: + case PublicKeyAlgorithmTags.DSA: replaceArrayAdapterContent(keySizeAdapter, R.array.dsa_key_size_spinner_values); mCustomKeyInfoTextView.setText(getResources().getString(R.string.key_size_custom_info_dsa)); break; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddUserIdDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddUserIdDialogFragment.java index d5264ae10..4d6b13476 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddUserIdDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddUserIdDialogFragment.java @@ -29,7 +29,6 @@ import android.os.Messenger; import android.os.RemoteException; import android.support.v4.app.DialogFragment; import android.text.Editable; -import android.text.TextUtils; import android.text.TextWatcher; import android.util.Patterns; import android.view.KeyEvent; @@ -149,6 +148,14 @@ public class AddUserIdDialogFragment extends DialogFragment implements OnEditorA } }); + mName.setThreshold(1); // Start working from first character + mName.setAdapter( + new ArrayAdapter + (getActivity(), android.R.layout.simple_spinner_dropdown_item, + ContactHelper.getPossibleUserNames(getActivity()) + ) + ); + alert.setNegativeButton(android.R.string.cancel, new OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ChangeExpiryDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ChangeExpiryDialogFragment.java deleted file mode 100644 index d5354a9f6..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ChangeExpiryDialogFragment.java +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright (C) 2014 Dominik Schürmann - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.ui.dialog; - -import android.app.DatePickerDialog; -import android.app.Dialog; -import android.content.Context; -import android.content.DialogInterface; -import android.os.Bundle; -import android.os.Message; -import android.os.Messenger; -import android.os.RemoteException; -import android.support.v4.app.DialogFragment; -import android.text.format.DateUtils; -import android.widget.DatePicker; - -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.util.Log; - -import java.util.Calendar; -import java.util.Date; -import java.util.GregorianCalendar; -import java.util.TimeZone; - -public class ChangeExpiryDialogFragment extends DialogFragment { - private static final String ARG_MESSENGER = "messenger"; - private static final String ARG_CREATION_DATE = "creation_date"; - private static final String ARG_EXPIRY_DATE = "expiry_date"; - - public static final int MESSAGE_NEW_EXPIRY_DATE = 1; - public static final String MESSAGE_DATA_EXPIRY_DATE = "expiry_date"; - - private Messenger mMessenger; - private Calendar mCreationCal; - private Calendar mExpiryCal; - - private int mDatePickerResultCount = 0; - private DatePickerDialog.OnDateSetListener mExpiryDateSetListener = - new DatePickerDialog.OnDateSetListener() { - public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) { - // Note: Ignore results after the first one - android sends multiples. - if (mDatePickerResultCount++ == 0) { - Calendar selectedCal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - selectedCal.set(year, monthOfYear, dayOfMonth); - if (mExpiryCal != null) { - long numDays = (selectedCal.getTimeInMillis() / 86400000) - - (mExpiryCal.getTimeInMillis() / 86400000); - if (numDays > 0) { - Bundle data = new Bundle(); - data.putSerializable(MESSAGE_DATA_EXPIRY_DATE, selectedCal.getTime()); - sendMessageToHandler(MESSAGE_NEW_EXPIRY_DATE, data); - } - } else { - Bundle data = new Bundle(); - data.putSerializable(MESSAGE_DATA_EXPIRY_DATE, selectedCal.getTime()); - sendMessageToHandler(MESSAGE_NEW_EXPIRY_DATE, data); - } - } - } - }; - - public class ExpiryDatePickerDialog extends DatePickerDialog { - - public ExpiryDatePickerDialog(Context context, OnDateSetListener callBack, - int year, int monthOfYear, int dayOfMonth) { - super(context, callBack, year, monthOfYear, dayOfMonth); - } - - // set permanent title - public void setTitle(CharSequence title) { - super.setTitle(getContext().getString(R.string.expiry_date_dialog_title)); - } - } - - /** - * Creates new instance of this dialog fragment - */ - public static ChangeExpiryDialogFragment newInstance(Messenger messenger, - Date creationDate, Date expiryDate) { - ChangeExpiryDialogFragment frag = new ChangeExpiryDialogFragment(); - Bundle args = new Bundle(); - args.putParcelable(ARG_MESSENGER, messenger); - args.putSerializable(ARG_CREATION_DATE, creationDate); - args.putSerializable(ARG_EXPIRY_DATE, expiryDate); - - frag.setArguments(args); - - return frag; - } - - /** - * Creates dialog - */ - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - mMessenger = getArguments().getParcelable(ARG_MESSENGER); - Date creationDate = (Date) getArguments().getSerializable(ARG_CREATION_DATE); - Date expiryDate = (Date) getArguments().getSerializable(ARG_EXPIRY_DATE); - - mCreationCal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - mCreationCal.setTime(creationDate); - mExpiryCal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - mExpiryCal.setTime(expiryDate); - - /* - * Using custom DatePickerDialog which overrides the setTitle because - * the DatePickerDialog title is buggy (unix warparound bug). - * See: https://code.google.com/p/android/issues/detail?id=49066 - */ - DatePickerDialog dialog = new ExpiryDatePickerDialog(getActivity(), - mExpiryDateSetListener, mExpiryCal.get(Calendar.YEAR), mExpiryCal.get(Calendar.MONTH), - mExpiryCal.get(Calendar.DAY_OF_MONTH)); - mDatePickerResultCount = 0; - dialog.setCancelable(true); - dialog.setButton(Dialog.BUTTON_NEGATIVE, - getActivity().getString(R.string.btn_no_date), - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - // Note: Ignore results after the first one - android sends multiples. - if (mDatePickerResultCount++ == 0) { - // none expiry dates corresponds to a null message - Bundle data = new Bundle(); - data.putSerializable(MESSAGE_DATA_EXPIRY_DATE, null); - sendMessageToHandler(MESSAGE_NEW_EXPIRY_DATE, data); - } - } - } - ); - - // setCalendarViewShown() is supported from API 11 onwards. - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) { - // Hide calendarView in tablets because of the unix warparound bug. - dialog.getDatePicker().setCalendarViewShown(false); - } - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) { - // will crash with IllegalArgumentException if we set a min date - // that is not before expiry - if (mCreationCal != null && mCreationCal.before(mExpiryCal)) { - dialog.getDatePicker().setMinDate(mCreationCal.getTime().getTime() - + DateUtils.DAY_IN_MILLIS); - } else { - // When created date isn't available - dialog.getDatePicker().setMinDate(mExpiryCal.getTime().getTime() - + DateUtils.DAY_IN_MILLIS); - } - } - - return dialog; - } - - /** - * Send message back to handler which is initialized in a activity - * - * @param what Message integer you want to send - */ - private void sendMessageToHandler(Integer what, Bundle data) { - Message msg = Message.obtain(); - msg.what = what; - if (data != null) { - msg.setData(data); - } - - try { - mMessenger.send(msg); - } catch (RemoteException e) { - Log.w(Constants.TAG, "Exception sending message, Is handler present?", e); - } catch (NullPointerException e) { - Log.w(Constants.TAG, "Messenger is null!", e); - } - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteFileDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteFileDialogFragment.java index cae6cf043..5f29f1d18 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteFileDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteFileDialogFragment.java @@ -18,40 +18,21 @@ package org.sufficientlysecure.keychain.ui.dialog; import android.app.Dialog; -import android.app.ProgressDialog; import android.content.DialogInterface; -import android.content.Intent; import android.net.Uri; +import android.os.Build; import android.os.Bundle; -import android.os.Message; -import android.os.Messenger; import android.provider.DocumentsContract; import android.support.v4.app.DialogFragment; import android.support.v4.app.FragmentActivity; import android.widget.Toast; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.service.KeychainIntentService; -import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; +import org.sufficientlysecure.keychain.helper.FileHelper; public class DeleteFileDialogFragment extends DialogFragment { - private static final String ARG_DELETE_FILE = "delete_file"; private static final String ARG_DELETE_URI = "delete_uri"; - /** - * Creates new instance of this delete file dialog fragment - */ - public static DeleteFileDialogFragment newInstance(String deleteFile) { - DeleteFileDialogFragment frag = new DeleteFileDialogFragment(); - Bundle args = new Bundle(); - - args.putString(ARG_DELETE_FILE, deleteFile); - - frag.setArguments(args); - - return frag; - } - /** * Creates new instance of this delete file dialog fragment */ @@ -73,15 +54,15 @@ public class DeleteFileDialogFragment extends DialogFragment { public Dialog onCreateDialog(Bundle savedInstanceState) { final FragmentActivity activity = getActivity(); - final Uri deleteUri = getArguments().containsKey(ARG_DELETE_URI) ? getArguments().getParcelable(ARG_DELETE_URI) : null; - final String deleteFile = getArguments().getString(ARG_DELETE_FILE); + final Uri deleteUri = getArguments().getParcelable(ARG_DELETE_URI); + final String deleteFilename = FileHelper.getFilename(getActivity(), deleteUri); CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(activity); alert.setIcon(R.drawable.ic_dialog_alert_holo_light); alert.setTitle(R.string.warning); - alert.setMessage(this.getString(R.string.file_delete_confirmation, deleteFile)); + alert.setMessage(this.getString(R.string.file_delete_confirmation, deleteFilename)); alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @@ -89,51 +70,23 @@ public class DeleteFileDialogFragment extends DialogFragment { public void onClick(DialogInterface dialog, int id) { dismiss(); - if (deleteUri != null) { - // We can not securely delete Documents, so just use usual delete on them - DocumentsContract.deleteDocument(getActivity().getContentResolver(), deleteUri); + // We can not securely delete Uris, so just use usual delete on them + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + if (DocumentsContract.deleteDocument(getActivity().getContentResolver(), deleteUri)) { + Toast.makeText(getActivity(), R.string.file_delete_successful, Toast.LENGTH_SHORT).show(); + return; + } + } + + if (getActivity().getContentResolver().delete(deleteUri, null, null) > 0) { + Toast.makeText(getActivity(), R.string.file_delete_successful, Toast.LENGTH_SHORT).show(); return; } - // Send all information needed to service to edit key in other thread - Intent intent = new Intent(activity, KeychainIntentService.class); + Toast.makeText(getActivity(), getActivity().getString(R.string.error_file_delete_failed, deleteFilename), Toast.LENGTH_SHORT).show(); - // fill values for this action - Bundle data = new Bundle(); - - intent.setAction(KeychainIntentService.ACTION_DELETE_FILE_SECURELY); - data.putString(KeychainIntentService.DELETE_FILE, deleteFile); - intent.putExtra(KeychainIntentService.EXTRA_DATA, data); - - ProgressDialogFragment deletingDialog = ProgressDialogFragment.newInstance( - getString(R.string.progress_deleting_securely), - ProgressDialog.STYLE_HORIZONTAL, - false, - null); - - // Message is received after deleting is done in KeychainIntentService - KeychainIntentServiceHandler saveHandler = - new KeychainIntentServiceHandler(activity, deletingDialog) { - public void handleMessage(Message message) { - // handle messages by standard KeychainIntentHandler first - super.handleMessage(message); - - if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { - Toast.makeText(activity, R.string.file_delete_successful, - Toast.LENGTH_SHORT).show(); - } - } - }; - - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(saveHandler); - intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger); - - // show progress dialog - deletingDialog.show(activity.getSupportFragmentManager(), "deletingDialog"); - - // start service with intent - activity.startService(intent); + // Note: We can't delete every file... + // If possible we should find out if deletion is possible before even showing the option to do so. } }); alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java index 01d2fae6a..4927a4d24 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java @@ -123,7 +123,7 @@ public class DeleteKeyDialogFragment extends DialogFragment { boolean success = false; for (long masterKeyId : masterKeyIds) { int count = activity.getContentResolver().delete( - KeyRingData.buildPublicKeyRingUri(Long.toString(masterKeyId)), null, null + KeyRingData.buildPublicKeyRingUri(masterKeyId), null, null ); success = count > 0; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyExpiryDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyExpiryDialogFragment.java new file mode 100644 index 000000000..aa63f9944 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyExpiryDialogFragment.java @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.ui.dialog; + +import android.app.Activity; +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.os.Message; +import android.os.Messenger; +import android.os.RemoteException; +import android.support.v4.app.DialogFragment; +import android.text.format.DateUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.DatePicker; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.util.Log; + +import java.util.Calendar; +import java.util.Date; +import java.util.TimeZone; + +public class EditSubkeyExpiryDialogFragment extends DialogFragment { + private static final String ARG_MESSENGER = "messenger"; + private static final String ARG_CREATION_DATE = "creation_date"; + private static final String ARG_EXPIRY_DATE = "expiry_date"; + + public static final int MESSAGE_NEW_EXPIRY_DATE = 1; + public static final int MESSAGE_CANCEL = 2; + public static final String MESSAGE_DATA_EXPIRY_DATE = "expiry_date"; + + private Messenger mMessenger; + private Calendar mExpiryCal; + + private DatePicker mDatePicker; + + /** + * Creates new instance of this dialog fragment + */ + public static EditSubkeyExpiryDialogFragment newInstance(Messenger messenger, + Long creationDate, Long expiryDate) { + EditSubkeyExpiryDialogFragment frag = new EditSubkeyExpiryDialogFragment(); + Bundle args = new Bundle(); + args.putParcelable(ARG_MESSENGER, messenger); + args.putSerializable(ARG_CREATION_DATE, creationDate); + args.putSerializable(ARG_EXPIRY_DATE, expiryDate); + + frag.setArguments(args); + + return frag; + } + + /** + * Creates dialog + */ + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final Activity activity = getActivity(); + mMessenger = getArguments().getParcelable(ARG_MESSENGER); + Date creationDate = new Date(getArguments().getLong(ARG_CREATION_DATE) * 1000); + Date expiryDate = new Date(getArguments().getLong(ARG_EXPIRY_DATE) * 1000); + + Calendar creationCal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + creationCal.setTime(creationDate); + mExpiryCal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + mExpiryCal.setTime(expiryDate); + + Log.d(Constants.TAG, "onCreateDialog"); + + // Explicitly not using DatePickerDialog here! + // DatePickerDialog is difficult to customize and has many problems (see old git versions) + CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(activity); + + alert.setTitle(R.string.expiry_date_dialog_title); + + LayoutInflater inflater = activity.getLayoutInflater(); + View view = inflater.inflate(R.layout.edit_subkey_expiry_dialog, null); + alert.setView(view); + + mDatePicker = (DatePicker) view.findViewById(R.id.edit_subkey_expiry_date_picker); + + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) { + // will crash with IllegalArgumentException if we set a min date + // that is not before expiry + if (creationCal.before(mExpiryCal)) { + mDatePicker.setMinDate(creationCal.getTime().getTime() + + DateUtils.DAY_IN_MILLIS); + } else { + // when creation date isn't available + mDatePicker.setMinDate(mExpiryCal.getTime().getTime() + + DateUtils.DAY_IN_MILLIS); + } + } + + alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) { + dismiss(); + + Calendar selectedCal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + //noinspection ResourceType + selectedCal.set(mDatePicker.getYear(), mDatePicker.getMonth(), mDatePicker.getDayOfMonth()); + + if (mExpiryCal != null) { + long numDays = (selectedCal.getTimeInMillis() / 86400000) + - (mExpiryCal.getTimeInMillis() / 86400000); + if (numDays > 0) { + Bundle data = new Bundle(); + data.putSerializable(MESSAGE_DATA_EXPIRY_DATE, selectedCal.getTime().getTime() / 1000); + sendMessageToHandler(MESSAGE_NEW_EXPIRY_DATE, data); + } + } else { + Bundle data = new Bundle(); + data.putSerializable(MESSAGE_DATA_EXPIRY_DATE, selectedCal.getTime().getTime() / 1000); + sendMessageToHandler(MESSAGE_NEW_EXPIRY_DATE, data); + } + } + }); + + alert.setNeutralButton(R.string.btn_no_date, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) { + dismiss(); + + Bundle data = new Bundle(); + data.putSerializable(MESSAGE_DATA_EXPIRY_DATE, null); + sendMessageToHandler(MESSAGE_NEW_EXPIRY_DATE, data); + } + }); + + alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) { + dismiss(); + } + }); + + return alert.show(); + } + + @Override + public void onCancel(DialogInterface dialog) { + super.onCancel(dialog); + + dismiss(); + sendMessageToHandler(MESSAGE_CANCEL, null); + } + + /** + * Send message back to handler which is initialized in a activity + * + * @param what Message integer you want to send + */ + private void sendMessageToHandler(Integer what, Bundle data) { + Message msg = Message.obtain(); + msg.what = what; + if (data != null) { + msg.setData(data); + } + + try { + mMessenger.send(msg); + } catch (RemoteException e) { + Log.w(Constants.TAG, "Exception sending message, Is handler present?", e); + } catch (NullPointerException e) { + Log.w(Constants.TAG, "Messenger is null!", e); + } + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditUserIdDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditUserIdDialogFragment.java index 5eba3a463..70a3b8fd0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditUserIdDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditUserIdDialogFragment.java @@ -32,6 +32,9 @@ import org.sufficientlysecure.keychain.util.Log; public class EditUserIdDialogFragment extends DialogFragment { private static final String ARG_MESSENGER = "messenger"; + private static final String ARG_IS_REVOKED = "is_revoked"; + private static final String ARG_IS_REVOKED_PENDING = "is_revoked_pending"; + public static final int MESSAGE_CHANGE_PRIMARY_USER_ID = 1; public static final int MESSAGE_REVOKE = 2; @@ -40,10 +43,13 @@ public class EditUserIdDialogFragment extends DialogFragment { /** * Creates new instance of this dialog fragment */ - public static EditUserIdDialogFragment newInstance(Messenger messenger) { + public static EditUserIdDialogFragment newInstance(Messenger messenger, boolean isRevoked, + boolean isRevokedPending) { EditUserIdDialogFragment frag = new EditUserIdDialogFragment(); Bundle args = new Bundle(); args.putParcelable(ARG_MESSENGER, messenger); + args.putBoolean(ARG_IS_REVOKED, isRevoked); + args.putBoolean(ARG_IS_REVOKED_PENDING, isRevokedPending); frag.setArguments(args); @@ -56,27 +62,49 @@ public class EditUserIdDialogFragment extends DialogFragment { @Override public Dialog onCreateDialog(Bundle savedInstanceState) { mMessenger = getArguments().getParcelable(ARG_MESSENGER); + boolean isRevoked = getArguments().getBoolean(ARG_IS_REVOKED); + boolean isRevokedPending = getArguments().getBoolean(ARG_IS_REVOKED_PENDING); CustomAlertDialogBuilder builder = new CustomAlertDialogBuilder(getActivity()); - CharSequence[] array = getResources().getStringArray(R.array.edit_key_edit_user_id); - builder.setTitle(R.string.edit_key_edit_user_id_title); - builder.setItems(array, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - switch (which) { - case 0: - sendMessageToHandler(MESSAGE_CHANGE_PRIMARY_USER_ID, null); - break; - case 1: - sendMessageToHandler(MESSAGE_REVOKE, null); - break; - default: - break; + if (isRevokedPending) { + CharSequence[] array = getResources().getStringArray(R.array.edit_key_edit_user_id_revert_revocation); + + builder.setItems(array, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + switch (which) { + case 0: + sendMessageToHandler(MESSAGE_REVOKE, null); + break; + default: + break; + } } - } - }); + }); + } else if (isRevoked) { + builder.setMessage(R.string.edit_key_edit_user_id_revoked); + } else { + CharSequence[] array = getResources().getStringArray(R.array.edit_key_edit_user_id); + + builder.setItems(array, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + switch (which) { + case 0: + sendMessageToHandler(MESSAGE_CHANGE_PRIMARY_USER_ID, null); + break; + case 1: + sendMessageToHandler(MESSAGE_REVOKE, null); + break; + default: + break; + } + } + }); + } + builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java index 448787ee2..18f134594 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java @@ -18,18 +18,15 @@ package org.sufficientlysecure.keychain.ui.dialog; import android.app.Activity; -import android.app.AlertDialog; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; -import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.os.Message; import android.os.Messenger; import android.os.RemoteException; -import android.provider.OpenableColumns; import android.support.v4.app.DialogFragment; import android.view.LayoutInflater; import android.view.View; @@ -42,7 +39,13 @@ import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.helper.FileHelper; import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.Notify; +import java.io.File; + +/** + * This is a file chooser dialog no longer used with KitKat + */ public class FileDialogFragment extends DialogFragment { private static final String ARG_MESSENGER = "messenger"; private static final String ARG_TITLE = "title"; @@ -52,8 +55,7 @@ public class FileDialogFragment extends DialogFragment { public static final int MESSAGE_OKAY = 1; - public static final String MESSAGE_DATA_URI = "uri"; - public static final String MESSAGE_DATA_FILENAME = "filename"; + public static final String MESSAGE_DATA_FILE = "file"; public static final String MESSAGE_DATA_CHECKED = "checked"; private Messenger mMessenger; @@ -63,8 +65,7 @@ public class FileDialogFragment extends DialogFragment { private CheckBox mCheckBox; private TextView mMessageTextView; - private String mOutputFilename; - private Uri mOutputUri; + private File mFile; private static final int REQUEST_CODE = 0x00007004; @@ -72,14 +73,14 @@ public class FileDialogFragment extends DialogFragment { * Creates new instance of this file dialog fragment */ public static FileDialogFragment newInstance(Messenger messenger, String title, String message, - String defaultFile, String checkboxText) { + File defaultFile, String checkboxText) { FileDialogFragment frag = new FileDialogFragment(); Bundle args = new Bundle(); args.putParcelable(ARG_MESSENGER, messenger); args.putString(ARG_TITLE, title); args.putString(ARG_MESSAGE, message); - args.putString(ARG_DEFAULT_FILE, defaultFile); + args.putString(ARG_DEFAULT_FILE, defaultFile.getAbsolutePath()); args.putString(ARG_CHECKBOX_TEXT, checkboxText); frag.setArguments(args); @@ -98,7 +99,11 @@ public class FileDialogFragment extends DialogFragment { String title = getArguments().getString(ARG_TITLE); String message = getArguments().getString(ARG_MESSAGE); - mOutputFilename = getArguments().getString(ARG_DEFAULT_FILE); + mFile = new File(getArguments().getString(ARG_DEFAULT_FILE)); + if (!mFile.isAbsolute()) { + // We use OK dir by default + mFile = new File(Constants.Path.APP_DIR.getAbsolutePath(), mFile.getName()); + } String checkboxText = getArguments().getString(ARG_CHECKBOX_TEXT); LayoutInflater inflater = (LayoutInflater) activity @@ -112,18 +117,14 @@ public class FileDialogFragment extends DialogFragment { mMessageTextView.setText(message); mFilename = (EditText) view.findViewById(R.id.input); - mFilename.setText(mOutputFilename); + mFilename.setText(mFile.getName()); mBrowse = (ImageButton) view.findViewById(R.id.btn_browse); mBrowse.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { // only .asc or .gpg files // setting it to text/plain prevents Cynaogenmod's file manager from selecting asc // or gpg types! - if (Constants.KITKAT) { - FileHelper.saveDocument(FileDialogFragment.this, mOutputUri, "*/*", REQUEST_CODE); - } else { - FileHelper.openFile(FileDialogFragment.this, mOutputFilename, "*/*", REQUEST_CODE); - } + FileHelper.openFile(FileDialogFragment.this, Uri.fromFile(mFile), "*/*", REQUEST_CODE); } }); @@ -146,19 +147,23 @@ public class FileDialogFragment extends DialogFragment { dismiss(); String currentFilename = mFilename.getText().toString(); - if (mOutputFilename == null || !mOutputFilename.equals(currentFilename)) { - mOutputUri = null; - mOutputFilename = mFilename.getText().toString(); + if (currentFilename == null || currentFilename.isEmpty()) { + // No file is like pressing cancel, UI: maybe disable positive button in this case? + return; + } + + if (mFile == null || currentFilename.startsWith("/")) { + mFile = new File(currentFilename); + } else if (!mFile.getName().equals(currentFilename)) { + // We update our File object if user changed name! + mFile = new File(mFile.getParentFile(), currentFilename); } boolean checked = mCheckBox.isEnabled() && mCheckBox.isChecked(); // return resulting data back to activity Bundle data = new Bundle(); - if (mOutputUri != null) { - data.putParcelable(MESSAGE_DATA_URI, mOutputUri); - } - data.putString(MESSAGE_DATA_FILENAME, mFilename.getText().toString()); + data.putString(MESSAGE_DATA_FILE, mFile.getAbsolutePath()); data.putBoolean(MESSAGE_DATA_CHECKED, checked); sendMessageToHandler(MESSAGE_OKAY, data); @@ -175,44 +180,17 @@ public class FileDialogFragment extends DialogFragment { return alert.show(); } - /** - * Updates filename in dialog, normally called in onActivityResult in activity using the - * FileDialog - */ - private void setFilename(String filename) { - AlertDialog dialog = (AlertDialog) getDialog(); - EditText filenameEditText = (EditText) dialog.findViewById(R.id.input); - - if (filenameEditText != null) { - filenameEditText.setText(filename); - } - } - @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode & 0xFFFF) { case REQUEST_CODE: { if (resultCode == Activity.RESULT_OK && data != null) { - if (Constants.KITKAT) { - mOutputUri = data.getData(); - Cursor cursor = getActivity().getContentResolver().query(mOutputUri, new String[]{OpenableColumns.DISPLAY_NAME}, null, null, null); - if (cursor != null) { - if (cursor.moveToNext()) { - mOutputFilename = cursor.getString(0); - mFilename.setText(mOutputFilename); - } - cursor.close(); - } + File file = new File(data.getData().getPath()); + if (file.getParentFile().exists()) { + mFile = file; + mFilename.setText(mFile.getName()); } else { - try { - String path = data.getData().getPath(); - Log.d(Constants.TAG, "path=" + path); - - // set filename used in export/import dialogs - setFilename(path); - } catch (NullPointerException e) { - Log.e(Constants.TAG, "Nullpointer while retrieving path!", e); - } + Notify.showNotify(getActivity(), R.string.no_file_selected, Notify.Style.ERROR); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SetPassphraseDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SetPassphraseDialogFragment.java index 1386ed098..5e2bec0e9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SetPassphraseDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SetPassphraseDialogFragment.java @@ -31,7 +31,6 @@ import android.text.TextUtils; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; -import android.view.WindowManager.LayoutParams; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.widget.Button; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java new file mode 100644 index 000000000..7e762fe77 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java @@ -0,0 +1,271 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.ui.widget; + +import android.app.Activity; +import android.content.Context; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.Rect; +import android.os.Bundle; +import android.support.v4.app.FragmentActivity; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.InputMethodManager; +import android.widget.ImageView; +import android.widget.TextView; + +import com.tokenautocomplete.FilteredArrayAdapter; +import com.tokenautocomplete.TokenCompleteTextView; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.helper.ContactHelper; +import org.sufficientlysecure.keychain.pgp.KeyRing; +import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; +import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; +import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; +import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.util.Log; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Locale; + +public class EncryptKeyCompletionView extends TokenCompleteTextView { + public EncryptKeyCompletionView(Context context) { + super(context); + initView(); + } + + public EncryptKeyCompletionView(Context context, AttributeSet attrs) { + super(context, attrs); + initView(); + } + + public EncryptKeyCompletionView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + initView(); + } + + private void initView() { + swapCursor(null); + setPrefix(getContext().getString(R.string.label_to)); + allowDuplicates(false); + } + + @Override + protected View getViewForObject(Object object) { + if (object instanceof EncryptionKey) { + LayoutInflater l = (LayoutInflater) getContext().getSystemService(Activity.LAYOUT_INFLATER_SERVICE); + View view = l.inflate(R.layout.recipient_box_entry, null); + ((TextView) view.findViewById(android.R.id.text1)).setText(((EncryptionKey) object).getPrimary()); + setImageByKey((ImageView) view.findViewById(android.R.id.icon), (EncryptionKey) object); + return view; + } + return null; + } + + private void setImageByKey(ImageView view, EncryptionKey key) { + Bitmap photo = ContactHelper.photoFromFingerprint(getContext().getContentResolver(), key.getFingerprint()); + + if (photo != null) { + view.setImageBitmap(photo); + } else { + view.setImageResource(R.drawable.ic_generic_man); + } + } + + @Override + protected Object defaultObject(String completionText) { + // TODO: We could try to automagically download the key if it's unknown but a key id + /*if (completionText.startsWith("0x")) { + + }*/ + return null; + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + if (getContext() instanceof FragmentActivity) { + ((FragmentActivity) getContext()).getSupportLoaderManager().initLoader(0, null, new LoaderManager.LoaderCallbacks() { + @Override + public Loader onCreateLoader(int id, Bundle args) { + return new CursorLoader(getContext(), KeychainContract.KeyRings.buildUnifiedKeyRingsUri(), + new String[]{KeychainContract.KeyRings.HAS_ENCRYPT, KeychainContract.KeyRings.KEY_ID, KeychainContract.KeyRings.USER_ID, KeychainContract.KeyRings.FINGERPRINT}, + null, null, null); + } + + @Override + public void onLoadFinished(Loader loader, Cursor data) { + swapCursor(data); + } + + @Override + public void onLoaderReset(Loader loader) { + swapCursor(null); + } + }); + } + } + + @Override + public void onFocusChanged(boolean hasFocus, int direction, Rect previous) { + super.onFocusChanged(hasFocus, direction, previous); + if (hasFocus) { + ((InputMethodManager)getContext().getSystemService(Context.INPUT_METHOD_SERVICE)) + .showSoftInput(this, InputMethodManager.SHOW_IMPLICIT); + } + } + + public void swapCursor(Cursor cursor) { + if (cursor == null) { + setAdapter(new EncryptKeyAdapter(Collections.emptyList())); + return; + } + ArrayList keys = new ArrayList(); + while (cursor.moveToNext()) { + try { + if (cursor.getInt(cursor.getColumnIndexOrThrow(KeychainContract.KeyRings.HAS_ENCRYPT)) != 0) { + EncryptionKey key = new EncryptionKey(cursor); + keys.add(key); + } + } catch (Exception e) { + Log.w(Constants.TAG, e); + return; + } + } + setAdapter(new EncryptKeyAdapter(keys)); + } + + public class EncryptionKey { + private String mUserIdFull; + private String[] mUserId; + private long mKeyId; + private String mFingerprint; + + public EncryptionKey(String userId, long keyId, String fingerprint) { + this.mUserId = KeyRing.splitUserId(userId); + this.mUserIdFull = userId; + this.mKeyId = keyId; + this.mFingerprint = fingerprint; + } + + public EncryptionKey(Cursor cursor) { + this(cursor.getString(cursor.getColumnIndexOrThrow(KeychainContract.KeyRings.USER_ID)), + cursor.getLong(cursor.getColumnIndexOrThrow(KeychainContract.KeyRings.KEY_ID)), + PgpKeyHelper.convertFingerprintToHex( + cursor.getBlob(cursor.getColumnIndexOrThrow(KeychainContract.KeyRings.FINGERPRINT)))); + + } + + public EncryptionKey(CachedPublicKeyRing ring) throws PgpGeneralException { + this(ring.getPrimaryUserId(), ring.extractOrGetMasterKeyId(), + PgpKeyHelper.convertFingerprintToHex(ring.getFingerprint())); + } + + public String getUserId() { + return mUserIdFull; + } + + public String getFingerprint() { + return mFingerprint; + } + + public String getPrimary() { + if (mUserId[0] != null && mUserId[2] != null) { + return mUserId[0] + " (" + mUserId[2] + ")"; + } else if (mUserId[0] != null) { + return mUserId[0]; + } else { + return mUserId[1]; + } + } + + public String getSecondary() { + if (mUserId[0] != null) { + return mUserId[1]; + } else { + return getKeyIdHex(); + } + } + + public String getTertiary() { + if (mUserId[0] != null) { + return getKeyIdHex(); + } else { + return null; + } + } + + public long getKeyId() { + return mKeyId; + } + + public String getKeyIdHex() { + return PgpKeyHelper.convertKeyIdToHex(mKeyId); + } + + public String getKeyIdHexShort() { + return PgpKeyHelper.convertKeyIdToHexShort(mKeyId); + } + + @Override + public String toString() { + return Long.toString(mKeyId); + } + } + + private class EncryptKeyAdapter extends FilteredArrayAdapter { + + public EncryptKeyAdapter(List objs) { + super(EncryptKeyCompletionView.this.getContext(), 0, 0, objs); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + LayoutInflater l = (LayoutInflater) getContext().getSystemService(Activity.LAYOUT_INFLATER_SERVICE); + View view; + if (convertView != null) { + view = convertView; + } else { + view = l.inflate(R.layout.recipient_selection_list_entry, null); + } + ((TextView) view.findViewById(android.R.id.title)).setText(getItem(position).getPrimary()); + ((TextView) view.findViewById(android.R.id.text1)).setText(getItem(position).getSecondary()); + ((TextView) view.findViewById(android.R.id.text2)).setText(getItem(position).getTertiary()); + setImageByKey((ImageView) view.findViewById(android.R.id.icon), getItem(position)); + return view; + } + + @Override + protected boolean keepObject(EncryptionKey obj, String mask) { + String m = mask.toLowerCase(Locale.ENGLISH); + return obj.getUserId().toLowerCase(Locale.ENGLISH).contains(m) || + obj.getKeyIdHex().contains(m) || + obj.getKeyIdHexShort().startsWith(m); + } + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/FoldableLinearLayout.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/FoldableLinearLayout.java index a29c17d37..31e01a7fb 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/FoldableLinearLayout.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/FoldableLinearLayout.java @@ -24,9 +24,9 @@ import android.view.LayoutInflater; import android.view.View; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; +import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.TextView; -import android.widget.ImageButton; import org.sufficientlysecure.keychain.R; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyEditor.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyEditor.java deleted file mode 100644 index c23b4c3ff..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyEditor.java +++ /dev/null @@ -1,380 +0,0 @@ -/* - * Copyright (C) 2014 Dominik Schürmann - * Copyright (C) 2010-2014 Thialfihar - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.ui.widget; - -import android.annotation.TargetApi; -import android.app.DatePickerDialog; -import android.app.Dialog; -import android.content.Context; -import android.content.DialogInterface; -import android.text.format.DateUtils; -import android.util.AttributeSet; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.CheckBox; -import android.widget.CompoundButton; -import android.widget.DatePicker; -import android.widget.ImageButton; -import android.widget.LinearLayout; -import android.widget.TableLayout; -import android.widget.TableRow; -import android.widget.TextView; -import android.widget.Button; - -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; -import org.sufficientlysecure.keychain.pgp.UncachedSecretKey; - -import java.text.DateFormat; -import java.util.Calendar; -import java.util.Date; -import java.util.GregorianCalendar; -import java.util.TimeZone; - -public class KeyEditor extends LinearLayout implements Editor, OnClickListener { - private UncachedSecretKey mKey; - - private EditorListener mEditorListener = null; - - private boolean mIsMasterKey; - ImageButton mDeleteButton; - TextView mAlgorithm; - TextView mKeyId; - TextView mCreationDate; - Button mExpiryDateButton; - Calendar mCreatedDate; - Calendar mExpiryDate; - Calendar mOriginalExpiryDate = null; - CheckBox mChkCertify; - CheckBox mChkSign; - CheckBox mChkEncrypt; - CheckBox mChkAuthenticate; - int mUsage; - int mOriginalUsage; - boolean mIsNewKey; - - private CheckBox.OnCheckedChangeListener mCheckChanged = new CheckBox.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - if (mEditorListener != null) { - mEditorListener.onEdited(); - } - } - }; - - - private int mDatePickerResultCount = 0; - private DatePickerDialog.OnDateSetListener mExpiryDateSetListener = - new DatePickerDialog.OnDateSetListener() { - public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) { - // Note: Ignore results after the first one - android sends multiples. - if (mDatePickerResultCount++ == 0) { - GregorianCalendar date = new GregorianCalendar(TimeZone.getTimeZone("UTC")); - date.set(year, monthOfYear, dayOfMonth); - if (mOriginalExpiryDate != null) { - long numDays = (date.getTimeInMillis() / 86400000) - - (mOriginalExpiryDate.getTimeInMillis() / 86400000); - if (numDays == 0) { - setExpiryDate(mOriginalExpiryDate); - } else { - setExpiryDate(date); - } - } else { - setExpiryDate(date); - } - if (mEditorListener != null) { - mEditorListener.onEdited(); - } - } - } - }; - - public KeyEditor(Context context) { - super(context); - } - - public KeyEditor(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - protected void onFinishInflate() { - setDrawingCacheEnabled(true); - setAlwaysDrawnWithCacheEnabled(true); - - mAlgorithm = (TextView) findViewById(R.id.algorithm); - mKeyId = (TextView) findViewById(R.id.keyId); - mCreationDate = (TextView) findViewById(R.id.creation); - mExpiryDateButton = (Button) findViewById(R.id.expiry); - - mDeleteButton = (ImageButton) findViewById(R.id.delete); - mDeleteButton.setOnClickListener(this); - mChkCertify = (CheckBox) findViewById(R.id.chkCertify); - mChkCertify.setOnCheckedChangeListener(mCheckChanged); - mChkSign = (CheckBox) findViewById(R.id.chkSign); - mChkSign.setOnCheckedChangeListener(mCheckChanged); - mChkEncrypt = (CheckBox) findViewById(R.id.chkEncrypt); - mChkEncrypt.setOnCheckedChangeListener(mCheckChanged); - mChkAuthenticate = (CheckBox) findViewById(R.id.chkAuthenticate); - mChkAuthenticate.setOnCheckedChangeListener(mCheckChanged); - - setExpiryDate(null); - - mExpiryDateButton.setOnClickListener(new OnClickListener() { - @TargetApi(11) - public void onClick(View v) { - Calendar expiryDate = mExpiryDate; - if (expiryDate == null) { - expiryDate = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - } - /* - * Using custom DatePickerDialog which overrides the setTitle because - * the DatePickerDialog title is buggy (unix warparound bug). - * See: https://code.google.com/p/android/issues/detail?id=49066 - */ - DatePickerDialog dialog = new ExpiryDatePickerDialog(getContext(), - mExpiryDateSetListener, expiryDate.get(Calendar.YEAR), expiryDate.get(Calendar.MONTH), - expiryDate.get(Calendar.DAY_OF_MONTH)); - mDatePickerResultCount = 0; - dialog.setCancelable(true); - dialog.setButton(Dialog.BUTTON_NEGATIVE, - getContext().getString(R.string.btn_no_date), - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - // Note: Ignore results after the first one - android sends multiples. - if (mDatePickerResultCount++ == 0) { - setExpiryDate(null); - if (mEditorListener != null) { - mEditorListener.onEdited(); - } - } - } - }); - - // setCalendarViewShown() is supported from API 11 onwards. - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) { - // Hide calendarView in tablets because of the unix warparound bug. - dialog.getDatePicker().setCalendarViewShown(false); - } - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) { - - // will crash with IllegalArgumentException if we set a min date - // that is not before expiry - if (mCreatedDate != null && mCreatedDate.before(expiryDate)) { - dialog.getDatePicker() - .setMinDate( - mCreatedDate.getTime().getTime() + DateUtils.DAY_IN_MILLIS); - } else { - // When created date isn't available - dialog.getDatePicker().setMinDate(expiryDate.getTime().getTime() + DateUtils.DAY_IN_MILLIS); - } - } - - dialog.show(); - } - }); - - super.onFinishInflate(); - } - - public void setCanBeEdited(boolean canBeEdited) { - if (!canBeEdited) { - mDeleteButton.setVisibility(View.INVISIBLE); - mExpiryDateButton.setEnabled(false); - mChkSign.setEnabled(false); //certify is always disabled - mChkEncrypt.setEnabled(false); - mChkAuthenticate.setEnabled(false); - } - } - - public void setValue(UncachedSecretKey key, boolean isMasterKey, int usage, boolean isNewKey) { - mKey = key; - - mIsMasterKey = isMasterKey; - if (mIsMasterKey) { - mDeleteButton.setVisibility(View.INVISIBLE); - } - - mAlgorithm.setText(PgpKeyHelper.getAlgorithmInfo(getContext(), key.getAlgorithm())); - String keyIdStr = PgpKeyHelper.convertKeyIdToHex(key.getKeyId()); - mKeyId.setText(keyIdStr); - - boolean isElGamalKey = (key.isElGamalEncrypt()); - boolean isDSAKey = (key.isDSA()); - if (isElGamalKey) { - mChkSign.setVisibility(View.INVISIBLE); - TableLayout table = (TableLayout) findViewById(R.id.table_keylayout); - TableRow row = (TableRow) findViewById(R.id.row_sign); - table.removeView(row); - } - if (isDSAKey) { - mChkEncrypt.setVisibility(View.INVISIBLE); - TableLayout table = (TableLayout) findViewById(R.id.table_keylayout); - TableRow row = (TableRow) findViewById(R.id.row_encrypt); - table.removeView(row); - } - if (!mIsMasterKey) { - mChkCertify.setVisibility(View.INVISIBLE); - TableLayout table = (TableLayout) findViewById(R.id.table_keylayout); - TableRow row = (TableRow) findViewById(R.id.row_certify); - table.removeView(row); - } else { - TextView mLabelUsage2 = (TextView) findViewById(R.id.label_usage2); - mLabelUsage2.setVisibility(View.INVISIBLE); - } - - mIsNewKey = isNewKey; - if (isNewKey) { - mUsage = usage; - mChkCertify.setChecked( - (usage & UncachedSecretKey.CERTIFY_OTHER) == UncachedSecretKey.CERTIFY_OTHER); - mChkSign.setChecked( - (usage & UncachedSecretKey.SIGN_DATA) == UncachedSecretKey.SIGN_DATA); - mChkEncrypt.setChecked( - ((usage & UncachedSecretKey.ENCRYPT_COMMS) == UncachedSecretKey.ENCRYPT_COMMS) || - ((usage & UncachedSecretKey.ENCRYPT_STORAGE) == UncachedSecretKey.ENCRYPT_STORAGE)); - mChkAuthenticate.setChecked( - (usage & UncachedSecretKey.AUTHENTICATION) == UncachedSecretKey.AUTHENTICATION); - } else { - mUsage = key.getKeyUsage(); - mOriginalUsage = mUsage; - if (key.isMasterKey()) { - mChkCertify.setChecked(key.canCertify()); - } - mChkSign.setChecked(key.canSign()); - mChkEncrypt.setChecked(key.canEncrypt()); - mChkAuthenticate.setChecked(key.canAuthenticate()); - } - - { - Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - cal.setTime(key.getCreationTime()); - setCreatedDate(cal); - } - - Date expiryDate = key.getExpiryTime(); - if (expiryDate == null) { - setExpiryDate(null); - } else { - Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - cal.setTime(expiryDate); - setExpiryDate(cal); - mOriginalExpiryDate = cal; - } - - } - - public UncachedSecretKey getValue() { - return mKey; - } - - public void onClick(View v) { - final ViewGroup parent = (ViewGroup) getParent(); - if (v == mDeleteButton) { - parent.removeView(this); - if (mEditorListener != null) { - mEditorListener.onDeleted(this, mIsNewKey); - } - } - } - - public void setEditorListener(EditorListener listener) { - mEditorListener = listener; - } - - private void setCreatedDate(Calendar date) { - mCreatedDate = date; - if (date == null) { - mCreationDate.setText(getContext().getString(R.string.none)); - } else { - mCreationDate.setText(DateFormat.getDateInstance().format(date.getTime())); - } - } - - private void setExpiryDate(Calendar date) { - mExpiryDate = date; - if (date == null) { - mExpiryDateButton.setText(getContext().getString(R.string.none)); - } else { - mExpiryDateButton.setText(DateFormat.getDateInstance().format(date.getTime())); - } - } - - public Calendar getExpiryDate() { - return mExpiryDate; - } - - public int getUsage() { - mUsage = (mUsage & ~UncachedSecretKey.CERTIFY_OTHER) | - (mChkCertify.isChecked() ? UncachedSecretKey.CERTIFY_OTHER : 0); - mUsage = (mUsage & ~UncachedSecretKey.SIGN_DATA) | - (mChkSign.isChecked() ? UncachedSecretKey.SIGN_DATA : 0); - mUsage = (mUsage & ~UncachedSecretKey.ENCRYPT_COMMS) | - (mChkEncrypt.isChecked() ? UncachedSecretKey.ENCRYPT_COMMS : 0); - mUsage = (mUsage & ~UncachedSecretKey.ENCRYPT_STORAGE) | - (mChkEncrypt.isChecked() ? UncachedSecretKey.ENCRYPT_STORAGE : 0); - mUsage = (mUsage & ~UncachedSecretKey.AUTHENTICATION) | - (mChkAuthenticate.isChecked() ? UncachedSecretKey.AUTHENTICATION : 0); - - return mUsage; - } - - public boolean needsSaving() { - if (mIsNewKey) { - return true; - } - - boolean retval = (getUsage() != mOriginalUsage); - - boolean dateChanged; - boolean mOEDNull = (mOriginalExpiryDate == null); - boolean mEDNull = (mExpiryDate == null); - if (mOEDNull != mEDNull) { - dateChanged = true; - } else { - if (mOEDNull) { - //both null, no change - dateChanged = false; - } else { - dateChanged = ((mExpiryDate.compareTo(mOriginalExpiryDate)) != 0); - } - } - retval |= dateChanged; - - return retval; - } - - public boolean getIsNewKey() { - return mIsNewKey; - } -} - -class ExpiryDatePickerDialog extends DatePickerDialog { - - public ExpiryDatePickerDialog(Context context, OnDateSetListener callBack, - int year, int monthOfYear, int dayOfMonth) { - super(context, callBack, year, monthOfYear, dayOfMonth); - } - - //Set permanent title. - public void setTitle(CharSequence title) { - super.setTitle(getContext().getString(R.string.expiry_date_dialog_title)); - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/NoSwipeWrapContentViewPager.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/NoSwipeWrapContentViewPager.java new file mode 100644 index 000000000..a48d2a026 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/NoSwipeWrapContentViewPager.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.ui.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; + +public class NoSwipeWrapContentViewPager extends android.support.v4.view.ViewPager { + public NoSwipeWrapContentViewPager(Context context) { + super(context); + } + + public NoSwipeWrapContentViewPager(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + + int height; + View child = getChildAt(getCurrentItem()); + if (child != null) { + child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); + height = child.getMeasuredHeight(); + } else { + height = 0; + } + + heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); + + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent arg0) { + // Never allow swiping to switch between pages + return false; + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + // Never allow swiping to switch between pages + return false; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SectionView.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SectionView.java deleted file mode 100644 index cd5671801..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SectionView.java +++ /dev/null @@ -1,428 +0,0 @@ -/* - * Copyright (C) 2010-2014 Thialfihar - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.ui.widget; - -import android.app.ProgressDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.os.Bundle; -import android.os.Message; -import android.os.Messenger; -import android.support.v7.app.ActionBarActivity; -import android.util.AttributeSet; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.LinearLayout; -import android.widget.TextView; -import android.widget.ImageButton; - -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.pgp.PgpConversionHelper; -import org.sufficientlysecure.keychain.pgp.UncachedSecretKey; -import org.sufficientlysecure.keychain.service.KeychainIntentService; -import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; -import org.sufficientlysecure.keychain.service.PassphraseCacheService; -import org.sufficientlysecure.keychain.ui.dialog.CreateKeyDialogFragment; -import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment; -import org.sufficientlysecure.keychain.ui.widget.Editor.EditorListener; -import org.sufficientlysecure.keychain.util.Choice; - -import java.util.ArrayList; -import java.util.List; -import java.util.Vector; - -public class SectionView extends LinearLayout implements OnClickListener, EditorListener, Editor { - private LayoutInflater mInflater; - private ImageButton mPlusButton; - private ViewGroup mEditors; - private TextView mTitle; - private int mType = 0; - private EditorListener mEditorListener = null; - - private Choice mNewKeyAlgorithmChoice; - private int mNewKeySize; - private boolean mOldItemDeleted = false; - private ArrayList mDeletedIDs = new ArrayList(); - private ArrayList mDeletedKeys = new ArrayList(); - private boolean mCanBeEdited = true; - - private ActionBarActivity mActivity; - - private ProgressDialogFragment mGeneratingDialog; - - public static final int TYPE_USER_ID = 1; - public static final int TYPE_KEY = 2; - - public void setEditorListener(EditorListener listener) { - mEditorListener = listener; - } - - public SectionView(Context context) { - super(context); - mActivity = (ActionBarActivity) context; - } - - public SectionView(Context context, AttributeSet attrs) { - super(context, attrs); - mActivity = (ActionBarActivity) context; - } - - public ViewGroup getEditors() { - return mEditors; - } - - public void setType(int type) { - mType = type; - switch (type) { - case TYPE_USER_ID: { - mTitle.setText(R.string.section_user_ids); - break; - } - - case TYPE_KEY: { - mTitle.setText(R.string.section_keys); - break; - } - - default: { - break; - } - } - } - - public void setCanBeEdited(boolean canBeEdited) { - mCanBeEdited = canBeEdited; - if (!mCanBeEdited) { - mPlusButton.setVisibility(View.INVISIBLE); - } - } - - /** - * {@inheritDoc} - */ - @Override - protected void onFinishInflate() { - mInflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); - - setDrawingCacheEnabled(true); - setAlwaysDrawnWithCacheEnabled(true); - - mPlusButton = (ImageButton) findViewById(R.id.plusbutton); - mPlusButton.setOnClickListener(this); - - mEditors = (ViewGroup) findViewById(R.id.editors); - mTitle = (TextView) findViewById(R.id.title); - - updateEditorsVisible(); - super.onFinishInflate(); - } - - /** - * {@inheritDoc} - */ - public void onDeleted(Editor editor, boolean wasNewItem) { - mOldItemDeleted |= !wasNewItem; - if (mOldItemDeleted) { - if (mType == TYPE_USER_ID) { - mDeletedIDs.add(((UserIdEditor) editor).getOriginalID()); - } else if (mType == TYPE_KEY) { - mDeletedKeys.add(((KeyEditor) editor).getValue()); - } - } - this.updateEditorsVisible(); - if (mEditorListener != null) { - mEditorListener.onEdited(); - } - } - - @Override - public void onEdited() { - if (mEditorListener != null) { - mEditorListener.onEdited(); - } - } - - protected void updateEditorsVisible() { - final boolean hasChildren = mEditors.getChildCount() > 0; - mEditors.setVisibility(hasChildren ? View.VISIBLE : View.GONE); - } - - public boolean needsSaving() { - //check each view for needs saving, take account of deleted items - boolean ret = mOldItemDeleted; - for (int i = 0; i < mEditors.getChildCount(); ++i) { - Editor editor = (Editor) mEditors.getChildAt(i); - ret |= editor.needsSaving(); - if (mType == TYPE_USER_ID) { - ret |= ((UserIdEditor) editor).primarySwapped(); - } - } - return ret; - } - - public boolean primaryChanged() { - boolean ret = false; - for (int i = 0; i < mEditors.getChildCount(); ++i) { - Editor editor = (Editor) mEditors.getChildAt(i); - if (mType == TYPE_USER_ID) { - ret |= ((UserIdEditor) editor).primarySwapped(); - } - } - return ret; - } - - public String getOriginalPrimaryID() { - //NB: this will have to change when we change how Primary IDs are chosen, and so we need to be - // careful about where Master key capabilities are stored... multiple primaries and - // revoked ones make this harder than the simple case we are continuing to assume here - for (int i = 0; i < mEditors.getChildCount(); ++i) { - Editor editor = (Editor) mEditors.getChildAt(i); - if (mType == TYPE_USER_ID) { - if (((UserIdEditor) editor).getIsOriginallyMainUserID()) { - return ((UserIdEditor) editor).getOriginalID(); - } - } - } - return null; - } - - public ArrayList getOriginalIDs() { - ArrayList orig = new ArrayList(); - if (mType == TYPE_USER_ID) { - for (int i = 0; i < mEditors.getChildCount(); ++i) { - UserIdEditor editor = (UserIdEditor) mEditors.getChildAt(i); - if (editor.isMainUserId()) { - orig.add(0, editor.getOriginalID()); - } else { - orig.add(editor.getOriginalID()); - } - } - return orig; - } else { - return null; - } - } - - public ArrayList getDeletedIDs() { - return mDeletedIDs; - } - - public ArrayList getDeletedKeys() { - return mDeletedKeys; - } - - public List getNeedsSavingArray() { - ArrayList mList = new ArrayList(); - for (int i = 0; i < mEditors.getChildCount(); ++i) { - Editor editor = (Editor) mEditors.getChildAt(i); - mList.add(editor.needsSaving()); - } - return mList; - } - - public List getNewIDFlags() { - ArrayList mList = new ArrayList(); - for (int i = 0; i < mEditors.getChildCount(); ++i) { - UserIdEditor editor = (UserIdEditor) mEditors.getChildAt(i); - if (editor.isMainUserId()) { - mList.add(0, editor.getIsNewID()); - } else { - mList.add(editor.getIsNewID()); - } - } - return mList; - } - - public List getNewKeysArray() { - ArrayList mList = new ArrayList(); - if (mType == TYPE_KEY) { - for (int i = 0; i < mEditors.getChildCount(); ++i) { - KeyEditor editor = (KeyEditor) mEditors.getChildAt(i); - mList.add(editor.getIsNewKey()); - } - } - return mList; - } - - /** - * {@inheritDoc} - */ - public void onClick(View v) { - if (mCanBeEdited) { - switch (mType) { - case TYPE_USER_ID: { - UserIdEditor view = (UserIdEditor) mInflater.inflate( - R.layout.edit_key_user_id_item, mEditors, false); - view.setEditorListener(this); - view.setValue("", mEditors.getChildCount() == 0, true); - mEditors.addView(view); - if (mEditorListener != null) { - mEditorListener.onEdited(); - } - break; - } - - case TYPE_KEY: { - CreateKeyDialogFragment mCreateKeyDialogFragment = - CreateKeyDialogFragment.newInstance(mEditors.getChildCount()); - mCreateKeyDialogFragment - .setOnAlgorithmSelectedListener( - new CreateKeyDialogFragment.OnAlgorithmSelectedListener() { - @Override - public void onAlgorithmSelected(Choice algorithmChoice, int keySize) { - mNewKeyAlgorithmChoice = algorithmChoice; - mNewKeySize = keySize; - createKey(); - } - }); - mCreateKeyDialogFragment.show(mActivity.getSupportFragmentManager(), "createKeyDialog"); - break; - } - - default: { - break; - } - } - this.updateEditorsVisible(); - } - } - - public void setUserIds(Vector list) { - if (mType != TYPE_USER_ID) { - return; - } - - mEditors.removeAllViews(); - for (String userId : list) { - UserIdEditor view = (UserIdEditor) mInflater.inflate(R.layout.edit_key_user_id_item, - mEditors, false); - view.setEditorListener(this); - view.setValue(userId, mEditors.getChildCount() == 0, false); - view.setCanBeEdited(mCanBeEdited); - mEditors.addView(view); - } - - this.updateEditorsVisible(); - } - - public void setKeys(Vector list, Vector usages, boolean newKeys) { - if (mType != TYPE_KEY) { - return; - } - - mEditors.removeAllViews(); - - // go through all keys and set view based on them - for (int i = 0; i < list.size(); i++) { - KeyEditor view = (KeyEditor) mInflater.inflate(R.layout.edit_key_key_item, mEditors, - false); - view.setEditorListener(this); - boolean isMasterKey = (mEditors.getChildCount() == 0); - view.setValue(list.get(i), isMasterKey, usages.get(i), newKeys); - view.setCanBeEdited(mCanBeEdited); - mEditors.addView(view); - } - - this.updateEditorsVisible(); - } - - private void createKey() { - - // fill values for this action - Boolean isMasterKey; - - String passphrase; - if (mEditors.getChildCount() > 0) { - UncachedSecretKey masterKey = ((KeyEditor) mEditors.getChildAt(0)).getValue(); - passphrase = PassphraseCacheService - .getCachedPassphrase(mActivity, masterKey.getKeyId()); - isMasterKey = false; - } else { - passphrase = ""; - isMasterKey = true; - } - /* - data.putBoolean(KeychainIntentService.GENERATE_KEY_MASTER_KEY, isMasterKey); - data.putString(KeychainIntentService.GENERATE_KEY_SYMMETRIC_PASSPHRASE, passphrase); - data.putInt(KeychainIntentService.GENERATE_KEY_ALGORITHM, mNewKeyAlgorithmChoice.getId()); - data.putInt(KeychainIntentService.GENERATE_KEY_KEY_SIZE, mNewKeySize); - - intent.putExtra(KeychainIntentService.EXTRA_DATA, data); - - // show progress dialog - mGeneratingDialog = ProgressDialogFragment.newInstance( - getResources().getQuantityString(R.plurals.progress_generating, 1), - ProgressDialog.STYLE_SPINNER, - true, - new DialogInterface.OnCancelListener() { - @Override - public void onCancel(DialogInterface dialog) { - mActivity.stopService(intent); - } - }); - - // Message is received after generating is done in KeychainIntentService - KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(mActivity, - mGeneratingDialog) { - public void handleMessage(Message message) { - // handle messages by standard KeychainIntentServiceHandler first - super.handleMessage(message); - - if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { - // get new key from data bundle returned from service - Bundle data = message.getDataAsStringList(); - UncachedSecretKey newKey = PgpConversionHelper - .BytesToPGPSecretKey(data - .getByteArray(KeychainIntentService.RESULT_NEW_KEY)); - addGeneratedKeyToView(newKey); - } - } - }; - - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(saveHandler); - intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger); - - mGeneratingDialog.show(mActivity.getSupportFragmentManager(), "dialog"); - - // start service with intent - mActivity.startService(intent); - */ - - } - - private void addGeneratedKeyToView(UncachedSecretKey newKey) { - // add view with new key - KeyEditor view = (KeyEditor) mInflater.inflate(R.layout.edit_key_key_item, - mEditors, false); - view.setEditorListener(SectionView.this); - int usage = 0; - if (mEditors.getChildCount() == 0) { - usage = UncachedSecretKey.CERTIFY_OTHER; - } - view.setValue(newKey, newKey.isMasterKey(), usage, true); - mEditors.addView(view); - SectionView.this.updateEditorsVisible(); - if (mEditorListener != null) { - mEditorListener.onEdited(); - } - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/UserIdEditor.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/UserIdEditor.java deleted file mode 100644 index f50d2adc6..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/UserIdEditor.java +++ /dev/null @@ -1,267 +0,0 @@ -/* - * Copyright (C) 2014 Dominik Schürmann - * Copyright (C) 2010-2014 Thialfihar - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.ui.widget; - -import android.content.Context; -import android.text.Editable; -import android.text.TextWatcher; -import android.util.AttributeSet; -import android.util.Patterns; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.AutoCompleteTextView; -import android.widget.EditText; -import android.widget.LinearLayout; -import android.widget.RadioButton; -import android.widget.ImageButton; - -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.helper.ContactHelper; -import org.sufficientlysecure.keychain.pgp.KeyRing; - -import java.util.regex.Matcher; - -public class UserIdEditor extends LinearLayout implements Editor, OnClickListener { - private EditorListener mEditorListener = null; - - private ImageButton mDeleteButton; - private RadioButton mIsMainUserId; - private String mOriginalID; - private EditText mName; - private String mOriginalName; - private AutoCompleteTextView mEmail; - private String mOriginalEmail; - private EditText mComment; - private String mOriginalComment; - private boolean mOriginallyMainUserID; - private boolean mIsNewId; - - public void setCanBeEdited(boolean canBeEdited) { - if (!canBeEdited) { - mDeleteButton.setVisibility(View.INVISIBLE); - mName.setEnabled(false); - mIsMainUserId.setEnabled(false); - mEmail.setEnabled(false); - mComment.setEnabled(false); - } - } - - public static class InvalidEmailException extends Exception { - static final long serialVersionUID = 0xf812773345L; - - public InvalidEmailException(String message) { - super(message); - } - } - - public UserIdEditor(Context context) { - super(context); - } - - public UserIdEditor(Context context, AttributeSet attrs) { - super(context, attrs); - } - - TextWatcher mTextWatcher = new TextWatcher() { - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - } - - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } - - @Override - public void afterTextChanged(Editable s) { - if (mEditorListener != null) { - mEditorListener.onEdited(); - } - } - }; - - @Override - protected void onFinishInflate() { - setDrawingCacheEnabled(true); - setAlwaysDrawnWithCacheEnabled(true); - - mDeleteButton = (ImageButton) findViewById(R.id.delete); - mDeleteButton.setOnClickListener(this); - mIsMainUserId = (RadioButton) findViewById(R.id.isMainUserId); - mIsMainUserId.setOnClickListener(this); - - mName = (EditText) findViewById(R.id.name); - mName.addTextChangedListener(mTextWatcher); - mEmail = (AutoCompleteTextView) findViewById(R.id.email); - mComment = (EditText) findViewById(R.id.user_id_item_comment); - mComment.addTextChangedListener(mTextWatcher); - - - mEmail.setThreshold(1); // Start working from first character - mEmail.setAdapter( - new ArrayAdapter - (this.getContext(), android.R.layout.simple_dropdown_item_1line, - ContactHelper.getPossibleUserEmails(getContext()) - )); - mEmail.addTextChangedListener(new TextWatcher(){ - @Override - public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) { } - - @Override - public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) { } - - @Override - public void afterTextChanged(Editable editable) { - String email = editable.toString(); - if (email.length() > 0) { - Matcher emailMatcher = Patterns.EMAIL_ADDRESS.matcher(email); - if (emailMatcher.matches()) { - mEmail.setCompoundDrawablesWithIntrinsicBounds(0, 0, - R.drawable.uid_mail_ok, 0); - } else { - mEmail.setCompoundDrawablesWithIntrinsicBounds(0, 0, - R.drawable.uid_mail_bad, 0); - } - } else { - // remove drawable if email is empty - mEmail.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); - } - if (mEditorListener != null) { - mEditorListener.onEdited(); - } - } - }); - - super.onFinishInflate(); - } - - public void setValue(String userId, boolean isMainID, boolean isNewId) { - - mName.setText(""); - mOriginalName = ""; - mComment.setText(""); - mOriginalComment = ""; - mEmail.setText(""); - mOriginalEmail = ""; - mIsNewId = isNewId; - mOriginalID = userId; - - String[] result = KeyRing.splitUserId(userId); - if (result[0] != null) { - mName.setText(result[0]); - mOriginalName = result[0]; - } - if (result[1] != null) { - mEmail.setText(result[1]); - mOriginalEmail = result[1]; - } - if (result[2] != null) { - mComment.setText(result[2]); - mOriginalComment = result[2]; - } - - mOriginallyMainUserID = isMainID; - setIsMainUserId(isMainID); - } - - public String getValue() { - String name = ("" + mName.getText()).trim(); - String email = ("" + mEmail.getText()).trim(); - String comment = ("" + mComment.getText()).trim(); - - String userId = name; - if (comment.length() > 0) { - userId += " (" + comment + ")"; - } - if (email.length() > 0) { - userId += " <" + email + ">"; - } - - if (userId.equals("")) { - // ok, empty one... - return userId; - } - //TODO: check gpg accepts an entirely empty ID packet. specs say this is allowed - return userId; - } - - public void onClick(View v) { - final ViewGroup parent = (ViewGroup) getParent(); - if (v == mDeleteButton) { - boolean wasMainUserId = mIsMainUserId.isChecked(); - parent.removeView(this); - if (mEditorListener != null) { - mEditorListener.onDeleted(this, mIsNewId); - } - if (wasMainUserId && parent.getChildCount() > 0) { - UserIdEditor editor = (UserIdEditor) parent.getChildAt(0); - editor.setIsMainUserId(true); - } - } else if (v == mIsMainUserId) { - for (int i = 0; i < parent.getChildCount(); ++i) { - UserIdEditor editor = (UserIdEditor) parent.getChildAt(i); - if (editor == this) { - editor.setIsMainUserId(true); - } else { - editor.setIsMainUserId(false); - } - } - if (mEditorListener != null) { - mEditorListener.onEdited(); - } - } - } - - public void setIsMainUserId(boolean value) { - mIsMainUserId.setChecked(value); - } - - public boolean isMainUserId() { - return mIsMainUserId.isChecked(); - } - - public void setEditorListener(EditorListener listener) { - mEditorListener = listener; - } - - @Override - public boolean needsSaving() { - boolean retval = false; //(mOriginallyMainUserID != isMainUserId()); - retval |= !(mOriginalName.equals(("" + mName.getText()).trim())); - retval |= !(mOriginalEmail.equals(("" + mEmail.getText()).trim())); - retval |= !(mOriginalComment.equals(("" + mComment.getText()).trim())); - retval |= mIsNewId; - return retval; - } - - public boolean getIsOriginallyMainUserID() { - return mOriginallyMainUserID; - } - - public boolean primarySwapped() { - return (mOriginallyMainUserID != isMainUserId()); - } - - public String getOriginalID() { - return mOriginalID; - } - - public boolean getIsNewID() { return mIsNewId; } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/AlgorithmNames.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/AlgorithmNames.java index 2499f2571..9acc7a73b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/AlgorithmNames.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/AlgorithmNames.java @@ -20,6 +20,7 @@ package org.sufficientlysecure.keychain.util; import android.annotation.SuppressLint; import android.app.Activity; +import org.spongycastle.bcpg.CompressionAlgorithmTags; import org.spongycastle.bcpg.HashAlgorithmTags; import org.spongycastle.openpgp.PGPEncryptedData; import org.sufficientlysecure.keychain.Constants; @@ -57,13 +58,13 @@ public class AlgorithmNames { mHashNames.put(HashAlgorithmTags.SHA384, "SHA-384"); mHashNames.put(HashAlgorithmTags.SHA512, "SHA-512"); - mCompressionNames.put(Constants.choice.compression.none, mActivity.getString(R.string.choice_none) + mCompressionNames.put(CompressionAlgorithmTags.UNCOMPRESSED, mActivity.getString(R.string.choice_none) + " (" + mActivity.getString(R.string.compression_fast) + ")"); - mCompressionNames.put(Constants.choice.compression.zip, + mCompressionNames.put(CompressionAlgorithmTags.ZIP, "ZIP (" + mActivity.getString(R.string.compression_fast) + ")"); - mCompressionNames.put(Constants.choice.compression.zlib, + mCompressionNames.put(CompressionAlgorithmTags.ZLIB, "ZLIB (" + mActivity.getString(R.string.compression_fast) + ")"); - mCompressionNames.put(Constants.choice.compression.bzip2, + mCompressionNames.put(CompressionAlgorithmTags.BZIP2, "BZIP2 (" + mActivity.getString(R.string.compression_very_slow) + ")"); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/DatabaseUtil.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/DatabaseUtil.java new file mode 100644 index 000000000..c18e5cabd --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/DatabaseUtil.java @@ -0,0 +1,36 @@ +package org.sufficientlysecure.keychain.util; + +import android.text.TextUtils; + +/** + * Shamelessly copied from android.database.DatabaseUtils + */ +public class DatabaseUtil { + /** + * Concatenates two SQL WHERE clauses, handling empty or null values. + */ + public static String concatenateWhere(String a, String b) { + if (TextUtils.isEmpty(a)) { + return b; + } + if (TextUtils.isEmpty(b)) { + return a; + } + + return "(" + a + ") AND (" + b + ")"; + } + + /** + * Appends one set of selection args to another. This is useful when adding a selection + * argument to a user provided set. + */ + public static String[] appendSelectionArgs(String[] originalValues, String[] newValues) { + if (originalValues == null || originalValues.length == 0) { + return newValues; + } + String[] result = new String[originalValues.length + newValues.length ]; + System.arraycopy(originalValues, 0, result, 0, originalValues.length); + System.arraycopy(newValues, 0, result, originalValues.length, newValues.length); + return result; + } +} diff --git a/OpenKeychain/src/main/res/drawable-hdpi/attachment_bg_holo.9.png b/OpenKeychain/src/main/res/drawable-hdpi/attachment_bg_holo.9.png new file mode 100644 index 000000000..3cbdd667b Binary files /dev/null and b/OpenKeychain/src/main/res/drawable-hdpi/attachment_bg_holo.9.png differ diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_action_cloud.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_action_cloud.png deleted file mode 100644 index 3daa64131..000000000 Binary files a/OpenKeychain/src/main/res/drawable-hdpi/ic_action_cloud.png and /dev/null differ diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_action_good.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_action_good.png deleted file mode 100644 index 38051d8d6..000000000 Binary files a/OpenKeychain/src/main/res/drawable-hdpi/ic_action_good.png and /dev/null differ diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_action_important_small.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_action_important_small.png deleted file mode 100644 index a1804b2c1..000000000 Binary files a/OpenKeychain/src/main/res/drawable-hdpi/ic_action_important_small.png and /dev/null differ diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_doc_generic_am.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_doc_generic_am.png new file mode 100644 index 000000000..55b9b7d3c Binary files /dev/null and b/OpenKeychain/src/main/res/drawable-hdpi/ic_doc_generic_am.png differ diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_generic_man.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_generic_man.png new file mode 100644 index 000000000..b6b3129f5 Binary files /dev/null and b/OpenKeychain/src/main/res/drawable-hdpi/ic_generic_man.png differ diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_menu_search.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_menu_search.png deleted file mode 100644 index 1cb61faf4..000000000 Binary files a/OpenKeychain/src/main/res/drawable-hdpi/ic_menu_search.png and /dev/null differ diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_menu_search_list.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_menu_search_list.png deleted file mode 100644 index efee6dfd2..000000000 Binary files a/OpenKeychain/src/main/res/drawable-hdpi/ic_menu_search_list.png and /dev/null differ diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_next.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_next.png deleted file mode 100644 index d71058055..000000000 Binary files a/OpenKeychain/src/main/res/drawable-hdpi/ic_next.png and /dev/null differ diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_previous.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_previous.png deleted file mode 100644 index d610e4667..000000000 Binary files a/OpenKeychain/src/main/res/drawable-hdpi/ic_previous.png and /dev/null differ diff --git a/OpenKeychain/src/main/res/drawable-hdpi/key_small.png b/OpenKeychain/src/main/res/drawable-hdpi/key_small.png deleted file mode 100644 index 6966048a1..000000000 Binary files a/OpenKeychain/src/main/res/drawable-hdpi/key_small.png and /dev/null differ diff --git a/OpenKeychain/src/main/res/drawable-ldpi/ic_next.png b/OpenKeychain/src/main/res/drawable-ldpi/ic_next.png deleted file mode 100644 index 474ed8faa..000000000 Binary files a/OpenKeychain/src/main/res/drawable-ldpi/ic_next.png and /dev/null differ diff --git a/OpenKeychain/src/main/res/drawable-ldpi/ic_previous.png b/OpenKeychain/src/main/res/drawable-ldpi/ic_previous.png deleted file mode 100644 index 6fd885e6b..000000000 Binary files a/OpenKeychain/src/main/res/drawable-ldpi/ic_previous.png and /dev/null differ diff --git a/OpenKeychain/src/main/res/drawable-ldpi/key_small.png b/OpenKeychain/src/main/res/drawable-ldpi/key_small.png deleted file mode 100644 index 073b95029..000000000 Binary files a/OpenKeychain/src/main/res/drawable-ldpi/key_small.png and /dev/null differ diff --git a/OpenKeychain/src/main/res/drawable-mdpi/attachment_bg_holo.9.png b/OpenKeychain/src/main/res/drawable-mdpi/attachment_bg_holo.9.png new file mode 100644 index 000000000..bb9214b6a Binary files /dev/null and b/OpenKeychain/src/main/res/drawable-mdpi/attachment_bg_holo.9.png differ diff --git a/OpenKeychain/src/main/res/drawable-mdpi/ic_action_cloud.png b/OpenKeychain/src/main/res/drawable-mdpi/ic_action_cloud.png deleted file mode 100644 index 266d4c21f..000000000 Binary files a/OpenKeychain/src/main/res/drawable-mdpi/ic_action_cloud.png and /dev/null differ diff --git a/OpenKeychain/src/main/res/drawable-mdpi/ic_action_good.png b/OpenKeychain/src/main/res/drawable-mdpi/ic_action_good.png deleted file mode 100644 index 13967b30a..000000000 Binary files a/OpenKeychain/src/main/res/drawable-mdpi/ic_action_good.png and /dev/null differ diff --git a/OpenKeychain/src/main/res/drawable-mdpi/ic_action_important_small.png b/OpenKeychain/src/main/res/drawable-mdpi/ic_action_important_small.png deleted file mode 100644 index 11a25b504..000000000 Binary files a/OpenKeychain/src/main/res/drawable-mdpi/ic_action_important_small.png and /dev/null differ diff --git a/OpenKeychain/src/main/res/drawable-mdpi/ic_doc_generic_am.png b/OpenKeychain/src/main/res/drawable-mdpi/ic_doc_generic_am.png new file mode 100644 index 000000000..a1bd14eaf Binary files /dev/null and b/OpenKeychain/src/main/res/drawable-mdpi/ic_doc_generic_am.png differ diff --git a/OpenKeychain/src/main/res/drawable-mdpi/ic_generic_man.png b/OpenKeychain/src/main/res/drawable-mdpi/ic_generic_man.png new file mode 100644 index 000000000..f763dd259 Binary files /dev/null and b/OpenKeychain/src/main/res/drawable-mdpi/ic_generic_man.png differ diff --git a/OpenKeychain/src/main/res/drawable-mdpi/ic_menu_search.png b/OpenKeychain/src/main/res/drawable-mdpi/ic_menu_search.png deleted file mode 100644 index 2369d03f3..000000000 Binary files a/OpenKeychain/src/main/res/drawable-mdpi/ic_menu_search.png and /dev/null differ diff --git a/OpenKeychain/src/main/res/drawable-mdpi/ic_menu_search_list.png b/OpenKeychain/src/main/res/drawable-mdpi/ic_menu_search_list.png deleted file mode 100644 index 9033f1ec2..000000000 Binary files a/OpenKeychain/src/main/res/drawable-mdpi/ic_menu_search_list.png and /dev/null differ diff --git a/OpenKeychain/src/main/res/drawable-mdpi/ic_next.png b/OpenKeychain/src/main/res/drawable-mdpi/ic_next.png deleted file mode 100644 index 8271c1380..000000000 Binary files a/OpenKeychain/src/main/res/drawable-mdpi/ic_next.png and /dev/null differ diff --git a/OpenKeychain/src/main/res/drawable-mdpi/ic_previous.png b/OpenKeychain/src/main/res/drawable-mdpi/ic_previous.png deleted file mode 100644 index ef90db972..000000000 Binary files a/OpenKeychain/src/main/res/drawable-mdpi/ic_previous.png and /dev/null differ diff --git a/OpenKeychain/src/main/res/drawable-mdpi/key_small.png b/OpenKeychain/src/main/res/drawable-mdpi/key_small.png deleted file mode 100644 index c806b6041..000000000 Binary files a/OpenKeychain/src/main/res/drawable-mdpi/key_small.png and /dev/null differ diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/attachment_bg_holo.9.png b/OpenKeychain/src/main/res/drawable-xhdpi/attachment_bg_holo.9.png new file mode 100644 index 000000000..1f4b25d21 Binary files /dev/null and b/OpenKeychain/src/main/res/drawable-xhdpi/attachment_bg_holo.9.png differ diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_cloud.png b/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_cloud.png deleted file mode 100644 index 0769899fd..000000000 Binary files a/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_cloud.png and /dev/null differ diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_good.png b/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_good.png deleted file mode 100644 index 0bb45d2c0..000000000 Binary files a/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_good.png and /dev/null differ diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_important_small.png b/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_important_small.png deleted file mode 100644 index 40ca1572c..000000000 Binary files a/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_important_small.png and /dev/null differ diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/ic_doc_generic_am.png b/OpenKeychain/src/main/res/drawable-xhdpi/ic_doc_generic_am.png new file mode 100644 index 000000000..e05c4b48d Binary files /dev/null and b/OpenKeychain/src/main/res/drawable-xhdpi/ic_doc_generic_am.png differ diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/ic_generic_man.png b/OpenKeychain/src/main/res/drawable-xhdpi/ic_generic_man.png new file mode 100644 index 000000000..212293db0 Binary files /dev/null and b/OpenKeychain/src/main/res/drawable-xhdpi/ic_generic_man.png differ diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/ic_menu_search.png b/OpenKeychain/src/main/res/drawable-xhdpi/ic_menu_search.png deleted file mode 100644 index 578cb24eb..000000000 Binary files a/OpenKeychain/src/main/res/drawable-xhdpi/ic_menu_search.png and /dev/null differ diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/ic_menu_search_list.png b/OpenKeychain/src/main/res/drawable-xhdpi/ic_menu_search_list.png deleted file mode 100644 index de20fa0e7..000000000 Binary files a/OpenKeychain/src/main/res/drawable-xhdpi/ic_menu_search_list.png and /dev/null differ diff --git a/OpenKeychain/src/main/res/drawable-xxhdpi/attachment_bg_holo.9.png b/OpenKeychain/src/main/res/drawable-xxhdpi/attachment_bg_holo.9.png new file mode 100644 index 000000000..13855a2b1 Binary files /dev/null and b/OpenKeychain/src/main/res/drawable-xxhdpi/attachment_bg_holo.9.png differ diff --git a/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_cloud.png b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_cloud.png deleted file mode 100644 index f97084dbe..000000000 Binary files a/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_cloud.png and /dev/null differ diff --git a/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_good.png b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_good.png deleted file mode 100644 index fda51ad86..000000000 Binary files a/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_good.png and /dev/null differ diff --git a/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_important_small.png b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_important_small.png deleted file mode 100644 index 44754152f..000000000 Binary files a/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_important_small.png and /dev/null differ diff --git a/OpenKeychain/src/main/res/drawable-xxhdpi/ic_doc_generic_am.png b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_doc_generic_am.png new file mode 100644 index 000000000..c09886632 Binary files /dev/null and b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_doc_generic_am.png differ diff --git a/OpenKeychain/src/main/res/drawable-xxhdpi/ic_generic_man.png b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_generic_man.png new file mode 100644 index 000000000..130c670c9 Binary files /dev/null and b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_generic_man.png differ diff --git a/OpenKeychain/src/main/res/layout/edit_key_subkey_added_item.xml b/OpenKeychain/src/main/res/layout/add_subkey_dialog.xml similarity index 65% rename from OpenKeychain/src/main/res/layout/edit_key_subkey_added_item.xml rename to OpenKeychain/src/main/res/layout/add_subkey_dialog.xml index a4258b998..e1ccfee1f 100644 --- a/OpenKeychain/src/main/res/layout/edit_key_subkey_added_item.xml +++ b/OpenKeychain/src/main/res/layout/add_subkey_dialog.xml @@ -1,25 +1,81 @@ - - - + android:layout_height="match_parent"> + + + + + + + + + + + + + + + + + + + + + + -