Merge branch 'master' into yubikey

Conflicts:
	.gitmodules
	OpenKeychain/build.gradle
	OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java
	extern/openpgp-api-lib
	settings.gradle
This commit is contained in:
Dominik Schürmann 2014-08-06 01:08:12 +02:00
commit 881a50207a
207 changed files with 4789 additions and 5294 deletions

3
.gitmodules vendored
View File

@ -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

188
CHANGELOG
View File

@ -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
* Initial public release

View File

@ -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')
}

View File

@ -49,6 +49,10 @@
android:name="android.hardware.touchscreen"
android:required="false" />
<permission android:name="org.sufficientlysecure.keychain.WRITE_TEMPORARY_STORAGE" />
<uses-permission android:name="org.sufficientlysecure.keychain.WRITE_TEMPORARY_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.NFC" />
@ -152,12 +156,14 @@
<action android:name="org.sufficientlysecure.keychain.action.ENCRYPT" />
<category android:name="android.intent.category.DEFAULT" />
<!-- TODO: accept other schemes! -->
<data android:scheme="file" />
<data android:scheme="content" />
</intent-filter>
<!-- Android's Send Action -->
<intent-filter android:label="@string/intent_send_encrypt">
<action android:name="android.intent.action.SEND" />
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
@ -170,26 +176,16 @@
android:label="@string/title_decrypt"
android:windowSoftInputMode="stateHidden">
<!--&lt;!&ndash; VIEW with mimeType: TODO (from email app) &ndash;&gt;-->
<!--<intent-filter android:label="@string/intent_import_key">-->
<!--<action android:name="android.intent.action.VIEW" />-->
<!-- VIEW with mimeType application/pgp-encrypted -->
<intent-filter android:label="@string/intent_send_decrypt">
<action android:name="android.intent.action.VIEW" />
<!--<category android:name="android.intent.category.BROWSABLE" />-->
<!--<category android:name="android.intent.category.DEFAULT" />-->
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT" />
<!--&lt;!&ndash; mime type as defined in http://tools.ietf.org/html/rfc3156 &ndash;&gt;-->
<!--<data android:mimeType="application/pgp-signature" />-->
<!--</intent-filter>-->
<!--&lt;!&ndash; VIEW with mimeType: TODO (from email app) &ndash;&gt;-->
<!--<intent-filter android:label="@string/intent_import_key">-->
<!--<action android:name="android.intent.action.VIEW" />-->
<!--<category android:name="android.intent.category.BROWSABLE" />-->
<!--<category android:name="android.intent.category.DEFAULT" />-->
<!--&lt;!&ndash; mime type as defined in http://tools.ietf.org/html/rfc3156 &ndash;&gt;-->
<!--<data android:mimeType="application/pgp-encrypted" />-->
<!--</intent-filter>-->
<!-- mime type as defined in http://tools.ietf.org/html/rfc3156 -->
<data android:mimeType="application/pgp-encrypted" />
</intent-filter>
<!-- Keychain's own Actions -->
<!-- DECRYPT with text as extra -->
<intent-filter>
@ -202,8 +198,9 @@
<action android:name="org.sufficientlysecure.keychain.action.DECRYPT" />
<category android:name="android.intent.category.DEFAULT" />
<!-- TODO: accept other schemes! -->
<data android:scheme="file" />
<data android:scheme="content" />
</intent-filter>
<!-- Android's Send Action -->
<intent-filter android:label="@string/intent_send_decrypt">
@ -644,6 +641,12 @@
android:resource="@xml/custom_pgp_contacts_structure" />
</service>
<provider
android:name=".provider.TemporaryStorageProvider"
android:authorities="org.sufficientlysecure.keychain.tempstorage"
android:writePermission="org.sufficientlysecure.keychain.WRITE_TEMPORARY_STORAGE"
android:exported="true" />
</application>
</manifest>

View File

@ -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;

View File

@ -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) {

View File

@ -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<String, Bitmap> photoCache = new HashMap<String, Bitmap>();
public static List<String> getPossibleUserEmails(Context context) {
Set<String> 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()) {

View File

@ -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);
}
}

View File

@ -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,28 +67,10 @@ 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() {
@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));
}
}
};
// 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
@ -103,12 +84,13 @@ public class ExportHelper {
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");
FileHelper.saveFile(new FileHelper.FileDialogCallback() {
@Override
public void onFileSelected(File file, boolean checked) {
mExportFile = file;
exportKeys(masterKeyIds, checked);
}
});
}, 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) {

View File

@ -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 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 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.
* <p/>
* 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;
}
}
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);
}
}

View File

@ -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) {

View File

@ -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 {

View File

@ -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()

View File

@ -1,3 +1,20 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.keyimport;
import android.os.Parcel;

View File

@ -1,3 +1,20 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.pgp;
import org.spongycastle.openpgp.PGPKeyRing;

View File

@ -1,3 +1,20 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.pgp;
import org.spongycastle.openpgp.PGPPublicKey;

View File

@ -1,3 +1,20 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.pgp;
import org.spongycastle.bcpg.ArmoredOutputStream;

View File

@ -1,3 +1,20 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.pgp;
import org.spongycastle.bcpg.HashAlgorithmTags;

View File

@ -1,3 +1,20 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.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;

View File

@ -1,3 +1,20 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.pgp;
import android.text.TextUtils;

View File

@ -1,3 +1,20 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.pgp;
/**

View File

@ -1,102 +0,0 @@
/*
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.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<PGPSecretKey>
*
* @param keysBytes
* @return
*/
public static ArrayList<UncachedSecretKey> BytesToPGPSecretKeyList(byte[] keysBytes) {
PGPObjectFactory factory = new PGPObjectFactory(keysBytes);
Object obj = null;
ArrayList<UncachedSecretKey> keys = new ArrayList<UncachedSecretKey>();
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<PGPSecretKey> itr = keyRing.getSecretKeys();
while (itr.hasNext()) {
keys.add(new UncachedSecretKey(itr.next()));
}
}
}
} catch (IOException e) {
}
return keys;
}
/**
* Convert from byte[] to PGPSecretKey
* <p/>
* 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);
}
}

View File

@ -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;
}

View File

@ -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);

View File

@ -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

View File

@ -1,3 +1,20 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.pgp;
import org.spongycastle.bcpg.ArmoredOutputStream;

View File

@ -1,3 +1,20 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.pgp;
import org.spongycastle.bcpg.sig.KeyFlags;

View File

@ -1,3 +1,20 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.pgp;
import org.spongycastle.bcpg.sig.KeyFlags;

View File

@ -1,3 +1,20 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.pgp;
import org.spongycastle.bcpg.SignatureSubpacket;

View File

@ -1,3 +1,20 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.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);
}
}

View File

@ -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) {

View File

@ -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);
}
}

View File

@ -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();
}

View File

@ -0,0 +1,171 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.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);
}
}

View File

@ -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() {

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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,6 +236,9 @@ 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);
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);
@ -268,9 +274,10 @@ public class KeychainIntentService extends IntentService
/* Output */
Bundle resultData = new Bundle();
finalizeEncryptOutputStream(data, resultData, outStream);
}
OtherHelper.logDebugBundle(resultData, "resultData");
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData);
@ -415,14 +422,17 @@ 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);
if (outputFile != null) {
// check if storage is ready
if (!FileHelper.isStorageMounted(outputFile)) {
throw new PgpGeneralException(getString(R.string.error_external_storage_not_ready));
}
}
ArrayList<Long> publicMasterKeyIds = new ArrayList<Long>();
ArrayList<Long> secretMasterKeyIds = new ArrayList<Long>();
@ -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.<Uri>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.<Uri>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;

View File

@ -1,3 +1,20 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.service;
import android.app.Activity;

View File

@ -1,3 +1,20 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.service;
import android.app.Activity;

View File

@ -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);

View File

@ -1,3 +1,20 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.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.
*
* <p/>
* This class should include all types of operations supported in the backend.
*
* <p/>
* 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.
*
* <p/>
* 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;
}
}

View File

@ -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

View File

@ -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);
mFileFragmentBundle.putParcelable(DecryptFileFragment.ARG_URI, uri);
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();
}
} else if (ACTION_DECRYPT.equals(action)) {
Log.e(Constants.TAG,
"Include the extra 'text' or an Uri with setData() in your Intent!");

View File

@ -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().<Uri>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);
private void setInputUri(Uri inputUri) {
if (inputUri == null) {
mInputUri = null;
mFilename.setText("");
return;
}
return Constants.Path.APP_DIR + "/" + filename;
mInputUri = inputUri;
mFilename.setText(FileHelper.getFilename(getActivity(), mInputUri));
}
private void decryptAction() {
String currentFilename = mFilename.getText().toString();
if (mInputFilename == null || !mInputFilename.equals(currentFilename)) {
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);
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;
}
}
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);
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 {
mOutputFilename = data.getString(FileDialogFragment.MESSAGE_DATA_FILENAME);
FileHelper.saveDocument(this, "*/*", targetName, REQUEST_CODE_OUTPUT);
}
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");
}
@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);
}
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.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;
}

View File

@ -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);
}

View File

@ -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;

View File

@ -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<Cursor> {
@ -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(

View File

@ -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<Uri> mInputUris;
private ArrayList<Uri> 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<Uri> getInputUris() {
if (mInputUris == null) mInputUris = new ArrayList<Uri>();
return mInputUris;
}
@Override
public ArrayList<Uri> getOutputUris() {
if (mOutputUris == null) mOutputUris = new ArrayList<Uri>();
return mOutputUris;
}
@Override
public void setInputUris(ArrayList<Uri> uris) {
mInputUris = uris;
notifyUpdate();
}
@Override
public void setOutputUris(ArrayList<Uri> 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.
* <p/>
* 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<LabeledIntent> targetedShareIntents = new ArrayList<LabeledIntent>();
List<ResolveInfo> resInfoList = getPackageManager().queryIntentActivities(prototype, 0);
List<ResolveInfo> resInfoListFiltered = new ArrayList<ResolveInfo>();
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<ResolveInfo>() {
@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<String> users = new HashSet<String>();
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<Uri> uris = new ArrayList<Uri>();
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.<Uri>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);
mFileFragmentBundle.putParcelableArrayList(EncryptFileFragment.ARG_URIS, uris);
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();
}
} else if (ACTION_ENCRYPT.equals(action)) {
Log.e(Constants.TAG,
"Include the extra 'text' or an Uri with setData() in your Intent!");

View File

@ -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<Uri> getInputUris();
public ArrayList<Uri> getOutputUris();
public void setInputUris(ArrayList<Uri> uris);
public void setOutputUris(ArrayList<Uri> 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);
}

View File

@ -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 = (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));
}
});
mSign.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
CheckBox checkBox = (CheckBox) v;
if (checkBox.isChecked()) {
selectSecretKey();
} else {
@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<Cursor>() {
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
// This is called when a new Loader needs to be created. This
// sample only has one Loader, so we don't care about the ID.
Uri baseUri = KeyRings.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<Cursor> loader, Cursor data) {
mSignAdapter.swapCursor(data);
}
@Override
public void onLoaderReset(Loader<Cursor> 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<Long> goodIds = new Vector<Long>();
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);
}
}
}
private void updateView() {
if (mEncryptionKeyIds == null || mEncryptionKeyIds.length == 0) {
mSelectKeysButton.setText(getString(R.string.select_keys_button_default));
} else {
mSelectKeysButton.setText(getResources().getQuantityString(
R.plurals.select_keys_button, mEncryptionKeyIds.length,
mEncryptionKeyIds.length));
}
if (mSecretKeyId == 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();
if (userIdSplit[0] != null) {
mMainUserId.setText(userIdSplit[0]);
} else {
mMainUserId.setText(R.string.user_id_no_name);
}
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);
updateEncryptionKeys();
}
}
private void selectPublicKeys() {
Intent intent = new Intent(getActivity(), SelectPublicKeyActivity.class);
Vector<Long> keyIds = new Vector<Long>();
if (mEncryptionKeyIds != null && mEncryptionKeyIds.length > 0) {
for (int i = 0; i < mEncryptionKeyIds.length; ++i) {
keyIds.add(mEncryptionKeyIds[i]);
private void updateEncryptionKeys() {
List<Object> objects = mEncryptKeyView.getObjects();
List<Long> keyIds = new ArrayList<Long>();
List<String> userIds = new ArrayList<String>();
for (Object object : objects) {
if (object instanceof EncryptKeyCompletionView.EncryptionKey) {
keyIds.add(((EncryptKeyCompletionView.EncryptionKey) object).getKeyId());
userIds.add(((EncryptKeyCompletionView.EncryptionKey) object).getUserId());
}
}
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);
long[] keyIdsArr = new long[keyIds.size()];
Iterator<Long> iterator = keyIds.iterator();
for (int i = 0; i < keyIds.size(); i++) {
keyIdsArr[i] = iterator.next();
}
}
intent.putExtra(SelectPublicKeyActivity.EXTRA_SELECTED_MASTER_KEY_IDS, initialKeyIds);
startActivityForResult(intent, REQUEST_CODE_PUBLIC_KEYS);
setEncryptionKeyIds(keyIdsArr);
setEncryptionUserIds(userIds.toArray(new String[userIds.size()]));
}
private void selectSecretKey() {
Intent intent = new Intent(getActivity(), SelectSecretKeyActivity.class);
intent.putExtra(SelectSecretKeyActivity.EXTRA_FILTER_SIGN, true);
startActivityForResult(intent, REQUEST_CODE_SECRET_KEYS);
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 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;
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)));
}
case REQUEST_CODE_SECRET_KEYS: {
if (resultCode == Activity.RESULT_OK) {
Uri uriMasterKey = data.getData();
setSignatureKeyId(Long.valueOf(uriMasterKey.getLastPathSegment()));
@Override
public long getItemId(int position) {
mCursor.moveToPosition(position);
return mCursor.getLong(mIndexMasterKeyId);
}
};
}
public Cursor swapCursor(Cursor newCursor) {
if (newCursor == null) return inner.swapCursor(null);
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 {
setSignatureKeyId(Constants.key.none);
v = convertView;
}
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;
}
}

View File

@ -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<Uri, Bitmap> thumbnailCache = new HashMap<Uri, Bitmap>();
@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<Choice> adapter = new ArrayAdapter<Choice>(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,264 +112,125 @@ 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);
addInputUris(getArguments().<Uri>getParcelableArrayList(ARG_URIS));
}
boolean asciiArmor = getArguments().getBoolean(ARG_ASCII_ARMOR);
if (asciiArmor) {
mAsciiArmor.setChecked(asciiArmor);
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);
}
}
private void addInputUris(List<Uri> uris) {
if (uris != null) {
for (Uri uri : uris) {
addInputUri(uri);
}
}
}
private void addInputUri(Uri inputUri) {
if (inputUri == null) {
return;
}
mEncryptInterface.getInputUris().add(inputUri);
mEncryptInterface.notifyUpdate();
mSelectedFiles.requestFocus();
/**
* Guess output filename based on input path
* We hide the encrypt to file button if multiple files are selected.
*
* @param path
* @return Suggestion for output filename
* With Android L it will be possible to select a target directory for multiple files, so we might want to
* change this later
*/
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;
if (mEncryptInterface.getInputUris().size() > 1) {
mEncryptFile.setVisibility(View.GONE);
} else {
mEncryptFile.setVisibility(View.VISIBLE);
}
}
private void delInputUri(int position) {
mEncryptInterface.getInputUris().remove(position);
mEncryptInterface.notifyUpdate();
mSelectedFiles.requestFocus();
if (mEncryptInterface.getInputUris().size() > 1) {
mEncryptFile.setVisibility(View.GONE);
} else {
mEncryptFile.setVisibility(View.VISIBLE);
}
}
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);
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 {
mOutputFilename = data.getString(FileDialogFragment.MESSAGE_DATA_FILENAME);
}
encryptStart();
}
}
};
// Create a new Messenger for the communication back
Messenger messenger = new Messenger(returnHandler);
mFileDialog = FileDialogFragment.newInstance(messenger,
getString(R.string.title_encrypt_to_file),
getString(R.string.specify_file_to_encrypt_to), mOutputFilename, null);
mFileDialog.show(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);
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;
FileHelper.saveDocument(this, "*/*", FileHelper.getFilename(getActivity(), inputUri) +
(mEncryptInterface.isUseArmor() ? ".asc" : ".gpg"), REQUEST_CODE_OUTPUT);
}
}
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;
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));
}
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) {
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) {
mEncryptInterface.startEncrypt(true);
} else if (mEncryptInterface.getInputUris().size() == 1) {
showOutputFileDialog();
}
}
});
return;
@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);
}
return true;
}
showOutputFileDialog();
}
private void encryptStart() {
// 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();
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);
} else {
data.putInt(KeychainIntentService.SOURCE, KeychainIntentService.IO_FILE);
data.putString(KeychainIntentService.ENCRYPT_INPUT_FILE, mInputFilename);
}
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);
}
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 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)));
}
}
}
};
// 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 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;
}
@ -422,4 +242,68 @@ public class EncryptFileFragment extends Fragment {
}
}
}
@Override
public void onNotifyUpdate() {
// Clear cache if needed
for (Uri uri : new HashSet<Uri>(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;
}
}
}

View File

@ -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);
}
}

View File

@ -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() {
}
}

View File

@ -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;

View File

@ -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);
}
});

View File

@ -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;

View File

@ -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: {

View File

@ -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 {

View File

@ -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[]{

View File

@ -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;

View File

@ -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)
);
}

View File

@ -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);
}
}

View File

@ -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;

View File

@ -111,7 +111,7 @@ public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> {
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);

View File

@ -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<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> {

View File

@ -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;

View File

@ -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);

View File

@ -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;

View File

@ -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<SaveKeyringParcel.SubkeyAdd> {
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<SaveKeyringParcel.SubkeyAdd> mData;
@ -70,12 +51,12 @@ public class SubkeysAddedAdapter extends ArrayAdapter<SaveKeyringParcel.SubkeyAd
}
static class ViewHolder {
public OnAlgorithmSelectedListener mAlgorithmSelectedListener;
public Spinner mAlgorithmSpinner;
public Spinner mKeySizeSpinner;
public TextView mCustomKeyTextView;
public EditText mCustomKeyEditText;
public TextView mCustomKeyInfoTextView;
public TextView vKeyId;
public TextView vKeyDetails;
public TextView vKeyExpiry;
public ImageView vCertifyIcon;
public ImageView vEncryptIcon;
public ImageView vSignIcon;
public ImageButton vDelete;
// also hold a reference to the model item
public SaveKeyringParcel.SubkeyAdd mModel;
@ -84,44 +65,25 @@ public class SubkeysAddedAdapter extends ArrayAdapter<SaveKeyringParcel.SubkeyAd
public View getView(final int position, View convertView, ViewGroup parent) {
if (convertView == null) {
// Not recycled, inflate a new view
convertView = mInflater.inflate(R.layout.edit_key_subkey_added_item, null);
convertView = mInflater.inflate(R.layout.view_key_subkey_item, null);
final ViewHolder holder = new ViewHolder();
holder.mAlgorithmSpinner = (Spinner) convertView.findViewById(R.id.create_key_algorithm);
holder.mKeySizeSpinner = (Spinner) convertView.findViewById(R.id.create_key_size);
holder.mCustomKeyTextView = (TextView) convertView.findViewById(R.id.custom_key_size_label);
holder.mCustomKeyEditText = (EditText) convertView.findViewById(R.id.custom_key_size_input);
holder.mCustomKeyInfoTextView = (TextView) convertView.findViewById(R.id.custom_key_size_info);
holder.vDelete = (ImageButton) convertView.findViewById(R.id.subkey_added_item_delete);
holder.vKeyId = (TextView) convertView.findViewById(R.id.subkey_item_key_id);
holder.vKeyDetails = (TextView) convertView.findViewById(R.id.subkey_item_details);
holder.vKeyExpiry = (TextView) convertView.findViewById(R.id.subkey_item_expiry);
holder.vCertifyIcon = (ImageView) convertView.findViewById(R.id.subkey_item_ic_certify);
holder.vEncryptIcon = (ImageView) convertView.findViewById(R.id.subkey_item_ic_encrypt);
holder.vSignIcon = (ImageView) convertView.findViewById(R.id.subkey_item_ic_sign);
holder.vDelete = (ImageButton) convertView.findViewById(R.id.subkey_item_delete_button);
holder.vDelete.setVisibility(View.VISIBLE); // always visible
// not used:
ImageView editImage = (ImageView) convertView.findViewById(R.id.subkey_item_edit_image);
editImage.setVisibility(View.GONE);
ImageView revokedIcon = (ImageView) convertView.findViewById(R.id.subkey_item_ic_revoked);
revokedIcon.setVisibility(View.GONE);
convertView.setTag(holder);
holder.mAlgorithmSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> 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<SaveKeyringParcel.SubkeyAd
// save reference to model item
holder.mModel = getItem(position);
// TODO
boolean wouldBeMasterKey = false;
// boolean wouldBeMasterKey = (childCount == 0);
String algorithmStr = PgpKeyHelper.getAlgorithmInfo(
mActivity,
holder.mModel.mAlgorithm,
holder.mModel.mKeysize
);
holder.vKeyId.setText(R.string.edit_key_new_subkey);
holder.vKeyDetails.setText(algorithmStr);
ArrayList<Choice> choices = new ArrayList<Choice>();
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<Choice> adapter = new ArrayAdapter<Choice>(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);
}
if ((flags & KeyFlags.SIGN_DATA) > 0) {
holder.vSignIcon.setVisibility(View.VISIBLE);
} else {
holder.vSignIcon.setVisibility(View.GONE);
}
// dynamic ArrayAdapter must be created (instead of ArrayAdapter.getFromResource), because it's content may change
ArrayAdapter<CharSequence> keySizeAdapter = new ArrayAdapter<CharSequence>(mActivity, android.R.layout.simple_spinner_item,
new ArrayList<CharSequence>(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) {
if (((flags & KeyFlags.ENCRYPT_COMMS) > 0)
|| ((flags & KeyFlags.ENCRYPT_STORAGE) > 0)) {
holder.vEncryptIcon.setVisibility(View.VISIBLE);
} else {
holder.vEncryptIcon.setVisibility(View.GONE);
}
@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);
// 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;
}
/**
* <h3>RSA</h3>
* <p>for RSA algorithm, key length must be greater than 1024 (according to
* <a href="https://github.com/open-keychain/open-keychain/issues/102">#102</a>). Possibility to generate keys bigger
* than 8192 bits is currently disabled, because it's almost impossible to generate them on a mobile device (check
* <a href="http://www.javamex.com/tutorials/cryptography/rsa_key_length.shtml">RSA key length plot</a> and
* <a href="http://www.keylength.com/">Cryptographic Key Length Recommendation</a>). Also, key length must be a
* multiplicity of 8.</p>
* <h3>ElGamal</h3>
* <p>For ElGamal algorithm, supported key lengths are 1536, 2048, 3072, 4096 or 8192 bits.</p>
* <h3>DSA</h3>
* <p>For DSA algorithm key length must be between 512 and 1024. Also, it must me dividable by 64.</p>
*
* @return correct key length, according to SpongyCastle specification. Returns <code>-1</code>, 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<CharSequence> keySizeAdapter = (ArrayAdapter<CharSequence>) 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<CharSequence> 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);
}
}
}
}

View File

@ -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);

View File

@ -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<Choice> choices = new ArrayList<Choice>();
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<Choice> choices = new ArrayList<Choice>();
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<Choice> adapter = new ArrayAdapter<Choice>(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<CharSequence> keySizeAdapter = new ArrayAdapter<CharSequence>(context, android.R.layout.simple_spinner_item,
new ArrayList<CharSequence>(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;

View File

@ -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<String>
(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) {

View File

@ -1,187 +0,0 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.ui.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);
}
}
}

View File

@ -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().<Uri>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() {

View File

@ -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;
}

View File

@ -0,0 +1,187 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.ui.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);
}
}
}

View File

@ -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,13 +62,33 @@ 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());
builder.setTitle(R.string.edit_key_edit_user_id_title);
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.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) {
@ -77,6 +103,8 @@ public class EditUserIdDialogFragment extends DialogFragment {
}
}
});
}
builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {

View File

@ -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);
}
}

View File

@ -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;

View File

@ -0,0 +1,271 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.ui.widget;
import android.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<Cursor>() {
@Override
public Loader<Cursor> 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<Cursor> loader, Cursor data) {
swapCursor(data);
}
@Override
public void onLoaderReset(Loader<Cursor> 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.<EncryptionKey>emptyList()));
return;
}
ArrayList<EncryptionKey> keys = new ArrayList<EncryptionKey>();
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<EncryptionKey> {
public EncryptKeyAdapter(List<EncryptionKey> 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);
}
}
}

View File

@ -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;

View File

@ -1,380 +0,0 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.ui.widget;
import android.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));
}
}

View File

@ -0,0 +1,62 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.ui.widget;
import android.content.Context;
import android.util.AttributeSet;
import android.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;
}
}

View File

@ -1,428 +0,0 @@
/*
* Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.ui.widget;
import android.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<String> mDeletedIDs = new ArrayList<String>();
private ArrayList<UncachedSecretKey> mDeletedKeys = new ArrayList<UncachedSecretKey>();
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<String> getOriginalIDs() {
ArrayList<String> orig = new ArrayList<String>();
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<String> getDeletedIDs() {
return mDeletedIDs;
}
public ArrayList<UncachedSecretKey> getDeletedKeys() {
return mDeletedKeys;
}
public List<Boolean> getNeedsSavingArray() {
ArrayList<Boolean> mList = new ArrayList<Boolean>();
for (int i = 0; i < mEditors.getChildCount(); ++i) {
Editor editor = (Editor) mEditors.getChildAt(i);
mList.add(editor.needsSaving());
}
return mList;
}
public List<Boolean> getNewIDFlags() {
ArrayList<Boolean> mList = new ArrayList<Boolean>();
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<Boolean> getNewKeysArray() {
ArrayList<Boolean> mList = new ArrayList<Boolean>();
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<String> 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<UncachedSecretKey> list, Vector<Integer> 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();
}
}
}

View File

@ -1,267 +0,0 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.ui.widget;
import android.content.Context;
import android.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<String>
(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; }
}

View File

@ -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) + ")");
}

View File

@ -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;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 450 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 485 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 473 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 694 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Some files were not shown because too many files have changed in this diff Show More