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
3
.gitmodules
vendored
@ -28,6 +28,9 @@
|
|||||||
[submodule "extern/minidns"]
|
[submodule "extern/minidns"]
|
||||||
path = extern/minidns
|
path = extern/minidns
|
||||||
url = https://github.com/open-keychain/minidns.git
|
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"]
|
[submodule "extern/openpgp-card-nfc-lib"]
|
||||||
path = extern/openpgp-card-nfc-lib
|
path = extern/openpgp-card-nfc-lib
|
||||||
url = https://github.com/open-keychain/openpgp-card-nfc-lib.git
|
url = https://github.com/open-keychain/openpgp-card-nfc-lib.git
|
188
CHANGELOG
@ -1,3 +1,17 @@
|
|||||||
|
2.8
|
||||||
|
* So many bugs have been fixed in this release that we focus on the main new features
|
||||||
|
* Key edit: awesome new design, key revocation
|
||||||
|
* Key import: awesome new design, secure keyserver connections via hkps, keyserver resolving via DNS SRV records
|
||||||
|
* New first time screen
|
||||||
|
* New create key screen: autocompletion of name and email based on your personal Android accounts
|
||||||
|
* File encryption: awesome new design, support for encrypting multiple files
|
||||||
|
* New icons to show status of key (by Brennan Novak)
|
||||||
|
* Important bug fix: Importing of large key collections from a file is now possible
|
||||||
|
* Notification showing cached passphrases
|
||||||
|
|
||||||
|
This release wouldn't be possible without the work of Vincent Breitmoser (GSoC 2014), mar-v-in (GSoC 2014), Daniel Albert, Art O Cathain, Daniel Haß, Tim Bray, Thialfihar
|
||||||
|
|
||||||
|
|
||||||
2.7
|
2.7
|
||||||
* Purple! (Dominik, Vincent)
|
* Purple! (Dominik, Vincent)
|
||||||
* New key view design (Dominik, Vincent)
|
* New key view design (Dominik, Vincent)
|
||||||
@ -6,50 +20,50 @@
|
|||||||
* Keybase.io import (Tim Bray)
|
* Keybase.io import (Tim Bray)
|
||||||
|
|
||||||
2.6.1
|
2.6.1
|
||||||
* some fixes for regression bugs
|
* Some fixes for regression bugs
|
||||||
|
|
||||||
2.6
|
2.6
|
||||||
* key certifications (thanks to Vincent Breitmoser)
|
* Key certifications (thanks to Vincent Breitmoser)
|
||||||
* support for GnuPG partial secret keys (thanks to Vincent Breitmoser)
|
* Support for GnuPG partial secret keys (thanks to Vincent Breitmoser)
|
||||||
* new design for signature verification
|
* New design for signature verification
|
||||||
* custom key length (thanks to Greg Witczak)
|
* Custom key length (thanks to Greg Witczak)
|
||||||
* fix share-functionality from other apps
|
* Fix share-functionality from other apps
|
||||||
|
|
||||||
2.5
|
2.5
|
||||||
* fix decryption of symmetric pgp messages/files
|
* Fix decryption of symmetric pgp messages/files
|
||||||
* refactored edit key screen (thanks to Ash Hughes)
|
* Refactored edit key screen (thanks to Ash Hughes)
|
||||||
* new modern design for encrypt/decrypt screens
|
* New modern design for encrypt/decrypt screens
|
||||||
* OpenPGP API version 3 (multiple api accounts, internal fixes, key lookup)
|
* OpenPGP API version 3 (multiple api accounts, internal fixes, key lookup)
|
||||||
|
|
||||||
2.4
|
2.4
|
||||||
Thanks to all applicants of Google Summer of Code 2014 who made this release feature rich and bug free!
|
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):
|
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.
|
Daniel Hammann, Daniel Haß, Greg Witczak, Miroojin Bakshi, Nikhil Peter Raj, Paul Sarbinowski, Sreeram Boyapati, Vincent Breitmoser.
|
||||||
* new unified key list
|
* New unified key list
|
||||||
* colorized key fingerprint
|
* Colorized key fingerprint
|
||||||
* support for keyserver ports
|
* Support for keyserver ports
|
||||||
* deactivate possibility to generate weak keys
|
* Deactivate possibility to generate weak keys
|
||||||
* much more internal work on the API
|
* Much more internal work on the API
|
||||||
* certify user ids
|
* Certify user ids
|
||||||
* keyserver query based on machine-readable output
|
* Keyserver query based on machine-readable output
|
||||||
* lock navigation drawer on tablets
|
* Lock navigation drawer on tablets
|
||||||
* suggestions for emails on creation of keys
|
* Suggestions for emails on creation of keys
|
||||||
* search in public key lists
|
* Search in public key lists
|
||||||
* and much more improvements and fixes…
|
* And much more improvements and fixes…
|
||||||
|
|
||||||
2.3.1
|
2.3.1
|
||||||
* hotfix for crash when upgrading from old versions
|
* Hotfix for crash when upgrading from old versions
|
||||||
|
|
||||||
2.3
|
2.3
|
||||||
* remove unnecessary export of public keys when exporting secret key (thanks to Ash Hughes)
|
* Remove unnecessary export of public keys when exporting secret key (thanks to Ash Hughes)
|
||||||
* fix setting expiry dates on keys (thanks to Ash Hughes)
|
* Fix setting expiry dates on keys (thanks to Ash Hughes)
|
||||||
* more internal fixes when editing keys (thanks to Ash Hughes)
|
* More internal fixes when editing keys (thanks to Ash Hughes)
|
||||||
* querying keyservers directly from the import screen
|
* Querying keyservers directly from the import screen
|
||||||
* fix layout and dialog style on Android 2.2-3.0
|
* Fix layout and dialog style on Android 2.2-3.0
|
||||||
* fix crash on keys with empty user ids
|
* Fix crash on keys with empty user ids
|
||||||
* fix crash and empty lists when coming back from signing screen
|
* 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
|
* 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
|
2.2
|
||||||
* New design with navigation drawer
|
* New design with navigation drawer
|
||||||
@ -76,110 +90,110 @@ Daniel Hammann, Daniel Haß, Greg Witczak, Miroojin Bakshi, Nikhil Peter Raj, Pa
|
|||||||
* New AIDL API
|
* New AIDL API
|
||||||
|
|
||||||
1.0.8
|
1.0.8
|
||||||
* basic keyserver support (HKP, please report bugs :))
|
* Basic keyserver support (HKP, please report bugs :))
|
||||||
* app2sd (untested, let me know if there are problems)
|
* App2sd (untested, let me know if there are problems)
|
||||||
* more choices for pass phrase cache: 1, 2, 4, 8, hours
|
* More choices for pass phrase cache: 1, 2, 4, 8, hours
|
||||||
* translations: Norwegian (thanks, Sander Danielsen), Chinese (thanks, Zhang Fredrick)
|
* Translations: Norwegian (thanks, Sander Danielsen), Chinese (thanks, Zhang Fredrick)
|
||||||
* bugfixes
|
* Bugfixes
|
||||||
* optimizations
|
* Optimizations
|
||||||
|
|
||||||
1.0.7
|
1.0.7
|
||||||
* clear sign problem with lacking trailing newline fixed
|
* Clear sign problem with lacking trailing newline fixed
|
||||||
* more options for pass phrase cache time to live (20, 40, 60 mins)
|
* More options for pass phrase cache time to live (20, 40, 60 mins)
|
||||||
|
|
||||||
1.0.6
|
1.0.6
|
||||||
* account adding crash on Froyo fixed
|
* Account adding crash on Froyo fixed
|
||||||
* secure file deletion
|
* Secure file deletion
|
||||||
* option to delete key file after import
|
* Option to delete key file after import
|
||||||
* stream encryption/decryption (gallery, etc.)
|
* Stream encryption/decryption (gallery, etc.)
|
||||||
* new options (language, force v3 signatures)
|
* New options (language, force v3 signatures)
|
||||||
* interface changes
|
* Interface changes
|
||||||
* bugfixes
|
* Bugfixes
|
||||||
|
|
||||||
1.0.5
|
1.0.5
|
||||||
* German and Italian translation
|
* German and Italian translation
|
||||||
* much smaller package, due to reduced BC sources
|
* Much smaller package, due to reduced BC sources
|
||||||
* new preferences GUI
|
* New preferences GUI
|
||||||
* layout adjustment for localization
|
* Layout adjustment for localization
|
||||||
* signature bugfix
|
* Signature bugfix
|
||||||
|
|
||||||
1.0.4
|
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
|
1.0.3
|
||||||
* fixed crashes during encryption/signing and possibly key export
|
* Fixed crashes during encryption/signing and possibly key export
|
||||||
|
|
||||||
1.0.2
|
1.0.2
|
||||||
* filterable key lists
|
* Filterable key lists
|
||||||
* smarter preselection of encryption keys
|
* Smarter preselection of encryption keys
|
||||||
* new Intent handling for VIEW and SEND, allows files to be encrypted/decrypted out of file managers
|
* 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
|
* Fixes and additional features (key preselection) for k9, new beta build available
|
||||||
|
|
||||||
1.0.1
|
1.0.1
|
||||||
* GMail account listing was broken in 1.0.0, fixed again
|
* GMail account listing was broken in 1.0.0, fixed again
|
||||||
|
|
||||||
1.0.0
|
1.0.0
|
||||||
* k9mail integration, APG supporting beta build of k9mail
|
* K-9 Mail integration, APG supporting beta build of K-9 Mail
|
||||||
* support of more file managers (including ASTRO)
|
* Support of more file managers (including ASTRO)
|
||||||
* Slovenian translation
|
* Slovenian translation
|
||||||
* new database, much faster, less memory usage
|
* New database, much faster, less memory usage
|
||||||
* defined Intents and content provider for other apps
|
* Defined Intents and content provider for other apps
|
||||||
* bugfixes
|
* Bugfixes
|
||||||
|
|
||||||
0.9.7
|
0.9.7
|
||||||
* 0.9.5 must have introduced a bug that prevented symmetric encryption, this release fixes it
|
* 0.9.5 must have introduced a bug that prevented symmetric encryption, this release fixes it
|
||||||
|
|
||||||
0.9.6
|
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
|
0.9.5
|
||||||
* k9mail integration: using "More -> Forward (alternate)"
|
* K-9 Mail integration: using "More -> Forward (alternate)"
|
||||||
* pass phrase cache
|
* Passphrase cache
|
||||||
* compression preferences added
|
* Compression preferences added
|
||||||
* accurate decryption progress bar
|
* Accurate decryption progress bar
|
||||||
* internationalization prepared, hopefully translations will follow
|
* Internationalization prepared, hopefully translations will follow
|
||||||
|
|
||||||
0.9.4
|
0.9.4
|
||||||
* Android 1.5 support, I *hope*, please report problems with layout and graphics
|
* Android 1.5 support, I *hope*, please report problems with layout and graphics
|
||||||
* yet another interface change, hopefully this will be it :)
|
* Yet another interface change, hopefully this will be it :)
|
||||||
* symmetric encryption for messages
|
* 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
|
* Encrypt and decrypt processes all wrapped into ONE activity respectively in preparation for defined Intents to use APG in other apps
|
||||||
|
|
||||||
0.9.3
|
0.9.3
|
||||||
* handle large files correctly
|
* Handle large files correctly
|
||||||
* better progress bars (especially for file encryption/decryption)
|
* Better progress bars (especially for file encryption/decryption)
|
||||||
* option to delete files after en-/decryption
|
* Option to delete files after en-/decryption
|
||||||
* bug fixes, layout tweaks
|
* Bug fixes, layout tweaks
|
||||||
|
|
||||||
0.9.2
|
0.9.2
|
||||||
* settings for default encryption/hash algorithm
|
* Settings for default encryption/hash algorithm
|
||||||
* hushmail key support
|
* Hushmail key support
|
||||||
* GUI improvements (encrypt file layout rewritten)
|
* GUI improvements (encrypt file layout rewritten)
|
||||||
* bug fixes
|
* Bug fixes
|
||||||
|
|
||||||
0.9.1
|
0.9.1
|
||||||
* ElGamal support for subkeys
|
* ElGamal support for subkeys
|
||||||
* fixes of some silly 0.9.0 bugs
|
* Fixes of some silly 0.9.0 bugs
|
||||||
|
|
||||||
0.9.0
|
0.9.0
|
||||||
* OI File Manager support
|
* OI File Manager support
|
||||||
* file encryption/decryption
|
* File encryption/decryption
|
||||||
|
|
||||||
0.8.1
|
0.8.1
|
||||||
* display/verify signed-only mails
|
* Display/verify signed-only mails
|
||||||
* bug fixes, layout fixes
|
* Bug fixes, layout fixes
|
||||||
|
|
||||||
0.8.0
|
0.8.0
|
||||||
* create/edit keys
|
* Create/edit keys
|
||||||
* export keys
|
* Export keys
|
||||||
* GUI more Android-like
|
* GUI more Android-like
|
||||||
* a lot of code review, rewriting things
|
* A lot of code review, rewriting things
|
||||||
* tidy up strings and error handling
|
* Tidy up strings and error handling
|
||||||
|
|
||||||
0.7.1
|
0.7.1
|
||||||
* minor fixes, some code review
|
* Minor fixes, some code review
|
||||||
* recognize ElGamal encryption keys as suitable for encryption
|
* Recognize ElGamal encryption keys as suitable for encryption
|
||||||
* allow signing only
|
* Allow signing only
|
||||||
|
|
||||||
0.7.0
|
0.7.0
|
||||||
* initial public release
|
* Initial public release
|
@ -1,4 +1,4 @@
|
|||||||
apply plugin: 'android'
|
apply plugin: 'com.android.application'
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// NOTE: Always use fixed version codes not dynamic ones, e.g. 0.7.3 instead of 0.7.+, see README for more information
|
// 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:SuperToasts:supertoasts')
|
||||||
compile project(':extern:minidns')
|
compile project(':extern:minidns')
|
||||||
compile project(':extern:KeybaseLib:Lib')
|
compile project(':extern:KeybaseLib:Lib')
|
||||||
|
compile project(':extern:TokenAutoComplete:library')
|
||||||
compile project(':extern:openpgp-card-nfc-lib:library')
|
compile project(':extern:openpgp-card-nfc-lib:library')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,6 +49,10 @@
|
|||||||
android:name="android.hardware.touchscreen"
|
android:name="android.hardware.touchscreen"
|
||||||
android:required="false" />
|
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.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.NFC" />
|
<uses-permission android:name="android.permission.NFC" />
|
||||||
@ -152,12 +156,14 @@
|
|||||||
<action android:name="org.sufficientlysecure.keychain.action.ENCRYPT" />
|
<action android:name="org.sufficientlysecure.keychain.action.ENCRYPT" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<!-- TODO: accept other schemes! -->
|
|
||||||
<data android:scheme="file" />
|
<data android:scheme="file" />
|
||||||
|
<data android:scheme="content" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<!-- Android's Send Action -->
|
<!-- Android's Send Action -->
|
||||||
<intent-filter android:label="@string/intent_send_encrypt">
|
<intent-filter android:label="@string/intent_send_encrypt">
|
||||||
<action android:name="android.intent.action.SEND" />
|
<action android:name="android.intent.action.SEND" />
|
||||||
|
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
|
||||||
@ -170,26 +176,16 @@
|
|||||||
android:label="@string/title_decrypt"
|
android:label="@string/title_decrypt"
|
||||||
android:windowSoftInputMode="stateHidden">
|
android:windowSoftInputMode="stateHidden">
|
||||||
|
|
||||||
<!--<!– VIEW with mimeType: TODO (from email app) –>-->
|
<!-- VIEW with mimeType application/pgp-encrypted -->
|
||||||
<!--<intent-filter android:label="@string/intent_import_key">-->
|
<intent-filter android:label="@string/intent_send_decrypt">
|
||||||
<!--<action android:name="android.intent.action.VIEW" />-->
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
<!--<category android:name="android.intent.category.BROWSABLE" />-->
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
<!--<category android:name="android.intent.category.DEFAULT" />-->
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
|
||||||
<!--<!– mime type as defined in http://tools.ietf.org/html/rfc3156 –>-->
|
<!-- mime type as defined in http://tools.ietf.org/html/rfc3156 -->
|
||||||
<!--<data android:mimeType="application/pgp-signature" />-->
|
<data android:mimeType="application/pgp-encrypted" />
|
||||||
<!--</intent-filter>-->
|
</intent-filter>
|
||||||
<!--<!– VIEW with mimeType: TODO (from email app) –>-->
|
|
||||||
<!--<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" />-->
|
|
||||||
|
|
||||||
<!--<!– mime type as defined in http://tools.ietf.org/html/rfc3156 –>-->
|
|
||||||
<!--<data android:mimeType="application/pgp-encrypted" />-->
|
|
||||||
<!--</intent-filter>-->
|
|
||||||
<!-- Keychain's own Actions -->
|
<!-- Keychain's own Actions -->
|
||||||
<!-- DECRYPT with text as extra -->
|
<!-- DECRYPT with text as extra -->
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
@ -202,8 +198,9 @@
|
|||||||
<action android:name="org.sufficientlysecure.keychain.action.DECRYPT" />
|
<action android:name="org.sufficientlysecure.keychain.action.DECRYPT" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<!-- TODO: accept other schemes! -->
|
|
||||||
<data android:scheme="file" />
|
<data android:scheme="file" />
|
||||||
|
<data android:scheme="content" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<!-- Android's Send Action -->
|
<!-- Android's Send Action -->
|
||||||
<intent-filter android:label="@string/intent_send_decrypt">
|
<intent-filter android:label="@string/intent_send_decrypt">
|
||||||
@ -644,6 +641,12 @@
|
|||||||
android:resource="@xml/custom_pgp_contacts_structure" />
|
android:resource="@xml/custom_pgp_contacts_structure" />
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
|
<provider
|
||||||
|
android:name=".provider.TemporaryStorageProvider"
|
||||||
|
android:authorities="org.sufficientlysecure.keychain.tempstorage"
|
||||||
|
android:writePermission="org.sufficientlysecure.keychain.WRITE_TEMPORARY_STORAGE"
|
||||||
|
android:exported="true" />
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
@ -17,16 +17,16 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain;
|
package org.sufficientlysecure.keychain;
|
||||||
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
|
|
||||||
import org.spongycastle.bcpg.CompressionAlgorithmTags;
|
|
||||||
import org.spongycastle.jce.provider.BouncyCastleProvider;
|
import org.spongycastle.jce.provider.BouncyCastleProvider;
|
||||||
import org.sufficientlysecure.keychain.remote.ui.AppsListActivity;
|
import org.sufficientlysecure.keychain.remote.ui.AppsListActivity;
|
||||||
import org.sufficientlysecure.keychain.ui.DecryptActivity;
|
import org.sufficientlysecure.keychain.ui.DecryptActivity;
|
||||||
import org.sufficientlysecure.keychain.ui.EncryptActivity;
|
import org.sufficientlysecure.keychain.ui.EncryptActivity;
|
||||||
import org.sufficientlysecure.keychain.ui.KeyListActivity;
|
import org.sufficientlysecure.keychain.ui.KeyListActivity;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
public final class Constants {
|
public final class Constants {
|
||||||
|
|
||||||
public static final boolean DEBUG = BuildConfig.DEBUG;
|
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 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 class Path {
|
||||||
public static final String APP_DIR = Environment.getExternalStorageDirectory()
|
public static final File APP_DIR = new File(Environment.getExternalStorageDirectory(), "OpenKeychain");
|
||||||
+ "/OpenKeychain";
|
public static final File APP_DIR_FILE = new File(APP_DIR, "export.asc");
|
||||||
public static final String APP_DIR_FILE = APP_DIR + "/export.asc";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class Pref {
|
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 class key {
|
||||||
public static final int none = 0;
|
public static final int none = 0;
|
||||||
public static final int symmetric = -1;
|
public static final int symmetric = -1;
|
||||||
|
@ -28,11 +28,10 @@ import android.os.Environment;
|
|||||||
import org.spongycastle.jce.provider.BouncyCastleProvider;
|
import org.spongycastle.jce.provider.BouncyCastleProvider;
|
||||||
import org.sufficientlysecure.keychain.helper.Preferences;
|
import org.sufficientlysecure.keychain.helper.Preferences;
|
||||||
import org.sufficientlysecure.keychain.helper.TlsHelper;
|
import org.sufficientlysecure.keychain.helper.TlsHelper;
|
||||||
|
import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
import org.sufficientlysecure.keychain.util.PRNGFixes;
|
import org.sufficientlysecure.keychain.util.PRNGFixes;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.security.Provider;
|
|
||||||
import java.security.Security;
|
import java.security.Security;
|
||||||
|
|
||||||
public class KeychainApplication extends Application {
|
public class KeychainApplication extends Application {
|
||||||
@ -73,8 +72,7 @@ public class KeychainApplication extends Application {
|
|||||||
|
|
||||||
// Create APG directory on sdcard if not existing
|
// Create APG directory on sdcard if not existing
|
||||||
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
|
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
|
||||||
File dir = new File(Constants.Path.APP_DIR);
|
if (!Constants.Path.APP_DIR.exists() && !Constants.Path.APP_DIR.mkdirs()) {
|
||||||
if (!dir.exists() && !dir.mkdirs()) {
|
|
||||||
// ignore this for now, it's not crucial
|
// ignore this for now, it's not crucial
|
||||||
// that the directory doesn't exist at this point
|
// that the directory doesn't exist at this point
|
||||||
}
|
}
|
||||||
@ -86,9 +84,11 @@ public class KeychainApplication extends Application {
|
|||||||
setupAccountAsNeeded(this);
|
setupAccountAsNeeded(this);
|
||||||
|
|
||||||
// Update keyserver list as needed
|
// Update keyserver list as needed
|
||||||
Preferences.getPreferences(this).updateKeyServers();
|
Preferences.getPreferences(this).updatePreferences();
|
||||||
|
|
||||||
TlsHelper.addStaticCA("pool.sks-keyservers.net", getAssets(), "sks-keyservers.netCA.cer");
|
TlsHelper.addStaticCA("pool.sks-keyservers.net", getAssets(), "sks-keyservers.netCA.cer");
|
||||||
|
|
||||||
|
TemporaryStorageProvider.cleanUp(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setupAccountAsNeeded(Context context) {
|
public static void setupAccountAsNeeded(Context context) {
|
||||||
|
@ -19,8 +19,13 @@ package org.sufficientlysecure.keychain.helper;
|
|||||||
|
|
||||||
import android.accounts.Account;
|
import android.accounts.Account;
|
||||||
import android.accounts.AccountManager;
|
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.database.Cursor;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.provider.ContactsContract;
|
import android.provider.ContactsContract;
|
||||||
@ -33,7 +38,14 @@ import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
|||||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
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 {
|
public class ContactHelper {
|
||||||
|
|
||||||
@ -60,6 +72,8 @@ public class ContactHelper {
|
|||||||
ContactsContract.Data.RAW_CONTACT_ID + "=? AND " + ContactsContract.Data.MIMETYPE + "=?";
|
ContactsContract.Data.RAW_CONTACT_ID + "=? AND " + ContactsContract.Data.MIMETYPE + "=?";
|
||||||
public static final String ID_SELECTION = ContactsContract.RawContacts._ID + "=?";
|
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) {
|
public static List<String> getPossibleUserEmails(Context context) {
|
||||||
Set<String> accountMails = getAccountEmails(context);
|
Set<String> accountMails = getAccountEmails(context);
|
||||||
accountMails.addAll(getMainProfileContactEmails(context));
|
accountMails.addAll(getMainProfileContactEmails(context));
|
||||||
@ -232,6 +246,30 @@ public class ContactHelper {
|
|||||||
return null;
|
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
|
* Write the current Keychain to the contact db
|
||||||
*/
|
*/
|
||||||
@ -356,7 +394,7 @@ public class ContactHelper {
|
|||||||
int rawContactId, long masterKeyId) {
|
int rawContactId, long masterKeyId) {
|
||||||
ops.add(selectByRawContactAndItemType(ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI),
|
ops.add(selectByRawContactAndItemType(ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI),
|
||||||
rawContactId, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE).build());
|
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);
|
USER_IDS_PROJECTION, NON_REVOKED_SELECTION, null, null);
|
||||||
if (ids != null) {
|
if (ids != null) {
|
||||||
while (ids.moveToNext()) {
|
while (ids.moveToNext()) {
|
||||||
|
@ -21,6 +21,7 @@ import android.content.Context;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Messenger;
|
import android.os.Messenger;
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.keyimport.HkpKeyserver;
|
import org.sufficientlysecure.keychain.keyimport.HkpKeyserver;
|
||||||
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
|
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
|
||||||
import org.sufficientlysecure.keychain.keyimport.Keyserver;
|
import org.sufficientlysecure.keychain.keyimport.Keyserver;
|
||||||
@ -29,6 +30,7 @@ import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
public class EmailKeyHelper {
|
public class EmailKeyHelper {
|
||||||
@ -86,7 +88,7 @@ public class EmailKeyHelper {
|
|||||||
for (ImportKeysListEntry key : keyServer.search(mail)) {
|
for (ImportKeysListEntry key : keyServer.search(mail)) {
|
||||||
if (key.isRevoked() || key.isExpired()) continue;
|
if (key.isRevoked() || key.isExpired()) continue;
|
||||||
for (String userId : key.getUserIds()) {
|
for (String userId : key.getUserIds()) {
|
||||||
if (userId.toLowerCase().contains(mail.toLowerCase())) {
|
if (userId.toLowerCase().contains(mail.toLowerCase(Locale.ENGLISH))) {
|
||||||
keys.add(key);
|
keys.add(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,18 +30,17 @@ import android.widget.Toast;
|
|||||||
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
|
|
||||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||||
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
||||||
import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment;
|
import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment;
|
||||||
import org.sufficientlysecure.keychain.ui.dialog.FileDialogFragment;
|
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
public class ExportHelper {
|
public class ExportHelper {
|
||||||
protected FileDialogFragment mFileDialog;
|
protected File mExportFile;
|
||||||
protected String mExportFilename;
|
|
||||||
|
|
||||||
ActionBarActivity mActivity;
|
ActionBarActivity mActivity;
|
||||||
|
|
||||||
@ -68,28 +67,10 @@ public class ExportHelper {
|
|||||||
/**
|
/**
|
||||||
* Show dialog where to export keys
|
* 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) {
|
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;
|
String title = null;
|
||||||
if (masterKeyIds == null) {
|
if (masterKeyIds == null) {
|
||||||
// export all keys
|
// export all keys
|
||||||
@ -103,12 +84,13 @@ public class ExportHelper {
|
|||||||
String checkMsg = showSecretCheckbox ?
|
String checkMsg = showSecretCheckbox ?
|
||||||
mActivity.getString(R.string.also_export_secret_keys) : null;
|
mActivity.getString(R.string.also_export_secret_keys) : null;
|
||||||
|
|
||||||
mFileDialog = FileDialogFragment.newInstance(messenger, title, message,
|
FileHelper.saveFile(new FileHelper.FileDialogCallback() {
|
||||||
exportFilename, checkMsg);
|
@Override
|
||||||
|
public void onFileSelected(File file, boolean checked) {
|
||||||
mFileDialog.show(mActivity.getSupportFragmentManager(), "fileDialog");
|
mExportFile = file;
|
||||||
|
exportKeys(masterKeyIds, checked);
|
||||||
}
|
}
|
||||||
});
|
}, mActivity.getSupportFragmentManager() ,title, message, exportFile, checkMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -125,7 +107,7 @@ public class ExportHelper {
|
|||||||
// fill values for this action
|
// fill values for this action
|
||||||
Bundle data = new Bundle();
|
Bundle data = new Bundle();
|
||||||
|
|
||||||
data.putString(KeychainIntentService.EXPORT_FILENAME, mExportFilename);
|
data.putString(KeychainIntentService.EXPORT_FILENAME, mExportFile.getAbsolutePath());
|
||||||
data.putBoolean(KeychainIntentService.EXPORT_SECRET, exportSecret);
|
data.putBoolean(KeychainIntentService.EXPORT_SECRET, exportSecret);
|
||||||
|
|
||||||
if (masterKeyIds == null) {
|
if (masterKeyIds == null) {
|
||||||
|
@ -23,15 +23,26 @@ import android.content.ActivityNotFoundException;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Point;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Environment;
|
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.Fragment;
|
||||||
|
import android.support.v4.app.FragmentManager;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
|
||||||
import org.sufficientlysecure.keychain.R;
|
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 {
|
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
|
* Opens the preferred installed file manager on Android and shows a toast if no manager is
|
||||||
* installed.
|
* installed.
|
||||||
*
|
*
|
||||||
* @param activity
|
* @param fragment
|
||||||
* @param filename default selected file, not supported by all file managers
|
* @param last default selected Uri, not supported by all file managers
|
||||||
* @param mimeType can be text/plain for example
|
* @param mimeType can be text/plain for example
|
||||||
* @param requestCode requestCode used to identify the result coming back from file manager to
|
* @param requestCode requestCode used to identify the result coming back from file manager to
|
||||||
* onActivityResult() in your activity
|
* onActivityResult() in your activity
|
||||||
*/
|
*/
|
||||||
public static void openFile(Activity activity, String filename, String mimeType, int requestCode) {
|
public static void openFile(Fragment fragment, Uri last, String mimeType, int requestCode) {
|
||||||
Intent intent = buildFileIntent(filename, mimeType);
|
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||||
|
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||||
|
|
||||||
try {
|
intent.setData(last);
|
||||||
activity.startActivityForResult(intent, requestCode);
|
intent.setType(mimeType);
|
||||||
} 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);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
fragment.startActivityForResult(intent, requestCode);
|
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
|
* Opens the storage browser on Android 4.4 or later for opening a file
|
||||||
|
*
|
||||||
* @param fragment
|
* @param fragment
|
||||||
* @param last default selected file
|
|
||||||
* @param mimeType can be text/plain for example
|
* @param mimeType can be text/plain for example
|
||||||
|
* @param multiple allow file chooser to return multiple files
|
||||||
* @param requestCode used to identify the result coming back from storage browser onActivityResult() in your
|
* @param requestCode used to identify the result coming back from storage browser onActivityResult() in your
|
||||||
*/
|
*/
|
||||||
@TargetApi(Build.VERSION_CODES.KITKAT)
|
@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 intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
|
||||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||||
intent.setData(last);
|
|
||||||
intent.setType(mimeType);
|
intent.setType(mimeType);
|
||||||
|
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, multiple);
|
||||||
|
|
||||||
fragment.startActivityForResult(intent, requestCode);
|
fragment.startActivityForResult(intent, requestCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens the storage browser on Android 4.4 or later for saving a file
|
* Opens the storage browser on Android 4.4 or later for saving a file
|
||||||
|
*
|
||||||
* @param fragment
|
* @param fragment
|
||||||
* @param last default selected file
|
|
||||||
* @param mimeType can be text/plain for example
|
* @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
|
* @param requestCode used to identify the result coming back from storage browser onActivityResult() in your
|
||||||
*/
|
*/
|
||||||
@TargetApi(Build.VERSION_CODES.KITKAT)
|
@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 intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
|
||||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||||
intent.setData(last);
|
|
||||||
intent.setType(mimeType);
|
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);
|
fragment.startActivityForResult(intent, requestCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Intent buildFileIntent(String filename, String mimeType) {
|
public static String getFilename(Context context, Uri uri) {
|
||||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
String filename = null;
|
||||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
try {
|
||||||
|
Cursor cursor = context.getContentResolver().query(uri, new String[]{OpenableColumns.DISPLAY_NAME}, null, null, null);
|
||||||
|
|
||||||
intent.setData(Uri.parse("file://" + filename));
|
if (cursor != null) {
|
||||||
intent.setType(mimeType);
|
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.
|
* Retrieve thumbnail of file, document api feature and thus KitKat only
|
||||||
* <p/>
|
|
||||||
* from https://github.com/iPaulPro/aFileChooser/blob/master/aFileChooser/src/com/ipaulpro/
|
|
||||||
* afilechooser/utils/FileUtils.java
|
|
||||||
*
|
|
||||||
* @param context
|
|
||||||
* @param uri
|
|
||||||
* @return
|
|
||||||
* @author paulburke
|
|
||||||
*/
|
*/
|
||||||
public static String getPath(Context context, Uri uri) {
|
public static Bitmap getThumbnail(Context context, Uri uri, Point size) {
|
||||||
Log.d(Constants.TAG + " File -",
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||||
"Authority: " + uri.getAuthority() + ", Fragment: " + uri.getFragment()
|
return DocumentsContract.getDocumentThumbnail(context.getContentResolver(), uri, size, null);
|
||||||
+ ", Port: " + uri.getPort() + ", Query: " + uri.getQuery() + ", Scheme: "
|
} else {
|
||||||
+ 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();
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String readableFileSize(long size) {
|
||||||
|
if (size <= 0) return "0";
|
||||||
|
final String[] units = new String[]{"B", "KB", "MB", "GB", "TB"};
|
||||||
|
int digitGroups = (int) (Math.log10(size) / Math.log10(1024));
|
||||||
|
return new DecimalFormat("#,##0.#").format(size / Math.pow(1024, digitGroups)) + " " + units[digitGroups];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static interface FileDialogCallback {
|
||||||
|
public void onFileSelected(File file, boolean checked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -21,6 +21,7 @@ package org.sufficientlysecure.keychain.helper;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
|
||||||
|
import org.spongycastle.bcpg.CompressionAlgorithmTags;
|
||||||
import org.spongycastle.bcpg.HashAlgorithmTags;
|
import org.spongycastle.bcpg.HashAlgorithmTags;
|
||||||
import org.spongycastle.openpgp.PGPEncryptedData;
|
import org.spongycastle.openpgp.PGPEncryptedData;
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
@ -99,7 +100,7 @@ public class Preferences {
|
|||||||
|
|
||||||
public int getDefaultMessageCompression() {
|
public int getDefaultMessageCompression() {
|
||||||
return mSharedPreferences.getInt(Constants.Pref.DEFAULT_MESSAGE_COMPRESSION,
|
return mSharedPreferences.getInt(Constants.Pref.DEFAULT_MESSAGE_COMPRESSION,
|
||||||
Constants.choice.compression.zlib);
|
CompressionAlgorithmTags.ZLIB);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDefaultMessageCompression(int value) {
|
public void setDefaultMessageCompression(int value) {
|
||||||
@ -110,7 +111,7 @@ public class Preferences {
|
|||||||
|
|
||||||
public int getDefaultFileCompression() {
|
public int getDefaultFileCompression() {
|
||||||
return mSharedPreferences.getInt(Constants.Pref.DEFAULT_FILE_COMPRESSION,
|
return mSharedPreferences.getInt(Constants.Pref.DEFAULT_FILE_COMPRESSION,
|
||||||
Constants.choice.compression.none);
|
CompressionAlgorithmTags.UNCOMPRESSED);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDefaultFileCompression(int value) {
|
public void setDefaultFileCompression(int value) {
|
||||||
@ -170,7 +171,8 @@ public class Preferences {
|
|||||||
editor.commit();
|
editor.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateKeyServers() {
|
public void updatePreferences() {
|
||||||
|
// migrate keyserver to hkps
|
||||||
if (mSharedPreferences.getInt(Constants.Pref.KEY_SERVERS_DEFAULT_VERSION, 0) !=
|
if (mSharedPreferences.getInt(Constants.Pref.KEY_SERVERS_DEFAULT_VERSION, 0) !=
|
||||||
Constants.Defaults.KEY_SERVERS_VERSION) {
|
Constants.Defaults.KEY_SERVERS_VERSION) {
|
||||||
String[] servers = getKeyServers();
|
String[] servers = getKeyServers();
|
||||||
@ -186,6 +188,11 @@ public class Preferences {
|
|||||||
.putInt(Constants.Pref.KEY_SERVERS_DEFAULT_VERSION, Constants.Defaults.KEY_SERVERS_VERSION)
|
.putInt(Constants.Pref.KEY_SERVERS_DEFAULT_VERSION, Constants.Defaults.KEY_SERVERS_VERSION)
|
||||||
.commit();
|
.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) {
|
public void setConcealPgpApplication(boolean conceal) {
|
||||||
|
@ -18,12 +18,10 @@
|
|||||||
package org.sufficientlysecure.keychain.helper;
|
package org.sufficientlysecure.keychain.helper;
|
||||||
|
|
||||||
import android.content.res.AssetManager;
|
import android.content.res.AssetManager;
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
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.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -34,10 +32,16 @@ import java.security.KeyManagementException;
|
|||||||
import java.security.KeyStore;
|
import java.security.KeyStore;
|
||||||
import java.security.KeyStoreException;
|
import java.security.KeyStoreException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
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.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.net.ssl.HttpsURLConnection;
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.TrustManagerFactory;
|
||||||
|
|
||||||
public class TlsHelper {
|
public class TlsHelper {
|
||||||
|
|
||||||
public static class TlsHelperException extends Exception {
|
public static class TlsHelperException extends Exception {
|
||||||
|
@ -18,11 +18,6 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.keyimport;
|
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.Constants;
|
||||||
import org.sufficientlysecure.keychain.helper.TlsHelper;
|
import org.sufficientlysecure.keychain.helper.TlsHelper;
|
||||||
import org.sufficientlysecure.keychain.pgp.PgpHelper;
|
import org.sufficientlysecure.keychain.pgp.PgpHelper;
|
||||||
@ -45,6 +40,11 @@ import java.util.TimeZone;
|
|||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
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 {
|
public class HkpKeyserver extends Keyserver {
|
||||||
private static class HttpError extends Exception {
|
private static class HttpError extends Exception {
|
||||||
private static final long serialVersionUID = 1718783705229428893L;
|
private static final long serialVersionUID = 1718783705229428893L;
|
||||||
@ -251,14 +251,14 @@ public class HkpKeyserver extends Keyserver {
|
|||||||
data = query(request);
|
data = query(request);
|
||||||
} catch (HttpError e) {
|
} catch (HttpError e) {
|
||||||
if (e.getData() != null) {
|
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!
|
// NOTE: This is also a 404 error for some keyservers!
|
||||||
return results;
|
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();
|
throw new TooManyResponsesException();
|
||||||
} else if (e.getData().toLowerCase(Locale.US).contains("insufficient")) {
|
} else if (e.getData().toLowerCase(Locale.ENGLISH).contains("insufficient")) {
|
||||||
throw new QueryTooShortException();
|
throw new QueryTooShortException();
|
||||||
} else if (e.getCode() == 404) {
|
} else if (e.getCode() == 404) {
|
||||||
// NOTE: handle this 404 at last, maybe it was a "no keys found" error
|
// 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
|
// group 1 contains the full fingerprint (v4) or the long key id if available
|
||||||
// see http://bit.ly/1d4bxbk and http://bit.ly/1gD1wwr
|
// 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) {
|
if (fingerprintOrKeyId.length() > 16) {
|
||||||
entry.setFingerprintHex(fingerprintOrKeyId);
|
entry.setFingerprintHex(fingerprintOrKeyId);
|
||||||
entry.setKeyIdHex("0x" + fingerprintOrKeyId.substring(fingerprintOrKeyId.length()
|
entry.setKeyIdHex("0x" + fingerprintOrKeyId.substring(fingerprintOrKeyId.length()
|
||||||
|
@ -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;
|
package org.sufficientlysecure.keychain.keyimport;
|
||||||
|
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
|
@ -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;
|
package org.sufficientlysecure.keychain.pgp;
|
||||||
|
|
||||||
import org.spongycastle.openpgp.PGPKeyRing;
|
import org.spongycastle.openpgp.PGPKeyRing;
|
||||||
|
@ -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;
|
package org.sufficientlysecure.keychain.pgp;
|
||||||
|
|
||||||
import org.spongycastle.openpgp.PGPPublicKey;
|
import org.spongycastle.openpgp.PGPPublicKey;
|
||||||
|
@ -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;
|
package org.sufficientlysecure.keychain.pgp;
|
||||||
|
|
||||||
import org.spongycastle.bcpg.ArmoredOutputStream;
|
import org.spongycastle.bcpg.ArmoredOutputStream;
|
||||||
|
@ -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;
|
package org.sufficientlysecure.keychain.pgp;
|
||||||
|
|
||||||
import org.spongycastle.bcpg.HashAlgorithmTags;
|
import org.spongycastle.bcpg.HashAlgorithmTags;
|
||||||
|
@ -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;
|
package org.sufficientlysecure.keychain.pgp;
|
||||||
|
|
||||||
import org.spongycastle.bcpg.S2K;
|
import org.spongycastle.bcpg.S2K;
|
||||||
@ -6,7 +23,6 @@ import org.spongycastle.openpgp.PGPKeyRing;
|
|||||||
import org.spongycastle.openpgp.PGPObjectFactory;
|
import org.spongycastle.openpgp.PGPObjectFactory;
|
||||||
import org.spongycastle.openpgp.PGPPrivateKey;
|
import org.spongycastle.openpgp.PGPPrivateKey;
|
||||||
import org.spongycastle.openpgp.PGPPublicKey;
|
import org.spongycastle.openpgp.PGPPublicKey;
|
||||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
|
||||||
import org.spongycastle.openpgp.PGPSecretKey;
|
import org.spongycastle.openpgp.PGPSecretKey;
|
||||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||||
import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
|
import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
|
||||||
|
@ -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;
|
package org.sufficientlysecure.keychain.pgp;
|
||||||
|
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
@ -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;
|
package org.sufficientlysecure.keychain.pgp;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -24,7 +24,7 @@ import android.text.Spannable;
|
|||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
import android.text.style.ForegroundColorSpan;
|
import android.text.style.ForegroundColorSpan;
|
||||||
|
|
||||||
import org.spongycastle.openpgp.PGPPublicKey;
|
import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
|
||||||
import org.spongycastle.util.encoders.Hex;
|
import org.spongycastle.util.encoders.Hex;
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
@ -37,9 +37,6 @@ import java.util.Locale;
|
|||||||
|
|
||||||
public class PgpKeyHelper {
|
public class PgpKeyHelper {
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO: Only used in HkpKeyServer. Get rid of this one!
|
|
||||||
*/
|
|
||||||
public static String getAlgorithmInfo(int algorithm) {
|
public static String getAlgorithmInfo(int algorithm) {
|
||||||
return getAlgorithmInfo(null, algorithm, 0);
|
return getAlgorithmInfo(null, algorithm, 0);
|
||||||
}
|
}
|
||||||
@ -55,25 +52,25 @@ public class PgpKeyHelper {
|
|||||||
String algorithmStr;
|
String algorithmStr;
|
||||||
|
|
||||||
switch (algorithm) {
|
switch (algorithm) {
|
||||||
case PGPPublicKey.RSA_ENCRYPT:
|
case PublicKeyAlgorithmTags.RSA_ENCRYPT:
|
||||||
case PGPPublicKey.RSA_GENERAL:
|
case PublicKeyAlgorithmTags.RSA_GENERAL:
|
||||||
case PGPPublicKey.RSA_SIGN: {
|
case PublicKeyAlgorithmTags.RSA_SIGN: {
|
||||||
algorithmStr = "RSA";
|
algorithmStr = "RSA";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case PGPPublicKey.DSA: {
|
case PublicKeyAlgorithmTags.DSA: {
|
||||||
algorithmStr = "DSA";
|
algorithmStr = "DSA";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case PGPPublicKey.ELGAMAL_ENCRYPT:
|
case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT:
|
||||||
case PGPPublicKey.ELGAMAL_GENERAL: {
|
case PublicKeyAlgorithmTags.ELGAMAL_GENERAL: {
|
||||||
algorithmStr = "ElGamal";
|
algorithmStr = "ElGamal";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case PGPPublicKey.ECDSA:
|
case PublicKeyAlgorithmTags.ECDSA:
|
||||||
case PGPPublicKey.ECDH: {
|
case PublicKeyAlgorithmTags.ECDH: {
|
||||||
algorithmStr = "ECC";
|
algorithmStr = "ECC";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -82,7 +79,6 @@ public class PgpKeyHelper {
|
|||||||
if (context != null) {
|
if (context != null) {
|
||||||
algorithmStr = context.getResources().getString(R.string.unknown_algorithm);
|
algorithmStr = context.getResources().getString(R.string.unknown_algorithm);
|
||||||
} else {
|
} else {
|
||||||
// TODO
|
|
||||||
algorithmStr = "unknown";
|
algorithmStr = "unknown";
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -104,7 +100,7 @@ public class PgpKeyHelper {
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public static String convertFingerprintToHex(byte[] fingerprint) {
|
public static String convertFingerprintToHex(byte[] fingerprint) {
|
||||||
String hexString = Hex.toHexString(fingerprint).toLowerCase(Locale.US);
|
String hexString = Hex.toHexString(fingerprint).toLowerCase(Locale.ENGLISH);
|
||||||
|
|
||||||
return hexString;
|
return hexString;
|
||||||
}
|
}
|
||||||
@ -133,7 +129,7 @@ public class PgpKeyHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static String convertKeyIdToHex32bit(long keyId) {
|
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) {
|
while (hexString.length() < 8) {
|
||||||
hexString = "0" + hexString;
|
hexString = "0" + hexString;
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ package org.sufficientlysecure.keychain.pgp;
|
|||||||
|
|
||||||
import org.spongycastle.bcpg.CompressionAlgorithmTags;
|
import org.spongycastle.bcpg.CompressionAlgorithmTags;
|
||||||
import org.spongycastle.bcpg.HashAlgorithmTags;
|
import org.spongycastle.bcpg.HashAlgorithmTags;
|
||||||
|
import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
|
||||||
import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags;
|
import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags;
|
||||||
import org.spongycastle.bcpg.sig.KeyFlags;
|
import org.spongycastle.bcpg.sig.KeyFlags;
|
||||||
import org.spongycastle.jce.spec.ElGamalParameterSpec;
|
import org.spongycastle.jce.spec.ElGamalParameterSpec;
|
||||||
@ -138,7 +139,7 @@ public class PgpKeyOperation {
|
|||||||
KeyPairGenerator keyGen;
|
KeyPairGenerator keyGen;
|
||||||
|
|
||||||
switch (algorithmChoice) {
|
switch (algorithmChoice) {
|
||||||
case Constants.choice.algorithm.dsa: {
|
case PublicKeyAlgorithmTags.DSA: {
|
||||||
progress(R.string.progress_generating_dsa, 30);
|
progress(R.string.progress_generating_dsa, 30);
|
||||||
keyGen = KeyPairGenerator.getInstance("DSA", Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
keyGen = KeyPairGenerator.getInstance("DSA", Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||||
keyGen.initialize(keySize, new SecureRandom());
|
keyGen.initialize(keySize, new SecureRandom());
|
||||||
@ -146,7 +147,7 @@ public class PgpKeyOperation {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case Constants.choice.algorithm.elgamal: {
|
case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT: {
|
||||||
progress(R.string.progress_generating_elgamal, 30);
|
progress(R.string.progress_generating_elgamal, 30);
|
||||||
keyGen = KeyPairGenerator.getInstance("ElGamal", Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
keyGen = KeyPairGenerator.getInstance("ElGamal", Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||||
BigInteger p = Primes.getBestPrime(keySize);
|
BigInteger p = Primes.getBestPrime(keySize);
|
||||||
@ -159,7 +160,7 @@ public class PgpKeyOperation {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case Constants.choice.algorithm.rsa: {
|
case PublicKeyAlgorithmTags.RSA_GENERAL: {
|
||||||
progress(R.string.progress_generating_rsa, 30);
|
progress(R.string.progress_generating_rsa, 30);
|
||||||
keyGen = KeyPairGenerator.getInstance("RSA", Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
keyGen = KeyPairGenerator.getInstance("RSA", Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||||
keyGen.initialize(keySize, new SecureRandom());
|
keyGen.initialize(keySize, new SecureRandom());
|
||||||
@ -217,7 +218,7 @@ public class PgpKeyOperation {
|
|||||||
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
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);
|
log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_MASTER_ELGAMAL, indent);
|
||||||
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
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()));
|
progress(R.string.progress_modify_adduid, (i-1) * (100 / saveParcel.mAddUserIds.size()));
|
||||||
String userId = saveParcel.mAddUserIds.get(i);
|
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("")) {
|
if (userId.equals("")) {
|
||||||
log.add(LogLevel.ERROR, LogType.MSG_MF_UID_ERROR_EMPTY, indent+1);
|
log.add(LogLevel.ERROR, LogType.MSG_MF_UID_ERROR_EMPTY, indent+1);
|
||||||
@ -768,7 +769,7 @@ public class PgpKeyOperation {
|
|||||||
PGPSignatureSubpacketGenerator unhashedPacketsGen = new PGPSignatureSubpacketGenerator();
|
PGPSignatureSubpacketGenerator unhashedPacketsGen = new PGPSignatureSubpacketGenerator();
|
||||||
|
|
||||||
// If this key can sign, we need a primary key binding signature
|
// 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
|
// cross-certify signing keys
|
||||||
PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator();
|
PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator();
|
||||||
subHashedPacketsGen.setSignatureCreationTime(false, todayDate);
|
subHashedPacketsGen.setSignatureCreationTime(false, todayDate);
|
||||||
|
@ -20,6 +20,7 @@ package org.sufficientlysecure.keychain.pgp;
|
|||||||
|
|
||||||
import org.spongycastle.bcpg.ArmoredOutputStream;
|
import org.spongycastle.bcpg.ArmoredOutputStream;
|
||||||
import org.spongycastle.bcpg.BCPGOutputStream;
|
import org.spongycastle.bcpg.BCPGOutputStream;
|
||||||
|
import org.spongycastle.bcpg.CompressionAlgorithmTags;
|
||||||
import org.spongycastle.openpgp.PGPCompressedDataGenerator;
|
import org.spongycastle.openpgp.PGPCompressedDataGenerator;
|
||||||
import org.spongycastle.openpgp.PGPEncryptedDataGenerator;
|
import org.spongycastle.openpgp.PGPEncryptedDataGenerator;
|
||||||
import org.spongycastle.openpgp.PGPException;
|
import org.spongycastle.openpgp.PGPException;
|
||||||
@ -116,7 +117,7 @@ public class PgpSignEncrypt {
|
|||||||
// optional
|
// optional
|
||||||
private Progressable mProgressable = null;
|
private Progressable mProgressable = null;
|
||||||
private boolean mEnableAsciiArmorOutput = false;
|
private boolean mEnableAsciiArmorOutput = false;
|
||||||
private int mCompressionId = Constants.choice.compression.none;
|
private int mCompressionId = CompressionAlgorithmTags.UNCOMPRESSED;
|
||||||
private long[] mEncryptionMasterKeyIds = null;
|
private long[] mEncryptionMasterKeyIds = null;
|
||||||
private String mSymmetricPassphrase = null;
|
private String mSymmetricPassphrase = null;
|
||||||
private int mSymmetricEncryptionAlgorithm = 0;
|
private int mSymmetricEncryptionAlgorithm = 0;
|
||||||
@ -264,7 +265,7 @@ public class PgpSignEncrypt {
|
|||||||
boolean enableSignature = mSignatureMasterKeyId != Constants.key.none;
|
boolean enableSignature = mSignatureMasterKeyId != Constants.key.none;
|
||||||
boolean enableEncryption = ((mEncryptionMasterKeyIds != null && mEncryptionMasterKeyIds.length > 0)
|
boolean enableEncryption = ((mEncryptionMasterKeyIds != null && mEncryptionMasterKeyIds.length > 0)
|
||||||
|| mSymmetricPassphrase != null);
|
|| mSymmetricPassphrase != null);
|
||||||
boolean enableCompression = (mCompressionId != Constants.choice.compression.none);
|
boolean enableCompression = (mCompressionId != CompressionAlgorithmTags.UNCOMPRESSED);
|
||||||
|
|
||||||
Log.d(Constants.TAG, "enableSignature:" + enableSignature
|
Log.d(Constants.TAG, "enableSignature:" + enableSignature
|
||||||
+ "\nenableEncryption:" + enableEncryption
|
+ "\nenableEncryption:" + enableEncryption
|
||||||
|
@ -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;
|
package org.sufficientlysecure.keychain.pgp;
|
||||||
|
|
||||||
import org.spongycastle.bcpg.ArmoredOutputStream;
|
import org.spongycastle.bcpg.ArmoredOutputStream;
|
||||||
|
@ -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;
|
package org.sufficientlysecure.keychain.pgp;
|
||||||
|
|
||||||
import org.spongycastle.bcpg.sig.KeyFlags;
|
import org.spongycastle.bcpg.sig.KeyFlags;
|
||||||
|
@ -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;
|
package org.sufficientlysecure.keychain.pgp;
|
||||||
|
|
||||||
import org.spongycastle.bcpg.sig.KeyFlags;
|
import org.spongycastle.bcpg.sig.KeyFlags;
|
||||||
|
@ -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;
|
package org.sufficientlysecure.keychain.pgp;
|
||||||
|
|
||||||
import org.spongycastle.bcpg.SignatureSubpacket;
|
import org.spongycastle.bcpg.SignatureSubpacket;
|
||||||
|
@ -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;
|
package org.sufficientlysecure.keychain.provider;
|
||||||
|
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
@ -201,7 +218,7 @@ public class CachedPublicKeyRing extends KeyRing {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Cursor getSubkeys() throws PgpGeneralException {
|
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);
|
return mProviderHelper.getContentResolver().query(keysUri, null, null, null, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -172,8 +172,8 @@ public class KeychainContract {
|
|||||||
return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).build();
|
return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Uri buildPublicKeyRingUri(String masterKeyId) {
|
public static Uri buildPublicKeyRingUri(long masterKeyId) {
|
||||||
return CONTENT_URI.buildUpon().appendPath(masterKeyId).appendPath(PATH_PUBLIC).build();
|
return CONTENT_URI.buildUpon().appendPath(Long.toString(masterKeyId)).appendPath(PATH_PUBLIC).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Uri buildPublicKeyRingUri(Uri uri) {
|
public static Uri buildPublicKeyRingUri(Uri uri) {
|
||||||
@ -184,8 +184,8 @@ public class KeychainContract {
|
|||||||
return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).build();
|
return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Uri buildSecretKeyRingUri(String masterKeyId) {
|
public static Uri buildSecretKeyRingUri(long masterKeyId) {
|
||||||
return CONTENT_URI.buildUpon().appendPath(masterKeyId).appendPath(PATH_SECRET).build();
|
return CONTENT_URI.buildUpon().appendPath(Long.toString(masterKeyId)).appendPath(PATH_SECRET).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Uri buildSecretKeyRingUri(Uri uri) {
|
public static Uri buildSecretKeyRingUri(Uri uri) {
|
||||||
@ -210,8 +210,8 @@ public class KeychainContract {
|
|||||||
public static final String CONTENT_ITEM_TYPE
|
public static final String CONTENT_ITEM_TYPE
|
||||||
= "vnd.android.cursor.item/vnd.org.sufficientlysecure.keychain.provider.keychain.keys";
|
= "vnd.android.cursor.item/vnd.org.sufficientlysecure.keychain.provider.keychain.keys";
|
||||||
|
|
||||||
public static Uri buildKeysUri(String masterKeyId) {
|
public static Uri buildKeysUri(long masterKeyId) {
|
||||||
return CONTENT_URI.buildUpon().appendPath(masterKeyId).appendPath(PATH_KEYS).build();
|
return CONTENT_URI.buildUpon().appendPath(Long.toString(masterKeyId)).appendPath(PATH_KEYS).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Uri buildKeysUri(Uri uri) {
|
public static Uri buildKeysUri(Uri uri) {
|
||||||
@ -237,8 +237,8 @@ public class KeychainContract {
|
|||||||
public static final String CONTENT_ITEM_TYPE
|
public static final String CONTENT_ITEM_TYPE
|
||||||
= "vnd.android.cursor.item/vnd.org.sufficientlysecure.keychain.provider.user_ids";
|
= "vnd.android.cursor.item/vnd.org.sufficientlysecure.keychain.provider.user_ids";
|
||||||
|
|
||||||
public static Uri buildUserIdsUri(String masterKeyId) {
|
public static Uri buildUserIdsUri(long masterKeyId) {
|
||||||
return CONTENT_URI.buildUpon().appendPath(masterKeyId).appendPath(PATH_USER_IDS).build();
|
return CONTENT_URI.buildUpon().appendPath(Long.toString(masterKeyId)).appendPath(PATH_USER_IDS).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Uri buildUserIdsUri(Uri uri) {
|
public static Uri buildUserIdsUri(Uri uri) {
|
||||||
@ -304,12 +304,14 @@ public class KeychainContract {
|
|||||||
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
|
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
|
||||||
.appendPath(BASE_KEY_RINGS).build();
|
.appendPath(BASE_KEY_RINGS).build();
|
||||||
|
|
||||||
public static Uri buildCertsUri(String masterKeyId) {
|
public static Uri buildCertsUri(long masterKeyId) {
|
||||||
return CONTENT_URI.buildUpon().appendPath(masterKeyId).appendPath(PATH_CERTS).build();
|
return CONTENT_URI.buildUpon().appendPath(Long.toString(masterKeyId)).appendPath(PATH_CERTS).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Uri buildCertsSpecificUri(String masterKeyId, String rank, String certifier) {
|
public static Uri buildCertsSpecificUri(long masterKeyId, long rank, long certifier) {
|
||||||
return CONTENT_URI.buildUpon().appendPath(masterKeyId).appendPath(PATH_CERTS).appendPath(rank).appendPath(certifier).build();
|
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) {
|
public static Uri buildCertsUri(Uri uri) {
|
||||||
|
@ -316,42 +316,37 @@ public class KeychainDatabase extends SQLiteOpenHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void copy(File in, File out) throws IOException {
|
private static void copy(File in, File out) throws IOException {
|
||||||
FileInputStream ss = new FileInputStream(in);
|
FileInputStream is = new FileInputStream(in);
|
||||||
FileOutputStream ds = new FileOutputStream(out);
|
FileOutputStream os = new FileOutputStream(out);
|
||||||
byte[] buf = new byte[512];
|
byte[] buf = new byte[512];
|
||||||
while (ss.available() > 0) {
|
while (is.available() > 0) {
|
||||||
int count = ss.read(buf, 0, 512);
|
int count = is.read(buf, 0, 512);
|
||||||
ds.write(buf, 0, count);
|
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) {
|
if (!Constants.DEBUG) {
|
||||||
return;
|
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()) {
|
if (!in.canRead()) {
|
||||||
throw new IOException("Cannot read " + in.getName());
|
throw new IOException("Cannot read " + in.getName());
|
||||||
}
|
}
|
||||||
if (!out.canRead()) {
|
if (!out.canWrite()) {
|
||||||
throw new IOException("Cannot write " + out.getName());
|
throw new IOException("Cannot write " + out.getName());
|
||||||
}
|
}
|
||||||
copy(in, out);
|
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -28,17 +28,14 @@ import android.os.RemoteException;
|
|||||||
import android.support.v4.util.LongSparseArray;
|
import android.support.v4.util.LongSparseArray;
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKey;
|
||||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
|
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
|
||||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
|
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
|
||||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||||
import org.sufficientlysecure.keychain.pgp.NullProgressable;
|
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.PgpHelper;
|
||||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.Progressable;
|
||||||
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
||||||
import org.sufficientlysecure.keychain.pgp.UncachedPublicKey;
|
import org.sufficientlysecure.keychain.pgp.UncachedPublicKey;
|
||||||
import org.sufficientlysecure.keychain.pgp.WrappedSignature;
|
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.provider.KeychainContract.UserIds;
|
||||||
import org.sufficientlysecure.keychain.remote.AccountSettings;
|
import org.sufficientlysecure.keychain.remote.AccountSettings;
|
||||||
import org.sufficientlysecure.keychain.remote.AppSettings;
|
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.service.OperationResults.SaveKeyringResult;
|
||||||
import org.sufficientlysecure.keychain.util.IterableIterator;
|
import org.sufficientlysecure.keychain.util.IterableIterator;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
@ -299,7 +299,7 @@ public class ProviderHelper {
|
|||||||
return SaveKeyringResult.RESULT_ERROR;
|
return SaveKeyringResult.RESULT_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
Uri uri = KeyRingData.buildPublicKeyRingUri(Long.toString(masterKeyId));
|
Uri uri = KeyRingData.buildPublicKeyRingUri(masterKeyId);
|
||||||
operations.add(ContentProviderOperation.newInsert(uri).withValues(values).build());
|
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);
|
progress.setProgress(LogType.MSG_IP_INSERT_SUBKEYS.getMsgId(), 40, 100);
|
||||||
mIndent += 1;
|
mIndent += 1;
|
||||||
{ // insert subkeys
|
{ // insert subkeys
|
||||||
Uri uri = Keys.buildKeysUri(Long.toString(masterKeyId));
|
Uri uri = Keys.buildKeysUri(masterKeyId);
|
||||||
int rank = 0;
|
int rank = 0;
|
||||||
for (CanonicalizedPublicKey key : keyRing.publicKeyIterator()) {
|
for (CanonicalizedPublicKey key : keyRing.publicKeyIterator()) {
|
||||||
long keyId = key.getKeyId();
|
long keyId = key.getKeyId();
|
||||||
@ -498,7 +498,7 @@ public class ProviderHelper {
|
|||||||
try {
|
try {
|
||||||
// delete old version of this keyRing, which also deletes all keys and userIds on cascade
|
// delete old version of this keyRing, which also deletes all keys and userIds on cascade
|
||||||
int deleted = mContentResolver.delete(
|
int deleted = mContentResolver.delete(
|
||||||
KeyRingData.buildPublicKeyRingUri(Long.toString(masterKeyId)), null, null);
|
KeyRingData.buildPublicKeyRingUri(masterKeyId), null, null);
|
||||||
if (deleted > 0) {
|
if (deleted > 0) {
|
||||||
log(LogLevel.DEBUG, LogType.MSG_IP_DELETE_OLD_OK);
|
log(LogLevel.DEBUG, LogType.MSG_IP_DELETE_OLD_OK);
|
||||||
result |= SaveKeyringResult.UPDATED;
|
result |= SaveKeyringResult.UPDATED;
|
||||||
@ -567,7 +567,7 @@ public class ProviderHelper {
|
|||||||
values.put(KeyRingData.MASTER_KEY_ID, masterKeyId);
|
values.put(KeyRingData.MASTER_KEY_ID, masterKeyId);
|
||||||
values.put(KeyRingData.KEY_RING_DATA, keyRing.getEncoded());
|
values.put(KeyRingData.KEY_RING_DATA, keyRing.getEncoded());
|
||||||
// insert new version of this keyRing
|
// insert new version of this keyRing
|
||||||
Uri uri = KeyRingData.buildSecretKeyRingUri(Long.toString(masterKeyId));
|
Uri uri = KeyRingData.buildSecretKeyRingUri(masterKeyId);
|
||||||
if (mContentResolver.insert(uri, values) == null) {
|
if (mContentResolver.insert(uri, values) == null) {
|
||||||
log(LogLevel.ERROR, LogType.MSG_IS_DB_EXCEPTION);
|
log(LogLevel.ERROR, LogType.MSG_IS_DB_EXCEPTION);
|
||||||
return SaveKeyringResult.RESULT_ERROR;
|
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
|
// first, mark all keys as not available
|
||||||
ContentValues values = new ContentValues();
|
ContentValues values = new ContentValues();
|
||||||
@ -836,7 +836,7 @@ public class ProviderHelper {
|
|||||||
values.put(Certs.VERIFIED, verified);
|
values.put(Certs.VERIFIED, verified);
|
||||||
values.put(Certs.DATA, cert.getEncoded());
|
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();
|
return ContentProviderOperation.newInsert(uri).withValues(values).build();
|
||||||
}
|
}
|
||||||
@ -853,7 +853,7 @@ public class ProviderHelper {
|
|||||||
values.put(UserIds.IS_REVOKED, item.isRevoked);
|
values.put(UserIds.IS_REVOKED, item.isRevoked);
|
||||||
values.put(UserIds.RANK, rank);
|
values.put(UserIds.RANK, rank);
|
||||||
|
|
||||||
Uri uri = UserIds.buildUserIdsUri(Long.toString(masterKeyId));
|
Uri uri = UserIds.buildUserIdsUri(masterKeyId);
|
||||||
|
|
||||||
return ContentProviderOperation.newInsert(uri).withValues(values).build();
|
return ContentProviderOperation.newInsert(uri).withValues(values).build();
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.remote;
|
package org.sufficientlysecure.keychain.remote;
|
||||||
|
|
||||||
|
import org.spongycastle.bcpg.CompressionAlgorithmTags;
|
||||||
import org.spongycastle.bcpg.HashAlgorithmTags;
|
import org.spongycastle.bcpg.HashAlgorithmTags;
|
||||||
import org.spongycastle.openpgp.PGPEncryptedData;
|
import org.spongycastle.openpgp.PGPEncryptedData;
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
@ -39,7 +40,7 @@ public class AccountSettings {
|
|||||||
// defaults:
|
// defaults:
|
||||||
this.mEncryptionAlgorithm = PGPEncryptedData.AES_256;
|
this.mEncryptionAlgorithm = PGPEncryptedData.AES_256;
|
||||||
this.mHashAlgorithm = HashAlgorithmTags.SHA512;
|
this.mHashAlgorithm = HashAlgorithmTags.SHA512;
|
||||||
this.mCompression = Constants.choice.compression.zlib;
|
this.mCompression = CompressionAlgorithmTags.ZLIB;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getAccountName() {
|
public String getAccountName() {
|
||||||
|
@ -26,9 +26,9 @@ import android.view.View;
|
|||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.AdapterView;
|
import android.widget.AdapterView;
|
||||||
import android.widget.AdapterView.OnItemSelectedListener;
|
import android.widget.AdapterView.OnItemSelectedListener;
|
||||||
|
import android.widget.Button;
|
||||||
import android.widget.Spinner;
|
import android.widget.Spinner;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Button;
|
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
|
@ -23,7 +23,13 @@ import android.content.AbstractThreadedSyncAdapter;
|
|||||||
import android.content.ContentProviderClient;
|
import android.content.ContentProviderClient;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SyncResult;
|
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.Constants;
|
||||||
import org.sufficientlysecure.keychain.KeychainApplication;
|
import org.sufficientlysecure.keychain.KeychainApplication;
|
||||||
import org.sufficientlysecure.keychain.helper.ContactHelper;
|
import org.sufficientlysecure.keychain.helper.ContactHelper;
|
||||||
|
@ -29,6 +29,7 @@ import android.os.Handler;
|
|||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
@ -24,7 +24,6 @@ import android.net.Uri;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
import android.os.Messenger;
|
import android.os.Messenger;
|
||||||
import android.os.Parcel;
|
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
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.FileHelper;
|
||||||
import org.sufficientlysecure.keychain.helper.OtherHelper;
|
import org.sufficientlysecure.keychain.helper.OtherHelper;
|
||||||
import org.sufficientlysecure.keychain.helper.Preferences;
|
import org.sufficientlysecure.keychain.helper.Preferences;
|
||||||
import org.sufficientlysecure.keychain.util.FileImportCache;
|
|
||||||
import org.sufficientlysecure.keychain.keyimport.HkpKeyserver;
|
import org.sufficientlysecure.keychain.keyimport.HkpKeyserver;
|
||||||
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
|
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
|
||||||
import org.sufficientlysecure.keychain.keyimport.KeybaseKeyserver;
|
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.provider.ProviderHelper;
|
||||||
import org.sufficientlysecure.keychain.service.OperationResults.EditKeyResult;
|
import org.sufficientlysecure.keychain.service.OperationResults.EditKeyResult;
|
||||||
import org.sufficientlysecure.keychain.service.OperationResults.ImportKeyResult;
|
import org.sufficientlysecure.keychain.service.OperationResults.ImportKeyResult;
|
||||||
|
import org.sufficientlysecure.keychain.util.FileImportCache;
|
||||||
import org.sufficientlysecure.keychain.util.InputData;
|
import org.sufficientlysecure.keychain.util.InputData;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
import org.sufficientlysecure.keychain.util.ProgressScaler;
|
import org.sufficientlysecure.keychain.util.ProgressScaler;
|
||||||
@ -63,11 +62,9 @@ import org.sufficientlysecure.keychain.util.ProgressScaler;
|
|||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -110,8 +107,10 @@ public class KeychainIntentService extends IntentService
|
|||||||
public static final String SOURCE = "source";
|
public static final String SOURCE = "source";
|
||||||
// possible targets:
|
// possible targets:
|
||||||
public static final int IO_BYTES = 1;
|
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 = 2;
|
||||||
public static final int IO_URI = 3;
|
public static final int IO_URIS = 3;
|
||||||
|
|
||||||
|
public static final String SELECTED_URI = "selected_uri";
|
||||||
|
|
||||||
// encrypt
|
// encrypt
|
||||||
public static final String ENCRYPT_SIGNATURE_KEY_ID = "secret_key_id";
|
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_MESSAGE_BYTES = "message_bytes";
|
||||||
public static final String ENCRYPT_INPUT_FILE = "input_file";
|
public static final String ENCRYPT_INPUT_FILE = "input_file";
|
||||||
public static final String ENCRYPT_INPUT_URI = "input_uri";
|
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_FILE = "output_file";
|
||||||
public static final String ENCRYPT_OUTPUT_URI = "output_uri";
|
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";
|
public static final String ENCRYPT_SYMMETRIC_PASSPHRASE = "passphrase";
|
||||||
|
|
||||||
// decrypt/verify
|
// decrypt/verify
|
||||||
@ -142,6 +143,7 @@ public class KeychainIntentService extends IntentService
|
|||||||
// export key
|
// export key
|
||||||
public static final String EXPORT_OUTPUT_STREAM = "export_output_stream";
|
public static final String EXPORT_OUTPUT_STREAM = "export_output_stream";
|
||||||
public static final String EXPORT_FILENAME = "export_filename";
|
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_SECRET = "export_secret";
|
||||||
public static final String EXPORT_ALL = "export_all";
|
public static final String EXPORT_ALL = "export_all";
|
||||||
public static final String EXPORT_KEY_RING_MASTER_KEY_ID = "export_key_ring_id";
|
public static final String EXPORT_KEY_RING_MASTER_KEY_ID = "export_key_ring_id";
|
||||||
@ -226,6 +228,7 @@ public class KeychainIntentService extends IntentService
|
|||||||
try {
|
try {
|
||||||
/* Input */
|
/* Input */
|
||||||
int source = data.get(SOURCE) != null ? data.getInt(SOURCE) : data.getInt(TARGET);
|
int source = data.get(SOURCE) != null ? data.getInt(SOURCE) : data.getInt(TARGET);
|
||||||
|
Bundle resultData = new Bundle();
|
||||||
|
|
||||||
long signatureKeyId = data.getLong(ENCRYPT_SIGNATURE_KEY_ID);
|
long signatureKeyId = data.getLong(ENCRYPT_SIGNATURE_KEY_ID);
|
||||||
String symmetricPassphrase = data.getString(ENCRYPT_SYMMETRIC_PASSPHRASE);
|
String symmetricPassphrase = data.getString(ENCRYPT_SYMMETRIC_PASSPHRASE);
|
||||||
@ -233,6 +236,9 @@ public class KeychainIntentService extends IntentService
|
|||||||
boolean useAsciiArmor = data.getBoolean(ENCRYPT_USE_ASCII_ARMOR);
|
boolean useAsciiArmor = data.getBoolean(ENCRYPT_USE_ASCII_ARMOR);
|
||||||
long encryptionKeyIds[] = data.getLongArray(ENCRYPT_ENCRYPTION_KEYS_IDS);
|
long encryptionKeyIds[] = data.getLongArray(ENCRYPT_ENCRYPTION_KEYS_IDS);
|
||||||
int compressionId = data.getInt(ENCRYPT_COMPRESSION_ID);
|
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);
|
InputData inputData = createEncryptInputData(data);
|
||||||
OutputStream outStream = createCryptOutputStream(data);
|
OutputStream outStream = createCryptOutputStream(data);
|
||||||
|
|
||||||
@ -268,9 +274,10 @@ public class KeychainIntentService extends IntentService
|
|||||||
|
|
||||||
/* Output */
|
/* Output */
|
||||||
|
|
||||||
Bundle resultData = new Bundle();
|
|
||||||
finalizeEncryptOutputStream(data, resultData, outStream);
|
finalizeEncryptOutputStream(data, resultData, outStream);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
OtherHelper.logDebugBundle(resultData, "resultData");
|
OtherHelper.logDebugBundle(resultData, "resultData");
|
||||||
|
|
||||||
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData);
|
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData);
|
||||||
@ -415,14 +422,17 @@ public class KeychainIntentService extends IntentService
|
|||||||
boolean exportSecret = data.getBoolean(EXPORT_SECRET, false);
|
boolean exportSecret = data.getBoolean(EXPORT_SECRET, false);
|
||||||
long[] masterKeyIds = data.getLongArray(EXPORT_KEY_RING_MASTER_KEY_ID);
|
long[] masterKeyIds = data.getLongArray(EXPORT_KEY_RING_MASTER_KEY_ID);
|
||||||
String outputFile = data.getString(EXPORT_FILENAME);
|
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
|
// If not exporting all keys get the masterKeyIds of the keys to export from the intent
|
||||||
boolean exportAll = data.getBoolean(EXPORT_ALL);
|
boolean exportAll = data.getBoolean(EXPORT_ALL);
|
||||||
|
|
||||||
|
if (outputFile != null) {
|
||||||
// check if storage is ready
|
// check if storage is ready
|
||||||
if (!FileHelper.isStorageMounted(outputFile)) {
|
if (!FileHelper.isStorageMounted(outputFile)) {
|
||||||
throw new PgpGeneralException(getString(R.string.error_external_storage_not_ready));
|
throw new PgpGeneralException(getString(R.string.error_external_storage_not_ready));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ArrayList<Long> publicMasterKeyIds = new ArrayList<Long>();
|
ArrayList<Long> publicMasterKeyIds = new ArrayList<Long>();
|
||||||
ArrayList<Long> secretMasterKeyIds = 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);
|
PgpImportExport pgpImportExport = new PgpImportExport(this, this, this);
|
||||||
Bundle resultData = pgpImportExport
|
Bundle resultData = pgpImportExport
|
||||||
.exportKeyRings(publicMasterKeyIds, secretMasterKeyIds,
|
.exportKeyRings(publicMasterKeyIds, secretMasterKeyIds,
|
||||||
new FileOutputStream(outputFile));
|
outStream);
|
||||||
|
|
||||||
if (mIsCanceled) {
|
if (mIsCanceled && outputFile != null) {
|
||||||
new File(outputFile).delete();
|
new File(outputFile).delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -694,22 +711,17 @@ public class KeychainIntentService extends IntentService
|
|||||||
byte[] bytes = data.getByteArray(bytesName);
|
byte[] bytes = data.getByteArray(bytesName);
|
||||||
return new InputData(new ByteArrayInputStream(bytes), bytes.length);
|
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 */
|
case IO_URI: /* encrypting content uri */
|
||||||
Uri providerUri = data.getParcelable(ENCRYPT_INPUT_URI);
|
Uri providerUri = data.getParcelable(ENCRYPT_INPUT_URI);
|
||||||
|
|
||||||
// InputStream
|
// InputStream
|
||||||
InputStream in = getContentResolver().openInputStream(providerUri);
|
return new InputData(getContentResolver().openInputStream(providerUri), FileHelper.getFileSize(this, providerUri, 0));
|
||||||
return new InputData(in, 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:
|
default:
|
||||||
throw new PgpGeneralException("No target choosen!");
|
throw new PgpGeneralException("No target choosen!");
|
||||||
@ -722,23 +734,16 @@ public class KeychainIntentService extends IntentService
|
|||||||
case IO_BYTES:
|
case IO_BYTES:
|
||||||
return new ByteArrayOutputStream();
|
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:
|
case IO_URI:
|
||||||
Uri providerUri = data.getParcelable(ENCRYPT_OUTPUT_URI);
|
Uri providerUri = data.getParcelable(ENCRYPT_OUTPUT_URI);
|
||||||
|
|
||||||
return getContentResolver().openOutputStream(providerUri);
|
return getContentResolver().openOutputStream(providerUri);
|
||||||
|
|
||||||
|
case IO_URIS:
|
||||||
|
providerUri = data.<Uri>getParcelableArrayList(ENCRYPT_OUTPUT_URIS).get(data.getInt(SELECTED_URI));
|
||||||
|
|
||||||
|
return getContentResolver().openOutputStream(providerUri);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new PgpGeneralException("No target choosen!");
|
throw new PgpGeneralException("No target choosen!");
|
||||||
}
|
}
|
||||||
@ -758,12 +763,9 @@ public class KeychainIntentService extends IntentService
|
|||||||
case IO_BYTES:
|
case IO_BYTES:
|
||||||
byte output[] = ((ByteArrayOutputStream) outStream).toByteArray();
|
byte output[] = ((ByteArrayOutputStream) outStream).toByteArray();
|
||||||
resultData.putByteArray(bytesName, output);
|
resultData.putByteArray(bytesName, output);
|
||||||
break;
|
|
||||||
case IO_FILE:
|
|
||||||
// nothing, file was written, just send okay and verification bundle
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case IO_URI:
|
case IO_URI:
|
||||||
|
case IO_URIS:
|
||||||
// nothing, output was written, just send okay and verification bundle
|
// nothing, output was written, just send okay and verification bundle
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
@ -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;
|
package org.sufficientlysecure.keychain.service;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
@ -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;
|
package org.sufficientlysecure.keychain.service;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
@ -34,9 +34,8 @@ import android.os.IBinder;
|
|||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
import android.os.Messenger;
|
import android.os.Messenger;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
|
|
||||||
import android.support.v4.util.LongSparseArray;
|
|
||||||
import android.support.v4.app.NotificationCompat;
|
import android.support.v4.app.NotificationCompat;
|
||||||
|
import android.support.v4.util.LongSparseArray;
|
||||||
|
|
||||||
import org.spongycastle.bcpg.S2K;
|
import org.spongycastle.bcpg.S2K;
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
@ -396,8 +395,8 @@ public class PassphraseCacheService extends Service {
|
|||||||
} else {
|
} else {
|
||||||
// Fallback, since expandable notifications weren't available back then
|
// Fallback, since expandable notifications weren't available back then
|
||||||
builder.setSmallIcon(R.drawable.ic_launcher)
|
builder.setSmallIcon(R.drawable.ic_launcher)
|
||||||
.setContentTitle(String.format(getString(R.string.passp_cache_notif_n_keys,
|
.setContentTitle(String.format(getString(R.string.passp_cache_notif_n_keys),
|
||||||
mPassphraseCache.size())))
|
mPassphraseCache.size()))
|
||||||
.setContentText(getString(R.string.passp_cache_notif_click_to_clear));
|
.setContentText(getString(R.string.passp_cache_notif_click_to_clear));
|
||||||
|
|
||||||
Intent intent = new Intent(getApplicationContext(), PassphraseCacheService.class);
|
Intent intent = new Intent(getApplicationContext(), PassphraseCacheService.class);
|
||||||
|
@ -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;
|
package org.sufficientlysecure.keychain.service;
|
||||||
|
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
@ -6,16 +23,17 @@ import android.os.Parcelable;
|
|||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.ArrayList;
|
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.
|
* to be done on a keyring.
|
||||||
*
|
* <p/>
|
||||||
* This class should include all types of operations supported in the backend.
|
* 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
|
* All changes are done in a differential manner. Besides the two key
|
||||||
* identification attributes, all attributes may be null, which indicates no
|
* identification attributes, all attributes may be null, which indicates no
|
||||||
* change to the keyring. This is also the reason why boxed values are used
|
* change to the keyring. This is also the reason why boxed values are used
|
||||||
* instead of primitives in the subclasses.
|
* instead of primitives in the subclasses.
|
||||||
*
|
* <p/>
|
||||||
* Application of operations in the backend should be fail-fast, which means an
|
* 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
|
* error in any included operation (for example revocation of a non-existent
|
||||||
* subkey) will cause the operation as a whole to fail.
|
* subkey) will cause the operation as a whole to fail.
|
||||||
@ -65,12 +83,23 @@ public class SaveKeyringParcel implements Parcelable {
|
|||||||
public int mKeysize;
|
public int mKeysize;
|
||||||
public int mFlags;
|
public int mFlags;
|
||||||
public Long mExpiry;
|
public Long mExpiry;
|
||||||
|
|
||||||
public SubkeyAdd(int algorithm, int keysize, int flags, Long expiry) {
|
public SubkeyAdd(int algorithm, int keysize, int flags, Long expiry) {
|
||||||
mAlgorithm = algorithm;
|
mAlgorithm = algorithm;
|
||||||
mKeysize = keysize;
|
mKeysize = keysize;
|
||||||
mFlags = flags;
|
mFlags = flags;
|
||||||
mExpiry = expiry;
|
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 {
|
public static class SubkeyChange implements Serializable {
|
||||||
@ -78,11 +107,46 @@ public class SaveKeyringParcel implements Parcelable {
|
|||||||
public Integer mFlags;
|
public Integer mFlags;
|
||||||
// this is a long unix timestamp, in seconds (NOT MILLISECONDS!)
|
// this is a long unix timestamp, in seconds (NOT MILLISECONDS!)
|
||||||
public Long mExpiry;
|
public Long mExpiry;
|
||||||
|
|
||||||
|
public SubkeyChange(long keyId) {
|
||||||
|
mKeyId = keyId;
|
||||||
|
}
|
||||||
|
|
||||||
public SubkeyChange(long keyId, Integer flags, Long expiry) {
|
public SubkeyChange(long keyId, Integer flags, Long expiry) {
|
||||||
mKeyId = keyId;
|
mKeyId = keyId;
|
||||||
mFlags = flags;
|
mFlags = flags;
|
||||||
mExpiry = expiry;
|
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) {
|
public SaveKeyringParcel(Parcel source) {
|
||||||
@ -136,4 +200,17 @@ public class SaveKeyringParcel implements Parcelable {
|
|||||||
return 0;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,8 +31,8 @@ import android.view.ViewGroup;
|
|||||||
import android.widget.CheckBox;
|
import android.widget.CheckBox;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
|
||||||
import org.spongycastle.bcpg.sig.KeyFlags;
|
import org.spongycastle.bcpg.sig.KeyFlags;
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.helper.Preferences;
|
import org.sufficientlysecure.keychain.helper.Preferences;
|
||||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||||
@ -169,11 +169,12 @@ public class CreateKeyFinalFragment extends Fragment {
|
|||||||
Bundle data = new Bundle();
|
Bundle data = new Bundle();
|
||||||
|
|
||||||
SaveKeyringParcel parcel = new SaveKeyringParcel();
|
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(PublicKeyAlgorithmTags.RSA_GENERAL, 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(PublicKeyAlgorithmTags.RSA_GENERAL, 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.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, null));
|
||||||
String userId = KeyRing.createUserId(mName, mEmail, null);
|
String userId = KeyRing.createUserId(mName, mEmail, null);
|
||||||
parcel.mAddUserIds.add(userId);
|
parcel.mAddUserIds.add(userId);
|
||||||
|
parcel.mChangePrimaryUserId = userId;
|
||||||
parcel.mNewPassphrase = mPassphrase;
|
parcel.mNewPassphrase = mPassphrase;
|
||||||
|
|
||||||
// get selected key entries
|
// get selected key entries
|
||||||
|
@ -23,11 +23,9 @@ import android.net.Uri;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.view.PagerTabStrip;
|
import android.support.v4.view.PagerTabStrip;
|
||||||
import android.support.v4.view.ViewPager;
|
import android.support.v4.view.ViewPager;
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.helper.FileHelper;
|
|
||||||
import org.sufficientlysecure.keychain.pgp.PgpHelper;
|
import org.sufficientlysecure.keychain.pgp.PgpHelper;
|
||||||
import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter;
|
import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
@ -114,7 +112,7 @@ public class DecryptActivity extends DrawerActivity {
|
|||||||
} else {
|
} else {
|
||||||
// Binary via content provider (could also be files)
|
// Binary via content provider (could also be files)
|
||||||
// override uri to get stream from send
|
// override uri to get stream from send
|
||||||
uri = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM);
|
uri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
|
||||||
action = ACTION_DECRYPT;
|
action = ACTION_DECRYPT;
|
||||||
}
|
}
|
||||||
} else if (Intent.ACTION_VIEW.equals(action)) {
|
} else if (Intent.ACTION_VIEW.equals(action)) {
|
||||||
@ -122,6 +120,7 @@ public class DecryptActivity extends DrawerActivity {
|
|||||||
|
|
||||||
// override action
|
// override action
|
||||||
action = ACTION_DECRYPT;
|
action = ACTION_DECRYPT;
|
||||||
|
mFileFragmentBundle.putBoolean(DecryptFileFragment.ARG_FROM_VIEW_INTENT, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
String textData = extras.getString(EXTRA_TEXT);
|
String textData = extras.getString(EXTRA_TEXT);
|
||||||
@ -155,21 +154,8 @@ public class DecryptActivity extends DrawerActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (ACTION_DECRYPT.equals(action) && uri != null) {
|
} else if (ACTION_DECRYPT.equals(action) && uri != null) {
|
||||||
// get file path from uri
|
mFileFragmentBundle.putParcelable(DecryptFileFragment.ARG_URI, uri);
|
||||||
String path = FileHelper.getPath(this, uri);
|
|
||||||
|
|
||||||
if (path != null) {
|
|
||||||
mFileFragmentBundle.putString(DecryptFileFragment.ARG_FILENAME, path);
|
|
||||||
mSwitchToTab = PAGER_TAB_FILE;
|
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)) {
|
} else if (ACTION_DECRYPT.equals(action)) {
|
||||||
Log.e(Constants.TAG,
|
Log.e(Constants.TAG,
|
||||||
"Include the extra 'text' or an Uri with setData() in your Intent!");
|
"Include the extra 'text' or an Uri with setData() in your Intent!");
|
||||||
|
@ -20,19 +20,16 @@ package org.sufficientlysecure.keychain.ui;
|
|||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.ProgressDialog;
|
import android.app.ProgressDialog;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.database.Cursor;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
import android.os.Messenger;
|
import android.os.Messenger;
|
||||||
import android.provider.OpenableColumns;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.CheckBox;
|
import android.widget.CheckBox;
|
||||||
import android.widget.EditText;
|
import android.widget.TextView;
|
||||||
import android.widget.ImageButton;
|
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.R;
|
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.pgp.PgpDecryptVerifyResult;
|
||||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||||
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
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.DeleteFileDialogFragment;
|
||||||
import org.sufficientlysecure.keychain.ui.dialog.FileDialogFragment;
|
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
import org.sufficientlysecure.keychain.util.Notify;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
public class DecryptFileFragment extends DecryptFragment {
|
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
|
// view
|
||||||
private EditText mFilename;
|
private TextView mFilename;
|
||||||
private CheckBox mDeleteAfter;
|
private CheckBox mDeleteAfter;
|
||||||
private ImageButton mBrowse;
|
|
||||||
private View mDecryptButton;
|
private View mDecryptButton;
|
||||||
|
|
||||||
private String mInputFilename = null;
|
// model
|
||||||
private Uri mInputUri = null;
|
private Uri mInputUri = null;
|
||||||
private String mOutputFilename = null;
|
|
||||||
private Uri mOutputUri = null;
|
private Uri mOutputUri = null;
|
||||||
|
|
||||||
private FileDialogFragment mFileDialog;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inflate the layout for this fragment
|
* Inflate the layout for this fragment
|
||||||
*/
|
*/
|
||||||
@ -72,17 +66,16 @@ public class DecryptFileFragment extends DecryptFragment {
|
|||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
View view = inflater.inflate(R.layout.decrypt_file_fragment, container, false);
|
View view = inflater.inflate(R.layout.decrypt_file_fragment, container, false);
|
||||||
|
|
||||||
mFilename = (EditText) view.findViewById(R.id.decrypt_file_filename);
|
mFilename = (TextView) view.findViewById(R.id.decrypt_file_filename);
|
||||||
mBrowse = (ImageButton) view.findViewById(R.id.decrypt_file_browse);
|
|
||||||
mDeleteAfter = (CheckBox) view.findViewById(R.id.decrypt_file_delete_after_decryption);
|
mDeleteAfter = (CheckBox) view.findViewById(R.id.decrypt_file_delete_after_decryption);
|
||||||
mDecryptButton = view.findViewById(R.id.decrypt_file_action_decrypt);
|
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) {
|
public void onClick(View v) {
|
||||||
if (Constants.KITKAT) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||||
FileHelper.openDocument(DecryptFileFragment.this, mInputUri, "*/*", RESULT_CODE_FILE);
|
FileHelper.openDocument(DecryptFileFragment.this, "*/*", REQUEST_CODE_INPUT);
|
||||||
} else {
|
} else {
|
||||||
FileHelper.openFile(DecryptFileFragment.this, mFilename.getText().toString(), "*/*",
|
FileHelper.openFile(DecryptFileFragment.this, mInputUri, "*/*",
|
||||||
RESULT_CODE_FILE);
|
REQUEST_CODE_INPUT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -100,74 +93,47 @@ public class DecryptFileFragment extends DecryptFragment {
|
|||||||
public void onActivityCreated(Bundle savedInstanceState) {
|
public void onActivityCreated(Bundle savedInstanceState) {
|
||||||
super.onActivityCreated(savedInstanceState);
|
super.onActivityCreated(savedInstanceState);
|
||||||
|
|
||||||
String filename = getArguments().getString(ARG_FILENAME);
|
setInputUri(getArguments().<Uri>getParcelable(ARG_URI));
|
||||||
if (filename != null) {
|
|
||||||
mFilename.setText(filename);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String guessOutputFilename() {
|
private void setInputUri(Uri inputUri) {
|
||||||
File file = new File(mInputFilename);
|
if (inputUri == null) {
|
||||||
String filename = file.getName();
|
mInputUri = null;
|
||||||
if (filename.endsWith(".asc") || filename.endsWith(".gpg") || filename.endsWith(".pgp")) {
|
mFilename.setText("");
|
||||||
filename = filename.substring(0, filename.length() - 4);
|
return;
|
||||||
}
|
}
|
||||||
return Constants.Path.APP_DIR + "/" + filename;
|
|
||||||
|
mInputUri = inputUri;
|
||||||
|
mFilename.setText(FileHelper.getFilename(getActivity(), mInputUri));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void decryptAction() {
|
private void decryptAction() {
|
||||||
String currentFilename = mFilename.getText().toString();
|
|
||||||
if (mInputFilename == null || !mInputFilename.equals(currentFilename)) {
|
|
||||||
mInputUri = null;
|
|
||||||
mInputFilename = mFilename.getText().toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mInputUri == null) {
|
if (mInputUri == null) {
|
||||||
mOutputFilename = guessOutputFilename();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mInputFilename.equals("")) {
|
|
||||||
Notify.showNotify(getActivity(), R.string.no_file_selected, Notify.Style.ERROR);
|
Notify.showNotify(getActivity(), R.string.no_file_selected, Notify.Style.ERROR);
|
||||||
return;
|
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();
|
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() {
|
private void askForOutputFilename() {
|
||||||
// Message is received after passphrase is cached
|
String targetName = removeEncryptedAppend(FileHelper.getFilename(getActivity(), mInputUri));
|
||||||
Handler returnHandler = new Handler() {
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
|
||||||
@Override
|
File file = new File(mInputUri.getPath());
|
||||||
public void handleMessage(Message message) {
|
File parentDir = file.exists() ? file.getParentFile() : Constants.Path.APP_DIR;
|
||||||
if (message.what == FileDialogFragment.MESSAGE_OKAY) {
|
File targetFile = new File(parentDir, targetName);
|
||||||
Bundle data = message.getData();
|
FileHelper.saveFile(this, getString(R.string.title_decrypt_to_file),
|
||||||
if (data.containsKey(FileDialogFragment.MESSAGE_DATA_URI)) {
|
getString(R.string.specify_file_to_decrypt_to), targetFile, REQUEST_CODE_OUTPUT);
|
||||||
mOutputUri = data.getParcelable(FileDialogFragment.MESSAGE_DATA_URI);
|
|
||||||
} else {
|
} 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
|
@Override
|
||||||
@ -183,25 +149,13 @@ public class DecryptFileFragment extends DecryptFragment {
|
|||||||
intent.setAction(KeychainIntentService.ACTION_DECRYPT_VERIFY);
|
intent.setAction(KeychainIntentService.ACTION_DECRYPT_VERIFY);
|
||||||
|
|
||||||
// data
|
// data
|
||||||
Log.d(Constants.TAG, "mInputFilename=" + mInputFilename + ", mOutputFilename="
|
Log.d(Constants.TAG, "mInputUri=" + mInputUri + ", mOutputUri=" + mOutputUri);
|
||||||
+ mOutputFilename + ",mInputUri=" + mInputUri + ", mOutputUri="
|
|
||||||
+ mOutputUri);
|
|
||||||
|
|
||||||
if (mInputUri != null) {
|
|
||||||
data.putInt(KeychainIntentService.SOURCE, KeychainIntentService.IO_URI);
|
data.putInt(KeychainIntentService.SOURCE, KeychainIntentService.IO_URI);
|
||||||
data.putParcelable(KeychainIntentService.ENCRYPT_INPUT_URI, mInputUri);
|
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.putInt(KeychainIntentService.TARGET, KeychainIntentService.IO_URI);
|
||||||
data.putParcelable(KeychainIntentService.ENCRYPT_OUTPUT_URI, mOutputUri);
|
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);
|
data.putString(KeychainIntentService.DECRYPT_PASSPHRASE, passphrase);
|
||||||
|
|
||||||
@ -232,15 +186,19 @@ public class DecryptFileFragment extends DecryptFragment {
|
|||||||
|
|
||||||
if (mDeleteAfter.isChecked()) {
|
if (mDeleteAfter.isChecked()) {
|
||||||
// Create and show dialog to delete original file
|
// Create and show dialog to delete original file
|
||||||
DeleteFileDialogFragment deleteFileDialog;
|
DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment.newInstance(mInputUri);
|
||||||
if (mInputUri != null) {
|
|
||||||
deleteFileDialog = DeleteFileDialogFragment.newInstance(mInputUri);
|
|
||||||
} else {
|
|
||||||
deleteFileDialog = DeleteFileDialogFragment
|
|
||||||
.newInstance(mInputFilename);
|
|
||||||
}
|
|
||||||
deleteFileDialog.show(getActivity().getSupportFragmentManager(), "deleteDialog");
|
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
|
@Override
|
||||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
switch (requestCode) {
|
switch (requestCode) {
|
||||||
case RESULT_CODE_FILE: {
|
case REQUEST_CODE_INPUT: {
|
||||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||||
if (Constants.KITKAT) {
|
setInputUri(data.getData());
|
||||||
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!");
|
|
||||||
}
|
}
|
||||||
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -25,11 +25,11 @@ import android.os.Message;
|
|||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.View.OnClickListener;
|
import android.view.View.OnClickListener;
|
||||||
|
import android.widget.Button;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.RelativeLayout;
|
import android.widget.RelativeLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Button;
|
|
||||||
|
|
||||||
import org.openintents.openpgp.OpenPgpSignatureResult;
|
import org.openintents.openpgp.OpenPgpSignatureResult;
|
||||||
import org.sufficientlysecure.keychain.R;
|
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.pgp.PgpKeyHelper;
|
||||||
import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
|
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;
|
private static final int RESULT_CODE_LOOKUP_KEY = 0x00007006;
|
||||||
|
|
||||||
protected long mSignatureKeyId = 0;
|
protected long mSignatureKeyId = 0;
|
||||||
@ -217,8 +217,6 @@ public class DecryptFragment extends Fragment {
|
|||||||
*
|
*
|
||||||
* @param passphrase
|
* @param passphrase
|
||||||
*/
|
*/
|
||||||
protected void decryptStart(String passphrase) {
|
protected abstract void decryptStart(String passphrase);
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -35,9 +35,9 @@ import android.view.View;
|
|||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.AdapterView;
|
import android.widget.AdapterView;
|
||||||
import android.widget.ArrayAdapter;
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.ImageView;
|
||||||
import android.widget.ListView;
|
import android.widget.ListView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.ImageView;
|
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
|
@ -37,7 +37,6 @@ import android.widget.AdapterView;
|
|||||||
import android.widget.ListView;
|
import android.widget.ListView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import org.spongycastle.bcpg.sig.KeyFlags;
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
|
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.SubkeysAddedAdapter;
|
||||||
import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter;
|
import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter;
|
||||||
import org.sufficientlysecure.keychain.ui.adapter.UserIdsAddedAdapter;
|
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.AddUserIdDialogFragment;
|
||||||
import org.sufficientlysecure.keychain.ui.dialog.ChangeExpiryDialogFragment;
|
|
||||||
import org.sufficientlysecure.keychain.ui.dialog.EditSubkeyDialogFragment;
|
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.EditUserIdDialogFragment;
|
||||||
import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
|
import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
|
||||||
import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment;
|
import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
public class EditKeyFragment extends LoaderFragment implements
|
public class EditKeyFragment extends LoaderFragment implements
|
||||||
LoaderManager.LoaderCallbacks<Cursor> {
|
LoaderManager.LoaderCallbacks<Cursor> {
|
||||||
|
|
||||||
@ -95,8 +92,8 @@ public class EditKeyFragment extends LoaderFragment implements
|
|||||||
private Uri mDataUri;
|
private Uri mDataUri;
|
||||||
|
|
||||||
private SaveKeyringParcel mSaveKeyringParcel;
|
private SaveKeyringParcel mSaveKeyringParcel;
|
||||||
private String mPrimaryUserId;
|
|
||||||
|
|
||||||
|
private String mPrimaryUserId;
|
||||||
private String mCurrentPassphrase;
|
private String mCurrentPassphrase;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -215,8 +212,7 @@ public class EditKeyFragment extends LoaderFragment implements
|
|||||||
mUserIdsList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
mUserIdsList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||||
String userId = mUserIdsAdapter.getUserId(position);
|
editUserId(position);
|
||||||
editUserId(userId);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -230,8 +226,7 @@ public class EditKeyFragment extends LoaderFragment implements
|
|||||||
mSubkeysList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
mSubkeysList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||||
long keyId = mSubkeysAdapter.getKeyId(position);
|
editSubkey(position);
|
||||||
editSubkey(keyId);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -320,7 +315,11 @@ public class EditKeyFragment extends LoaderFragment implements
|
|||||||
setPassphraseDialog.show(getActivity().getSupportFragmentManager(), "setPassphraseDialog");
|
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() {
|
Handler returnHandler = new Handler() {
|
||||||
@Override
|
@Override
|
||||||
public void handleMessage(Message message) {
|
public void handleMessage(Message message) {
|
||||||
@ -353,20 +352,21 @@ public class EditKeyFragment extends LoaderFragment implements
|
|||||||
DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() {
|
DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() {
|
||||||
public void run() {
|
public void run() {
|
||||||
EditUserIdDialogFragment dialogFragment =
|
EditUserIdDialogFragment dialogFragment =
|
||||||
EditUserIdDialogFragment.newInstance(messenger);
|
EditUserIdDialogFragment.newInstance(messenger, isRevoked, isRevokedPending);
|
||||||
|
|
||||||
dialogFragment.show(getActivity().getSupportFragmentManager(), "editUserIdDialog");
|
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() {
|
Handler returnHandler = new Handler() {
|
||||||
@Override
|
@Override
|
||||||
public void handleMessage(Message message) {
|
public void handleMessage(Message message) {
|
||||||
switch (message.what) {
|
switch (message.what) {
|
||||||
case EditSubkeyDialogFragment.MESSAGE_CHANGE_EXPIRY:
|
case EditSubkeyDialogFragment.MESSAGE_CHANGE_EXPIRY:
|
||||||
editSubkeyExpiry(keyId);
|
editSubkeyExpiry(position);
|
||||||
break;
|
break;
|
||||||
case EditSubkeyDialogFragment.MESSAGE_REVOKE:
|
case EditSubkeyDialogFragment.MESSAGE_REVOKE:
|
||||||
// toggle
|
// 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() {
|
Handler returnHandler = new Handler() {
|
||||||
@Override
|
@Override
|
||||||
public void handleMessage(Message message) {
|
public void handleMessage(Message message) {
|
||||||
switch (message.what) {
|
switch (message.what) {
|
||||||
case ChangeExpiryDialogFragment.MESSAGE_NEW_EXPIRY_DATE:
|
case EditSubkeyExpiryDialogFragment.MESSAGE_NEW_EXPIRY_DATE:
|
||||||
// toggle
|
Long expiry = (Long) message.getData().
|
||||||
// if (mSaveKeyringParcel.changePrimaryUserId != null
|
getSerializable(EditSubkeyExpiryDialogFragment.MESSAGE_DATA_EXPIRY_DATE);
|
||||||
// && mSaveKeyringParcel.changePrimaryUserId.equals(userId)) {
|
mSaveKeyringParcel.getOrCreateSubkeyChange(keyId).mExpiry = expiry;
|
||||||
// mSaveKeyringParcel.changePrimaryUserId = null;
|
|
||||||
// } else {
|
|
||||||
// mSaveKeyringParcel.changePrimaryUserId = userId;
|
|
||||||
// }
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
getLoaderManager().getLoader(LOADER_ID_SUBKEYS).forceLoad();
|
getLoaderManager().getLoader(LOADER_ID_SUBKEYS).forceLoad();
|
||||||
@ -418,8 +418,8 @@ public class EditKeyFragment extends LoaderFragment implements
|
|||||||
|
|
||||||
DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() {
|
DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() {
|
||||||
public void run() {
|
public void run() {
|
||||||
ChangeExpiryDialogFragment dialogFragment =
|
EditSubkeyExpiryDialogFragment dialogFragment =
|
||||||
ChangeExpiryDialogFragment.newInstance(messenger, new Date(), new Date());
|
EditSubkeyExpiryDialogFragment.newInstance(messenger, creationDate, expiryDate);
|
||||||
|
|
||||||
dialogFragment.show(getActivity().getSupportFragmentManager(), "editSubkeyExpiryDialog");
|
dialogFragment.show(getActivity().getSupportFragmentManager(), "editSubkeyExpiryDialog");
|
||||||
}
|
}
|
||||||
@ -453,8 +453,21 @@ public class EditKeyFragment extends LoaderFragment implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void addSubkey() {
|
private void addSubkey() {
|
||||||
// default values
|
boolean willBeMasterKey = mSubkeysAdapter.getCount() == 0
|
||||||
mSubkeysAddedAdapter.add(new SaveKeyringParcel.SubkeyAdd(Constants.choice.algorithm.rsa, 4096, KeyFlags.SIGN_DATA, null));
|
&& 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() {
|
private void cachePassphraseForEdit() {
|
||||||
@ -479,9 +492,7 @@ public class EditKeyFragment extends LoaderFragment implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void save(String passphrase) {
|
private void save(String passphrase) {
|
||||||
Log.d(Constants.TAG, "mSaveKeyringParcel.mAddUserIds: " + mSaveKeyringParcel.mAddUserIds);
|
Log.d(Constants.TAG, "mSaveKeyringParcel:\n" + mSaveKeyringParcel.toString());
|
||||||
Log.d(Constants.TAG, "mSaveKeyringParcel.mNewPassphrase: " + mSaveKeyringParcel.mNewPassphrase);
|
|
||||||
Log.d(Constants.TAG, "mSaveKeyringParcel.mRevokeUserIds: " + mSaveKeyringParcel.mRevokeUserIds);
|
|
||||||
|
|
||||||
// Message is received after importing is done in KeychainIntentService
|
// Message is received after importing is done in KeychainIntentService
|
||||||
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(
|
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(
|
||||||
|
@ -18,23 +18,46 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.ui;
|
package org.sufficientlysecure.keychain.ui;
|
||||||
|
|
||||||
|
import android.app.ProgressDialog;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.pm.LabeledIntent;
|
||||||
|
import android.content.pm.ResolveInfo;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
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.PagerTabStrip;
|
||||||
import android.support.v4.view.ViewPager;
|
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.Constants;
|
||||||
import org.sufficientlysecure.keychain.R;
|
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.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.Log;
|
||||||
|
import org.sufficientlysecure.keychain.util.Notify;
|
||||||
|
|
||||||
public class EncryptActivity extends DrawerActivity implements
|
import java.util.ArrayList;
|
||||||
EncryptSymmetricFragment.OnSymmetricKeySelection,
|
import java.util.Arrays;
|
||||||
EncryptAsymmetricFragment.OnAsymmetricKeySelection,
|
import java.util.Collections;
|
||||||
EncryptActivityInterface {
|
import java.util.Comparator;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class EncryptActivity extends DrawerActivity implements EncryptActivityInterface {
|
||||||
|
|
||||||
/* Intents */
|
/* Intents */
|
||||||
public static final String ACTION_ENCRYPT = Constants.INTENT_PREFIX + "ENCRYPT";
|
public static final String ACTION_ENCRYPT = Constants.INTENT_PREFIX + "ENCRYPT";
|
||||||
@ -51,7 +74,7 @@ public class EncryptActivity extends DrawerActivity implements
|
|||||||
|
|
||||||
// view
|
// view
|
||||||
ViewPager mViewPagerMode;
|
ViewPager mViewPagerMode;
|
||||||
PagerTabStrip mPagerTabStripMode;
|
//PagerTabStrip mPagerTabStripMode;
|
||||||
PagerTabStripAdapter mTabsAdapterMode;
|
PagerTabStripAdapter mTabsAdapterMode;
|
||||||
ViewPager mViewPagerContent;
|
ViewPager mViewPagerContent;
|
||||||
PagerTabStrip mPagerTabStripContent;
|
PagerTabStrip mPagerTabStripContent;
|
||||||
@ -72,37 +95,27 @@ public class EncryptActivity extends DrawerActivity implements
|
|||||||
|
|
||||||
// model used by message and file fragments
|
// model used by message and file fragments
|
||||||
private long mEncryptionKeyIds[] = null;
|
private long mEncryptionKeyIds[] = null;
|
||||||
|
private String mEncryptionUserIds[] = null;
|
||||||
private long mSigningKeyId = Constants.key.none;
|
private long mSigningKeyId = Constants.key.none;
|
||||||
private String mPassphrase;
|
private String mPassphrase = "";
|
||||||
private String mPassphraseAgain;
|
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() {
|
public boolean isModeSymmetric() {
|
||||||
if (PAGER_MODE_SYMMETRIC == mViewPagerMode.getCurrentItem()) {
|
return PAGER_MODE_SYMMETRIC == mViewPagerMode.getCurrentItem();
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isContentMessage() {
|
||||||
|
return PAGER_CONTENT_MESSAGE == mViewPagerContent.getCurrentItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isUseArmor() {
|
||||||
|
return mUseArmor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -116,19 +129,352 @@ public class EncryptActivity extends DrawerActivity implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getPassphrase() {
|
public String[] getEncryptionUsers() {
|
||||||
return mPassphrase;
|
return mEncryptionUserIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getPassphraseAgain() {
|
public void setSignatureKey(long signatureKey) {
|
||||||
return mPassphraseAgain;
|
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() {
|
private void initView() {
|
||||||
mViewPagerMode = (ViewPager) findViewById(R.id.encrypt_pager_mode);
|
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);
|
mViewPagerContent = (ViewPager) findViewById(R.id.encrypt_pager_content);
|
||||||
mPagerTabStripContent = (PagerTabStrip) findViewById(R.id.encrypt_pager_tab_strip_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,
|
mTabsAdapterContent.addTab(EncryptMessageFragment.class,
|
||||||
mMessageFragmentBundle, getString(R.string.label_message));
|
mMessageFragmentBundle, getString(R.string.label_message));
|
||||||
mTabsAdapterContent.addTab(EncryptFileFragment.class,
|
mTabsAdapterContent.addTab(EncryptFileFragment.class,
|
||||||
mFileFragmentBundle, getString(R.string.label_file));
|
mFileFragmentBundle, getString(R.string.label_files));
|
||||||
mViewPagerContent.setCurrentItem(mSwitchToContent);
|
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();
|
String action = intent.getAction();
|
||||||
Bundle extras = intent.getExtras();
|
Bundle extras = intent.getExtras();
|
||||||
String type = intent.getType();
|
String type = intent.getType();
|
||||||
Uri uri = intent.getData();
|
ArrayList<Uri> uris = new ArrayList<Uri>();
|
||||||
|
|
||||||
if (extras == null) {
|
if (extras == null) {
|
||||||
extras = new Bundle();
|
extras = new Bundle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (intent.getData() != null) {
|
||||||
|
uris.add(intent.getData());
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Android's Action
|
* Android's Action
|
||||||
*/
|
*/
|
||||||
if (Intent.ACTION_SEND.equals(action) && type != null) {
|
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)) {
|
if ("text/plain".equals(type)) {
|
||||||
// Plain text
|
// Plain text
|
||||||
String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);
|
String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);
|
||||||
@ -201,14 +586,19 @@ public class EncryptActivity extends DrawerActivity implements
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Files via content provider, override uri and action
|
// 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;
|
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)) {
|
if (extras.containsKey(EXTRA_ASCII_ARMOR)) {
|
||||||
boolean requestAsciiArmor = extras.getBoolean(EXTRA_ASCII_ARMOR, true);
|
mUseArmor = extras.getBoolean(EXTRA_ASCII_ARMOR, true);
|
||||||
mFileFragmentBundle.putBoolean(EncryptFileFragment.ARG_ASCII_ARMOR, requestAsciiArmor);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String textData = extras.getString(EXTRA_TEXT);
|
String textData = extras.getString(EXTRA_TEXT);
|
||||||
@ -230,25 +620,10 @@ public class EncryptActivity extends DrawerActivity implements
|
|||||||
// encrypt text based on given extra
|
// encrypt text based on given extra
|
||||||
mMessageFragmentBundle.putString(EncryptMessageFragment.ARG_TEXT, textData);
|
mMessageFragmentBundle.putString(EncryptMessageFragment.ARG_TEXT, textData);
|
||||||
mSwitchToContent = PAGER_CONTENT_MESSAGE;
|
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
|
// encrypt file based on Uri
|
||||||
|
mFileFragmentBundle.putParcelableArrayList(EncryptFileFragment.ARG_URIS, uris);
|
||||||
// get file path from uri
|
|
||||||
String path = FileHelper.getPath(this, uri);
|
|
||||||
|
|
||||||
if (path != null) {
|
|
||||||
mFileFragmentBundle.putString(EncryptFileFragment.ARG_FILENAME, path);
|
|
||||||
mSwitchToContent = PAGER_CONTENT_FILE;
|
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)) {
|
} else if (ACTION_ENCRYPT.equals(action)) {
|
||||||
Log.e(Constants.TAG,
|
Log.e(Constants.TAG,
|
||||||
"Include the extra 'text' or an Uri with setData() in your Intent!");
|
"Include the extra 'text' or an Uri with setData() in your Intent!");
|
||||||
|
@ -17,14 +17,41 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.ui;
|
package org.sufficientlysecure.keychain.ui;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
public interface EncryptActivityInterface {
|
public interface EncryptActivityInterface {
|
||||||
|
|
||||||
public boolean isModeSymmetric();
|
public interface UpdateListener {
|
||||||
|
void onNotifyUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isUseArmor();
|
||||||
|
|
||||||
public long getSignatureKey();
|
public long getSignatureKey();
|
||||||
public long[] getEncryptionKeys();
|
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 void setPassphrase(String passphrase);
|
||||||
public String getPassphraseAgain();
|
|
||||||
|
|
||||||
|
// 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);
|
||||||
}
|
}
|
||||||
|
@ -18,16 +18,25 @@
|
|||||||
package org.sufficientlysecure.keychain.ui;
|
package org.sufficientlysecure.keychain.ui;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Intent;
|
import android.content.Context;
|
||||||
|
import android.database.Cursor;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.app.Fragment;
|
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.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
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.TextView;
|
||||||
import android.widget.Button;
|
|
||||||
|
import com.tokenautocomplete.TokenCompleteTextView;
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.R;
|
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.CachedPublicKeyRing;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||||
|
import org.sufficientlysecure.keychain.ui.widget.EncryptKeyCompletionView;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
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_SIGNATURE_KEY_ID = "signature_key_id";
|
||||||
public static final String ARG_ENCRYPTION_KEY_IDS = "encryption_key_ids";
|
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;
|
ProviderHelper mProviderHelper;
|
||||||
|
|
||||||
OnAsymmetricKeySelection mKeySelectionListener;
|
|
||||||
|
|
||||||
// view
|
// view
|
||||||
private Button mSelectKeysButton;
|
private Spinner mSign;
|
||||||
private CheckBox mSign;
|
private EncryptKeyCompletionView mEncryptKeyView;
|
||||||
private TextView mMainUserId;
|
private SelectSignKeyCursorAdapter mSignAdapter = new SelectSignKeyCursorAdapter();
|
||||||
private TextView mMainUserIdRest;
|
|
||||||
|
|
||||||
// model
|
// model
|
||||||
private long mSecretKeyId = Constants.key.none;
|
private EncryptActivityInterface mEncryptInterface;
|
||||||
private long mEncryptionKeyIds[] = null;
|
|
||||||
|
|
||||||
// Container Activity must implement this interface
|
@Override
|
||||||
public interface OnAsymmetricKeySelection {
|
public void onNotifyUpdate() {
|
||||||
public void onSigningKeySelected(long signingKeyId);
|
|
||||||
|
|
||||||
public void onEncryptionKeysSelected(long[] encryptionKeyIds);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAttach(Activity activity) {
|
public void onAttach(Activity activity) {
|
||||||
super.onAttach(activity);
|
super.onAttach(activity);
|
||||||
try {
|
try {
|
||||||
mKeySelectionListener = (OnAsymmetricKeySelection) activity;
|
mEncryptInterface = (EncryptActivityInterface) activity;
|
||||||
} catch (ClassCastException e) {
|
} catch (ClassCastException e) {
|
||||||
throw new ClassCastException(activity.toString() + " must implement OnAsymmetricKeySelection");
|
throw new ClassCastException(activity.toString() + " must implement EncryptActivityInterface");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setSignatureKeyId(long signatureKeyId) {
|
private void setSignatureKeyId(long signatureKeyId) {
|
||||||
mSecretKeyId = signatureKeyId;
|
mEncryptInterface.setSignatureKey(signatureKeyId);
|
||||||
// update key selection in EncryptActivity
|
|
||||||
mKeySelectionListener.onSigningKeySelected(signatureKeyId);
|
|
||||||
updateView();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setEncryptionKeyIds(long[] encryptionKeyIds) {
|
private void setEncryptionKeyIds(long[] encryptionKeyIds) {
|
||||||
mEncryptionKeyIds = encryptionKeyIds;
|
mEncryptInterface.setEncryptionKeys(encryptionKeyIds);
|
||||||
// update key selection in EncryptActivity
|
}
|
||||||
mKeySelectionListener.onEncryptionKeysSelected(encryptionKeyIds);
|
|
||||||
updateView();
|
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) {
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
View view = inflater.inflate(R.layout.encrypt_asymmetric_fragment, container, false);
|
View view = inflater.inflate(R.layout.encrypt_asymmetric_fragment, container, false);
|
||||||
|
|
||||||
mSelectKeysButton = (Button) view.findViewById(R.id.btn_selectEncryptKeys);
|
mSign = (Spinner) view.findViewById(R.id.sign);
|
||||||
mSign = (CheckBox) view.findViewById(R.id.sign);
|
mSign.setAdapter(mSignAdapter);
|
||||||
mMainUserId = (TextView) view.findViewById(R.id.mainUserId);
|
mSign.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||||
mMainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest);
|
@Override
|
||||||
mSelectKeysButton.setOnClickListener(new View.OnClickListener() {
|
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||||
public void onClick(View v) {
|
setSignatureKeyId(parent.getAdapter().getItemId(position));
|
||||||
selectPublicKeys();
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
mSign.setOnClickListener(new View.OnClickListener() {
|
@Override
|
||||||
public void onClick(View v) {
|
public void onNothingSelected(AdapterView<?> parent) {
|
||||||
CheckBox checkBox = (CheckBox) v;
|
|
||||||
if (checkBox.isChecked()) {
|
|
||||||
selectSecretKey();
|
|
||||||
} else {
|
|
||||||
setSignatureKeyId(Constants.key.none);
|
setSignatureKeyId(Constants.key.none);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
mEncryptKeyView = (EncryptKeyCompletionView) view.findViewById(R.id.recipient_list);
|
||||||
|
mEncryptKeyView.setThreshold(1); // Start working from first character
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
@ -135,6 +131,65 @@ public class EncryptAsymmetricFragment extends Fragment {
|
|||||||
|
|
||||||
// preselect keys given by arguments (given by Intent to EncryptActivity)
|
// preselect keys given by arguments (given by Intent to EncryptActivity)
|
||||||
preselectKeys(signatureKeyId, encryptionKeyIds, mProviderHelper);
|
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) {
|
if (preselectedEncryptionKeyIds != null) {
|
||||||
Vector<Long> goodIds = new Vector<Long>();
|
for (long preselectedId : preselectedEncryptionKeyIds) {
|
||||||
for (int i = 0; i < preselectedEncryptionKeyIds.length; ++i) {
|
|
||||||
try {
|
try {
|
||||||
long id = providerHelper.getCachedPublicKeyRing(
|
CachedPublicKeyRing ring = providerHelper.getCachedPublicKeyRing(
|
||||||
KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(
|
KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(preselectedId));
|
||||||
preselectedEncryptionKeyIds[i])
|
mEncryptKeyView.addObject(mEncryptKeyView.new EncryptionKey(ring));
|
||||||
).getMasterKeyId();
|
|
||||||
goodIds.add(id);
|
|
||||||
} catch (PgpGeneralException e) {
|
} catch (PgpGeneralException e) {
|
||||||
Log.e(Constants.TAG, "key not found!", e);
|
Log.e(Constants.TAG, "key not found!", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (goodIds.size() > 0) {
|
updateEncryptionKeys();
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void selectPublicKeys() {
|
private void updateEncryptionKeys() {
|
||||||
Intent intent = new Intent(getActivity(), SelectPublicKeyActivity.class);
|
List<Object> objects = mEncryptKeyView.getObjects();
|
||||||
Vector<Long> keyIds = new Vector<Long>();
|
List<Long> keyIds = new ArrayList<Long>();
|
||||||
if (mEncryptionKeyIds != null && mEncryptionKeyIds.length > 0) {
|
List<String> userIds = new ArrayList<String>();
|
||||||
for (int i = 0; i < mEncryptionKeyIds.length; ++i) {
|
for (Object object : objects) {
|
||||||
keyIds.add(mEncryptionKeyIds[i]);
|
if (object instanceof EncryptKeyCompletionView.EncryptionKey) {
|
||||||
|
keyIds.add(((EncryptKeyCompletionView.EncryptionKey) object).getKeyId());
|
||||||
|
userIds.add(((EncryptKeyCompletionView.EncryptionKey) object).getUserId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
long[] initialKeyIds = null;
|
long[] keyIdsArr = new long[keyIds.size()];
|
||||||
if (keyIds.size() > 0) {
|
Iterator<Long> iterator = keyIds.iterator();
|
||||||
initialKeyIds = new long[keyIds.size()];
|
for (int i = 0; i < keyIds.size(); i++) {
|
||||||
for (int i = 0; i < keyIds.size(); ++i) {
|
keyIdsArr[i] = iterator.next();
|
||||||
initialKeyIds[i] = keyIds.get(i);
|
|
||||||
}
|
}
|
||||||
}
|
setEncryptionKeyIds(keyIdsArr);
|
||||||
intent.putExtra(SelectPublicKeyActivity.EXTRA_SELECTED_MASTER_KEY_IDS, initialKeyIds);
|
setEncryptionUserIds(userIds.toArray(new String[userIds.size()]));
|
||||||
startActivityForResult(intent, REQUEST_CODE_PUBLIC_KEYS);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void selectSecretKey() {
|
private class SelectSignKeyCursorAdapter extends BaseAdapter implements SpinnerAdapter {
|
||||||
Intent intent = new Intent(getActivity(), SelectSecretKeyActivity.class);
|
private CursorAdapter inner;
|
||||||
intent.putExtra(SelectSecretKeyActivity.EXTRA_FILTER_SIGN, true);
|
private int mIndexUserId;
|
||||||
startActivityForResult(intent, REQUEST_CODE_SECRET_KEYS);
|
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
|
@Override
|
||||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
public void bindView(View view, Context context, Cursor cursor) {
|
||||||
switch (requestCode) {
|
String[] userId = KeyRing.splitUserId(cursor.getString(mIndexUserId));
|
||||||
case REQUEST_CODE_PUBLIC_KEYS: {
|
((TextView) view.findViewById(android.R.id.title)).setText(userId[2] == null ? userId[0] : (userId[0] + " (" + userId[2] + ")"));
|
||||||
if (resultCode == Activity.RESULT_OK) {
|
((TextView) view.findViewById(android.R.id.text1)).setText(userId[1]);
|
||||||
Bundle bundle = data.getExtras();
|
((TextView) view.findViewById(android.R.id.text2)).setText(PgpKeyHelper.convertKeyIdToHex(cursor.getLong(mIndexKeyId)));
|
||||||
setEncryptionKeyIds(bundle
|
|
||||||
.getLongArray(SelectPublicKeyActivity.RESULT_EXTRA_MASTER_KEY_IDS));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case REQUEST_CODE_SECRET_KEYS: {
|
@Override
|
||||||
if (resultCode == Activity.RESULT_OK) {
|
public long getItemId(int position) {
|
||||||
Uri uriMasterKey = data.getData();
|
mCursor.moveToPosition(position);
|
||||||
setSignatureKeyId(Long.valueOf(uriMasterKey.getLastPathSegment()));
|
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 {
|
} else {
|
||||||
setSignatureKeyId(Constants.key.none);
|
v = convertView;
|
||||||
}
|
}
|
||||||
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);
|
||||||
default: {
|
} else {
|
||||||
super.onActivityResult(requestCode, resultCode, data);
|
v = inner.getView(position - 1, convertView, parent);
|
||||||
|
v.findViewById(android.R.id.text1).setVisibility(View.VISIBLE);
|
||||||
break;
|
v.findViewById(android.R.id.text2).setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
|
return v;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,66 +17,50 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.ui;
|
package org.sufficientlysecure.keychain.ui;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.ProgressDialog;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.database.Cursor;
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Point;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
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.support.v4.app.Fragment;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.ArrayAdapter;
|
import android.widget.BaseAdapter;
|
||||||
import android.widget.CheckBox;
|
import android.widget.ImageView;
|
||||||
import android.widget.EditText;
|
import android.widget.ListView;
|
||||||
import android.widget.ImageButton;
|
import android.widget.TextView;
|
||||||
import android.widget.Spinner;
|
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.helper.FileHelper;
|
import org.sufficientlysecure.keychain.helper.FileHelper;
|
||||||
import org.sufficientlysecure.keychain.helper.Preferences;
|
import org.sufficientlysecure.keychain.helper.OtherHelper;
|
||||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider;
|
||||||
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 java.io.File;
|
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 class EncryptFileFragment extends Fragment implements EncryptActivityInterface.UpdateListener {
|
||||||
public static final String ARG_FILENAME = "filename";
|
public static final String ARG_URIS = "uris";
|
||||||
public static final String ARG_ASCII_ARMOR = "ascii_armor";
|
|
||||||
|
|
||||||
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;
|
private EncryptActivityInterface mEncryptInterface;
|
||||||
|
|
||||||
// view
|
// view
|
||||||
private CheckBox mAsciiArmor = null;
|
private View mAddView;
|
||||||
private Spinner mFileCompression = null;
|
private View mShareFile;
|
||||||
private EditText mFilename = null;
|
|
||||||
private CheckBox mDeleteAfter = null;
|
|
||||||
private CheckBox mShareAfter = null;
|
|
||||||
private ImageButton mBrowse = null;
|
|
||||||
private View mEncryptFile;
|
private View mEncryptFile;
|
||||||
|
private ListView mSelectedFiles;
|
||||||
private FileDialogFragment mFileDialog;
|
private SelectedFilesAdapter mAdapter = new SelectedFilesAdapter();
|
||||||
|
private final Map<Uri, Bitmap> thumbnailCache = new HashMap<Uri, Bitmap>();
|
||||||
// model
|
|
||||||
private String mInputFilename = null;
|
|
||||||
private Uri mInputUri = null;
|
|
||||||
private String mOutputFilename = null;
|
|
||||||
private Uri mOutputUri = null;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAttach(Activity activity) {
|
public void onAttach(Activity activity) {
|
||||||
@ -99,52 +83,27 @@ public class EncryptFileFragment extends Fragment {
|
|||||||
mEncryptFile.setOnClickListener(new View.OnClickListener() {
|
mEncryptFile.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
encryptClicked();
|
encryptClicked(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
mShareFile = view.findViewById(R.id.action_encrypt_share);
|
||||||
mFilename = (EditText) view.findViewById(R.id.filename);
|
mShareFile.setOnClickListener(new View.OnClickListener() {
|
||||||
mBrowse = (ImageButton) view.findViewById(R.id.btn_browse);
|
@Override
|
||||||
mBrowse.setOnClickListener(new View.OnClickListener() {
|
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
if (Constants.KITKAT) {
|
encryptClicked(true);
|
||||||
FileHelper.openDocument(EncryptFileFragment.this, mInputUri, "*/*", REQUEST_CODE_FILE);
|
|
||||||
} else {
|
|
||||||
FileHelper.openFile(EncryptFileFragment.this, mFilename.getText().toString(), "*/*",
|
|
||||||
REQUEST_CODE_FILE);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
mFileCompression = (Spinner) view.findViewById(R.id.fileCompression);
|
mAddView = inflater.inflate(R.layout.file_list_entry_add, null);
|
||||||
Choice[] choices = new Choice[]{
|
mAddView.setOnClickListener(new View.OnClickListener() {
|
||||||
new Choice(Constants.choice.compression.none, getString(R.string.choice_none) + " ("
|
@Override
|
||||||
+ getString(R.string.compression_fast) + ")"),
|
public void onClick(View v) {
|
||||||
new Choice(Constants.choice.compression.zip, "ZIP ("
|
addInputUri();
|
||||||
+ 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;
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
mSelectedFiles = (ListView) view.findViewById(R.id.selected_files_list);
|
||||||
mDeleteAfter = (CheckBox) view.findViewById(R.id.deleteAfterEncryption);
|
mSelectedFiles.addFooterView(mAddView);
|
||||||
mShareAfter = (CheckBox) view.findViewById(R.id.shareAfterEncryption);
|
mSelectedFiles.setAdapter(mAdapter);
|
||||||
|
|
||||||
mAsciiArmor = (CheckBox) view.findViewById(R.id.asciiArmor);
|
|
||||||
mAsciiArmor.setChecked(Preferences.getPreferences(getActivity()).getDefaultAsciiArmor());
|
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
@ -153,264 +112,125 @@ public class EncryptFileFragment extends Fragment {
|
|||||||
public void onActivityCreated(Bundle savedInstanceState) {
|
public void onActivityCreated(Bundle savedInstanceState) {
|
||||||
super.onActivityCreated(savedInstanceState);
|
super.onActivityCreated(savedInstanceState);
|
||||||
|
|
||||||
String filename = getArguments().getString(ARG_FILENAME);
|
addInputUris(getArguments().<Uri>getParcelableArrayList(ARG_URIS));
|
||||||
if (filename != null) {
|
|
||||||
mFilename.setText(filename);
|
|
||||||
}
|
}
|
||||||
boolean asciiArmor = getArguments().getBoolean(ARG_ASCII_ARMOR);
|
|
||||||
if (asciiArmor) {
|
private void addInputUri() {
|
||||||
mAsciiArmor.setChecked(asciiArmor);
|
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
|
* With Android L it will be possible to select a target directory for multiple files, so we might want to
|
||||||
* @return Suggestion for output filename
|
* 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() {
|
private void showOutputFileDialog() {
|
||||||
// Message is received after file is selected
|
if (mEncryptInterface.getInputUris().size() > 1 || mEncryptInterface.getInputUris().isEmpty()) {
|
||||||
Handler returnHandler = new Handler() {
|
throw new IllegalStateException();
|
||||||
@Override
|
}
|
||||||
public void handleMessage(Message message) {
|
Uri inputUri = mEncryptInterface.getInputUris().get(0);
|
||||||
if (message.what == FileDialogFragment.MESSAGE_OKAY) {
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
|
||||||
Bundle data = message.getData();
|
File file = new File(inputUri.getPath());
|
||||||
if (data.containsKey(FileDialogFragment.MESSAGE_DATA_URI)) {
|
File parentDir = file.exists() ? file.getParentFile() : Constants.Path.APP_DIR;
|
||||||
mOutputUri = data.getParcelable(FileDialogFragment.MESSAGE_DATA_URI);
|
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 {
|
} else {
|
||||||
mOutputFilename = data.getString(FileDialogFragment.MESSAGE_DATA_FILENAME);
|
FileHelper.saveDocument(this, "*/*", FileHelper.getFilename(getActivity(), inputUri) +
|
||||||
}
|
(mEncryptInterface.isUseArmor() ? ".asc" : ".gpg"), REQUEST_CODE_OUTPUT);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mEncryptInterface.isModeSymmetric()) {
|
private void encryptClicked(boolean share) {
|
||||||
// symmetric encryption
|
if (share) {
|
||||||
|
mEncryptInterface.getOutputUris().clear();
|
||||||
boolean gotPassphrase = (mEncryptInterface.getPassphrase() != null
|
for (Uri uri : mEncryptInterface.getInputUris()) {
|
||||||
&& mEncryptInterface.getPassphrase().length() != 0);
|
String targetName = FileHelper.getFilename(getActivity(), uri) +
|
||||||
if (!gotPassphrase) {
|
(mEncryptInterface.isUseArmor() ? ".asc" : ".gpg");
|
||||||
Notify.showNotify(getActivity(), R.string.passphrase_must_not_be_empty, Notify.Style.ERROR)
|
mEncryptInterface.getOutputUris().add(TemporaryStorageProvider.createFile(getActivity(), targetName));
|
||||||
;
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
mEncryptInterface.startEncrypt(true);
|
||||||
if (!mEncryptInterface.getPassphrase().equals(mEncryptInterface.getPassphraseAgain())) {
|
} else if (mEncryptInterface.getInputUris().size() == 1) {
|
||||||
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) {
|
|
||||||
showOutputFileDialog();
|
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;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
switch (requestCode) {
|
switch (requestCode) {
|
||||||
case REQUEST_CODE_FILE: {
|
case REQUEST_CODE_INPUT: {
|
||||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||||
if (Constants.KITKAT) {
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT || !handleClipData(data)) {
|
||||||
mInputUri = data.getData();
|
addInputUri(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!");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,28 +18,16 @@
|
|||||||
package org.sufficientlysecure.keychain.ui;
|
package org.sufficientlysecure.keychain.ui;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.ProgressDialog;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.Message;
|
|
||||||
import android.os.Messenger;
|
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.text.Editable;
|
||||||
|
import android.text.TextWatcher;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
|
||||||
import org.sufficientlysecure.keychain.R;
|
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 class EncryptMessageFragment extends Fragment {
|
||||||
public static final String ARG_TEXT = "text";
|
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);
|
View view = inflater.inflate(R.layout.encrypt_message_fragment, container, false);
|
||||||
|
|
||||||
mMessage = (TextView) view.findViewById(R.id.message);
|
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);
|
mEncryptClipboard = view.findViewById(R.id.action_encrypt_clipboard);
|
||||||
mEncryptShare = view.findViewById(R.id.action_encrypt_share);
|
mEncryptShare = view.findViewById(R.id.action_encrypt_share);
|
||||||
mEncryptClipboard.setOnClickListener(new View.OnClickListener() {
|
mEncryptClipboard.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
encryptClicked(true);
|
mEncryptInterface.startEncrypt(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
mEncryptShare.setOnClickListener(new View.OnClickListener() {
|
mEncryptShare.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
encryptClicked(false);
|
mEncryptInterface.startEncrypt(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -92,7 +96,7 @@ public class EncryptMessageFragment extends Fragment {
|
|||||||
public void onActivityCreated(Bundle savedInstanceState) {
|
public void onActivityCreated(Bundle savedInstanceState) {
|
||||||
super.onActivityCreated(savedInstanceState);
|
super.onActivityCreated(savedInstanceState);
|
||||||
|
|
||||||
String text = getArguments().getString(ARG_TEXT);
|
String text = mEncryptInterface.getMessage();
|
||||||
if (text != null) {
|
if (text != null) {
|
||||||
mMessage.setText(text);
|
mMessage.setText(text);
|
||||||
}
|
}
|
||||||
@ -117,138 +121,4 @@ public class EncryptMessageFragment extends Fragment {
|
|||||||
|
|
||||||
return message;
|
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -29,27 +29,20 @@ import android.widget.EditText;
|
|||||||
|
|
||||||
import org.sufficientlysecure.keychain.R;
|
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 mPassphrase;
|
||||||
private EditText mPassphraseAgain;
|
private EditText mPassphraseAgain;
|
||||||
|
|
||||||
// Container Activity must implement this interface
|
|
||||||
public interface OnSymmetricKeySelection {
|
|
||||||
public void onPassphraseUpdate(String passphrase);
|
|
||||||
|
|
||||||
public void onPassphraseAgainUpdate(String passphrase);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAttach(Activity activity) {
|
public void onAttach(Activity activity) {
|
||||||
super.onAttach(activity);
|
super.onAttach(activity);
|
||||||
try {
|
try {
|
||||||
mPassphraseUpdateListener = (OnSymmetricKeySelection) activity;
|
mEncryptInterface = (EncryptActivityInterface) activity;
|
||||||
} catch (ClassCastException e) {
|
} 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);
|
mPassphrase = (EditText) view.findViewById(R.id.passphrase);
|
||||||
mPassphraseAgain = (EditText) view.findViewById(R.id.passphraseAgain);
|
mPassphraseAgain = (EditText) view.findViewById(R.id.passphraseAgain);
|
||||||
mPassphrase.addTextChangedListener(new TextWatcher() {
|
TextWatcher textWatcher = new TextWatcher() {
|
||||||
@Override
|
@Override
|
||||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||||
}
|
}
|
||||||
@ -74,25 +67,21 @@ public class EncryptSymmetricFragment extends Fragment {
|
|||||||
@Override
|
@Override
|
||||||
public void afterTextChanged(Editable s) {
|
public void afterTextChanged(Editable s) {
|
||||||
// update passphrase in EncryptActivity
|
// 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
|
mPassphrase.addTextChangedListener(textWatcher);
|
||||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
mPassphraseAgain.addTextChangedListener(textWatcher);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void afterTextChanged(Editable s) {
|
|
||||||
// update passphrase in EncryptActivity
|
|
||||||
mPassphraseUpdateListener.onPassphraseAgainUpdate(s.toString());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNotifyUpdate() {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,6 @@ import org.sufficientlysecure.keychain.Constants;
|
|||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.helper.OtherHelper;
|
import org.sufficientlysecure.keychain.helper.OtherHelper;
|
||||||
import org.sufficientlysecure.keychain.helper.Preferences;
|
import org.sufficientlysecure.keychain.helper.Preferences;
|
||||||
import org.sufficientlysecure.keychain.util.FileImportCache;
|
|
||||||
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
|
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
|
||||||
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
|
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
|
||||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
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.service.OperationResults.ImportKeyResult;
|
||||||
import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter;
|
import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter;
|
||||||
import org.sufficientlysecure.keychain.ui.widget.SlidingTabLayout;
|
import org.sufficientlysecure.keychain.ui.widget.SlidingTabLayout;
|
||||||
|
import org.sufficientlysecure.keychain.util.FileImportCache;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
import org.sufficientlysecure.keychain.util.Notify;
|
import org.sufficientlysecure.keychain.util.Notify;
|
||||||
|
|
||||||
|
@ -66,7 +66,7 @@ public class ImportKeysFileFragment extends Fragment {
|
|||||||
// open .asc or .gpg files
|
// open .asc or .gpg files
|
||||||
// setting it to text/plain prevents Cyanogenmod's file manager from selecting asc
|
// setting it to text/plain prevents Cyanogenmod's file manager from selecting asc
|
||||||
// or gpg types!
|
// or gpg types!
|
||||||
FileHelper.openFile(ImportKeysFileFragment.this, Constants.Path.APP_DIR + "/",
|
FileHelper.openFile(ImportKeysFileFragment.this, Uri.fromFile(Constants.Path.APP_DIR),
|
||||||
"*/*", REQUEST_CODE_FILE);
|
"*/*", REQUEST_CODE_FILE);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -94,22 +94,22 @@ public class KeyListActivity extends DrawerActivity {
|
|||||||
|
|
||||||
case R.id.menu_key_list_debug_read:
|
case R.id.menu_key_list_debug_read:
|
||||||
try {
|
try {
|
||||||
KeychainDatabase.debugRead(this);
|
KeychainDatabase.debugBackup(this, true);
|
||||||
Notify.showNotify(this, "Restored Notify.Style backup", Notify.Style.INFO);
|
Notify.showNotify(this, "Restored debug_backup.db", Notify.Style.INFO);
|
||||||
getContentResolver().notifyChange(KeychainContract.KeyRings.CONTENT_URI, null);
|
getContentResolver().notifyChange(KeychainContract.KeyRings.CONTENT_URI, null);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.e(Constants.TAG, "IO Error", 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;
|
return true;
|
||||||
|
|
||||||
case R.id.menu_key_list_debug_write:
|
case R.id.menu_key_list_debug_write:
|
||||||
try {
|
try {
|
||||||
KeychainDatabase.debugWrite(this);
|
KeychainDatabase.debugBackup(this, false);
|
||||||
Notify.showNotify(this, "Backup Notify.Style", Notify.Style.INFO);
|
Notify.showNotify(this, "Backup to debug_backup.db completed", Notify.Style.INFO);
|
||||||
} catch(IOException e) {
|
} catch(IOException e) {
|
||||||
Log.e(Constants.TAG, "IO Error", 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;
|
return true;
|
||||||
|
|
||||||
|
@ -47,10 +47,10 @@ import android.view.View.OnClickListener;
|
|||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.AbsListView.MultiChoiceModeListener;
|
import android.widget.AbsListView.MultiChoiceModeListener;
|
||||||
import android.widget.AdapterView;
|
import android.widget.AdapterView;
|
||||||
|
import android.widget.Button;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.ListView;
|
import android.widget.ListView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Button;
|
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
@ -176,8 +176,8 @@ public class KeyListFragment extends LoaderFragment
|
|||||||
case R.id.menu_key_list_multi_export: {
|
case R.id.menu_key_list_multi_export: {
|
||||||
ids = mAdapter.getCurrentSelectedMasterKeyIds();
|
ids = mAdapter.getCurrentSelectedMasterKeyIds();
|
||||||
ExportHelper mExportHelper = new ExportHelper((ActionBarActivity) getActivity());
|
ExportHelper mExportHelper = new ExportHelper((ActionBarActivity) getActivity());
|
||||||
mExportHelper.showExportKeysDialog(
|
mExportHelper.showExportKeysDialog(ids, Constants.Path.APP_DIR_FILE,
|
||||||
ids, Constants.Path.APP_DIR_FILE, mAdapter.isAnySecretSelected());
|
mAdapter.isAnySecretSelected());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case R.id.menu_key_list_multi_select_all: {
|
case R.id.menu_key_list_multi_select_all: {
|
||||||
|
@ -41,9 +41,7 @@ import org.sufficientlysecure.keychain.service.OperationResultParcel.LogEntryPar
|
|||||||
import org.sufficientlysecure.keychain.service.OperationResultParcel.LogLevel;
|
import org.sufficientlysecure.keychain.service.OperationResultParcel.LogLevel;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class LogDisplayFragment extends ListFragment implements OnTouchListener {
|
public class LogDisplayFragment extends ListFragment implements OnTouchListener {
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@ import android.preference.PreferenceActivity;
|
|||||||
import android.preference.PreferenceFragment;
|
import android.preference.PreferenceFragment;
|
||||||
import android.preference.PreferenceScreen;
|
import android.preference.PreferenceScreen;
|
||||||
|
|
||||||
|
import org.spongycastle.bcpg.CompressionAlgorithmTags;
|
||||||
import org.spongycastle.bcpg.HashAlgorithmTags;
|
import org.spongycastle.bcpg.HashAlgorithmTags;
|
||||||
import org.spongycastle.openpgp.PGPEncryptedData;
|
import org.spongycastle.openpgp.PGPEncryptedData;
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
@ -88,10 +89,10 @@ public class PreferencesActivity extends PreferenceActivity {
|
|||||||
(IntegerListPreference) findPreference(Constants.Pref.DEFAULT_ENCRYPTION_ALGORITHM));
|
(IntegerListPreference) findPreference(Constants.Pref.DEFAULT_ENCRYPTION_ALGORITHM));
|
||||||
|
|
||||||
int[] valueIds = new int[]{
|
int[] valueIds = new int[]{
|
||||||
Constants.choice.compression.none,
|
CompressionAlgorithmTags.UNCOMPRESSED,
|
||||||
Constants.choice.compression.zip,
|
CompressionAlgorithmTags.ZIP,
|
||||||
Constants.choice.compression.zlib,
|
CompressionAlgorithmTags.ZLIB,
|
||||||
Constants.choice.compression.bzip2,
|
CompressionAlgorithmTags.BZIP2,
|
||||||
};
|
};
|
||||||
String[] entries = new String[]{
|
String[] entries = new String[]{
|
||||||
getString(R.string.choice_none) + " (" + getString(R.string.compression_fast) + ")",
|
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));
|
(IntegerListPreference) findPreference(Constants.Pref.DEFAULT_ENCRYPTION_ALGORITHM));
|
||||||
|
|
||||||
int[] valueIds = new int[]{
|
int[] valueIds = new int[]{
|
||||||
Constants.choice.compression.none,
|
CompressionAlgorithmTags.UNCOMPRESSED,
|
||||||
Constants.choice.compression.zip,
|
CompressionAlgorithmTags.ZIP,
|
||||||
Constants.choice.compression.zlib,
|
CompressionAlgorithmTags.ZLIB,
|
||||||
Constants.choice.compression.bzip2,
|
CompressionAlgorithmTags.BZIP2,
|
||||||
};
|
};
|
||||||
|
|
||||||
String[] entries = new String[]{
|
String[] entries = new String[]{
|
||||||
|
@ -34,8 +34,8 @@ import android.widget.TextView;
|
|||||||
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
|
||||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
|
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||||
import org.sufficientlysecure.keychain.pgp.WrappedSignature;
|
import org.sufficientlysecure.keychain.pgp.WrappedSignature;
|
||||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
|
||||||
|
@ -42,7 +42,6 @@ import android.support.v7.app.ActionBarActivity;
|
|||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.Window;
|
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
@ -57,9 +56,9 @@ import org.sufficientlysecure.keychain.provider.KeychainContract;
|
|||||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||||
import org.sufficientlysecure.keychain.service.OperationResultParcel;
|
import org.sufficientlysecure.keychain.service.OperationResultParcel;
|
||||||
import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter;
|
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.ui.widget.SlidingTabLayout.TabColorizer;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
import org.sufficientlysecure.keychain.ui.widget.SlidingTabLayout;
|
|
||||||
import org.sufficientlysecure.keychain.util.Notify;
|
import org.sufficientlysecure.keychain.util.Notify;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
@ -103,7 +102,6 @@ public class ViewKeyActivity extends ActionBarActivity implements
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
|
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
mExportHelper = new ExportHelper(this);
|
mExportHelper = new ExportHelper(this);
|
||||||
@ -296,8 +294,7 @@ public class ViewKeyActivity extends ActionBarActivity implements
|
|||||||
|
|
||||||
exportHelper.showExportKeysDialog(
|
exportHelper.showExportKeysDialog(
|
||||||
new long[]{(Long) data.get(KeychainContract.KeyRings.MASTER_KEY_ID)},
|
new long[]{(Long) data.get(KeychainContract.KeyRings.MASTER_KEY_ID)},
|
||||||
Constants.Path.APP_DIR_FILE,
|
Constants.Path.APP_DIR_FILE, ((Long) data.get(KeychainContract.KeyRings.HAS_SECRET) == 1)
|
||||||
((Long) data.get(KeychainContract.KeyRings.HAS_SECRET) == 1)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,7 +150,7 @@ public class ViewKeyCertsFragment extends LoaderFragment
|
|||||||
|
|
||||||
Intent viewIntent = new Intent(getActivity(), ViewCertActivity.class);
|
Intent viewIntent = new Intent(getActivity(), ViewCertActivity.class);
|
||||||
viewIntent.setData(Certs.buildCertsSpecificUri(
|
viewIntent.setData(Certs.buildCertsSpecificUri(
|
||||||
Long.toString(masterKeyId), Long.toString(rank), Long.toString(certifierId)));
|
masterKeyId, rank, certifierId));
|
||||||
startActivity(viewIntent);
|
startActivity(viewIntent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,6 @@ import org.sufficientlysecure.keychain.provider.KeychainContract;
|
|||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
|
||||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||||
import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException;
|
|
||||||
import org.sufficientlysecure.keychain.ui.dialog.ShareNfcDialogFragment;
|
import org.sufficientlysecure.keychain.ui.dialog.ShareNfcDialogFragment;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
import org.sufficientlysecure.keychain.util.Notify;
|
import org.sufficientlysecure.keychain.util.Notify;
|
||||||
|
@ -111,7 +111,7 @@ public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> {
|
|||||||
convertView = mInflater.inflate(R.layout.import_keys_list_entry, null);
|
convertView = mInflater.inflate(R.layout.import_keys_list_entry, null);
|
||||||
holder.mainUserId = (TextView) convertView.findViewById(R.id.mainUserId);
|
holder.mainUserId = (TextView) convertView.findViewById(R.id.mainUserId);
|
||||||
holder.mainUserIdRest = (TextView) convertView.findViewById(R.id.mainUserIdRest);
|
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.fingerprint = (TextView) convertView.findViewById(R.id.view_key_fingerprint);
|
||||||
holder.algorithm = (TextView) convertView.findViewById(R.id.algorithm);
|
holder.algorithm = (TextView) convertView.findViewById(R.id.algorithm);
|
||||||
holder.status = (TextView) convertView.findViewById(R.id.status);
|
holder.status = (TextView) convertView.findViewById(R.id.status);
|
||||||
|
@ -33,7 +33,6 @@ import java.io.BufferedInputStream;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class ImportKeysListLoader
|
public class ImportKeysListLoader
|
||||||
extends AsyncTaskLoader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> {
|
extends AsyncTaskLoader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> {
|
||||||
|
@ -20,13 +20,8 @@ package org.sufficientlysecure.keychain.ui.adapter;
|
|||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
import android.support.v4.app.FragmentManager;
|
|
||||||
import android.support.v4.app.FragmentPagerAdapter;
|
import android.support.v4.app.FragmentPagerAdapter;
|
||||||
import android.support.v4.app.FragmentTransaction;
|
|
||||||
import android.support.v7.app.ActionBarActivity;
|
import android.support.v7.app.ActionBarActivity;
|
||||||
import android.view.ViewGroup;
|
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
@ -152,7 +152,7 @@ abstract public class SelectKeyCursorAdapter extends CursorAdapter {
|
|||||||
holder.view = view;
|
holder.view = view;
|
||||||
holder.mainUserId = (TextView) view.findViewById(R.id.mainUserId);
|
holder.mainUserId = (TextView) view.findViewById(R.id.mainUserId);
|
||||||
holder.mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest);
|
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.status = (TextView) view.findViewById(R.id.status);
|
||||||
holder.selected = (CheckBox) view.findViewById(R.id.selected);
|
holder.selected = (CheckBox) view.findViewById(R.id.selected);
|
||||||
view.setTag(holder);
|
view.setTag(holder);
|
||||||
|
@ -20,6 +20,7 @@ package org.sufficientlysecure.keychain.ui.adapter;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.ColorStateList;
|
import android.content.res.ColorStateList;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
|
import android.graphics.Typeface;
|
||||||
import android.support.v4.widget.CursorAdapter;
|
import android.support.v4.widget.CursorAdapter;
|
||||||
import android.text.format.DateFormat;
|
import android.text.format.DateFormat;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
@ -89,6 +90,20 @@ public class SubkeysAdapter extends CursorAdapter {
|
|||||||
return mCursor.getLong(INDEX_KEY_ID);
|
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
|
@Override
|
||||||
public Cursor swapCursor(Cursor newCursor) {
|
public Cursor swapCursor(Cursor newCursor) {
|
||||||
hasAnySecret = false;
|
hasAnySecret = false;
|
||||||
@ -106,15 +121,18 @@ public class SubkeysAdapter extends CursorAdapter {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void bindView(View view, Context context, Cursor cursor) {
|
public void bindView(View view, Context context, Cursor cursor) {
|
||||||
TextView vKeyId = (TextView) view.findViewById(R.id.keyId);
|
TextView vKeyId = (TextView) view.findViewById(R.id.subkey_item_key_id);
|
||||||
TextView vKeyDetails = (TextView) view.findViewById(R.id.keyDetails);
|
TextView vKeyDetails = (TextView) view.findViewById(R.id.subkey_item_details);
|
||||||
TextView vKeyExpiry = (TextView) view.findViewById(R.id.keyExpiry);
|
TextView vKeyExpiry = (TextView) view.findViewById(R.id.subkey_item_expiry);
|
||||||
ImageView vMasterKeyIcon = (ImageView) view.findViewById(R.id.ic_masterKey);
|
ImageView vCertifyIcon = (ImageView) view.findViewById(R.id.subkey_item_ic_certify);
|
||||||
ImageView vCertifyIcon = (ImageView) view.findViewById(R.id.ic_certifyKey);
|
ImageView vEncryptIcon = (ImageView) view.findViewById(R.id.subkey_item_ic_encrypt);
|
||||||
ImageView vEncryptIcon = (ImageView) view.findViewById(R.id.ic_encryptKey);
|
ImageView vSignIcon = (ImageView) view.findViewById(R.id.subkey_item_ic_sign);
|
||||||
ImageView vSignIcon = (ImageView) view.findViewById(R.id.ic_signKey);
|
ImageView vRevokedIcon = (ImageView) view.findViewById(R.id.subkey_item_ic_revoked);
|
||||||
ImageView vRevokedKeyIcon = (ImageView) view.findViewById(R.id.ic_revokedKey);
|
ImageView vEditImage = (ImageView) view.findViewById(R.id.subkey_item_edit_image);
|
||||||
ImageView vEditImage = (ImageView) view.findViewById(R.id.user_id_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);
|
long keyId = cursor.getLong(INDEX_KEY_ID);
|
||||||
String keyIdStr = PgpKeyHelper.convertKeyIdToHex(keyId);
|
String keyIdStr = PgpKeyHelper.convertKeyIdToHex(keyId);
|
||||||
@ -133,14 +151,26 @@ public class SubkeysAdapter extends CursorAdapter {
|
|||||||
vKeyDetails.setText(algorithmStr);
|
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
|
// 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);
|
vCertifyIcon.setVisibility(cursor.getInt(INDEX_CAN_CERTIFY) != 0 ? View.VISIBLE : View.GONE);
|
||||||
vEncryptIcon.setVisibility(cursor.getInt(INDEX_CAN_ENCRYPT) != 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);
|
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;
|
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
|
// for edit key
|
||||||
if (mSaveKeyringParcel != null) {
|
if (mSaveKeyringParcel != null) {
|
||||||
boolean revokeThisSubkey = (mSaveKeyringParcel.mRevokeSubKeys.contains(keyId));
|
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);
|
vEditImage.setVisibility(View.VISIBLE);
|
||||||
} else {
|
} else {
|
||||||
vEditImage.setVisibility(View.GONE);
|
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;
|
boolean isExpired;
|
||||||
if (!cursor.isNull(INDEX_EXPIRY)) {
|
if (expiryDate != null) {
|
||||||
Date expiryDate = new Date(cursor.getLong(INDEX_EXPIRY) * 1000);
|
|
||||||
isExpired = expiryDate.before(new Date());
|
isExpired = expiryDate.before(new Date());
|
||||||
|
|
||||||
vKeyExpiry.setText(context.getString(R.string.label_expiry) + ": "
|
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));
|
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
|
// if key is expired or revoked, strike through text
|
||||||
boolean isInvalid = isRevoked || isExpired;
|
boolean isInvalid = isRevoked || isExpired;
|
||||||
if (isInvalid) {
|
if (isInvalid) {
|
||||||
@ -195,7 +233,7 @@ public class SubkeysAdapter extends CursorAdapter {
|
|||||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||||
View view = mInflater.inflate(R.layout.view_key_subkey_item, null);
|
View view = mInflater.inflate(R.layout.view_key_subkey_item, null);
|
||||||
if (mDefaultTextColor == 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();
|
mDefaultTextColor = keyId.getTextColors();
|
||||||
}
|
}
|
||||||
return view;
|
return view;
|
||||||
|
@ -17,48 +17,29 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.ui.adapter;
|
package org.sufficientlysecure.keychain.ui.adapter;
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.AlertDialog;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
import android.text.format.DateFormat;
|
||||||
import android.os.Build;
|
|
||||||
import android.text.Editable;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.text.TextWatcher;
|
|
||||||
import android.util.Patterns;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.inputmethod.InputMethodManager;
|
|
||||||
import android.widget.AdapterView;
|
|
||||||
import android.widget.ArrayAdapter;
|
import android.widget.ArrayAdapter;
|
||||||
import android.widget.AutoCompleteTextView;
|
|
||||||
import android.widget.EditText;
|
|
||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
import android.widget.Spinner;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.spongycastle.bcpg.sig.KeyFlags;
|
||||||
import org.sufficientlysecure.keychain.R;
|
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.service.SaveKeyringParcel;
|
||||||
import org.sufficientlysecure.keychain.ui.dialog.CreateKeyDialogFragment;
|
|
||||||
import org.sufficientlysecure.keychain.util.Choice;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.Date;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.regex.Matcher;
|
|
||||||
|
|
||||||
public class SubkeysAddedAdapter extends ArrayAdapter<SaveKeyringParcel.SubkeyAdd> {
|
public class SubkeysAddedAdapter extends ArrayAdapter<SaveKeyringParcel.SubkeyAdd> {
|
||||||
private LayoutInflater mInflater;
|
private LayoutInflater mInflater;
|
||||||
private Activity mActivity;
|
private Activity mActivity;
|
||||||
|
|
||||||
public interface OnAlgorithmSelectedListener {
|
|
||||||
public void onAlgorithmSelected(Choice algorithmChoice, int keySize);
|
|
||||||
}
|
|
||||||
|
|
||||||
// hold a private reference to the underlying data List
|
// hold a private reference to the underlying data List
|
||||||
private List<SaveKeyringParcel.SubkeyAdd> mData;
|
private List<SaveKeyringParcel.SubkeyAdd> mData;
|
||||||
|
|
||||||
@ -70,12 +51,12 @@ public class SubkeysAddedAdapter extends ArrayAdapter<SaveKeyringParcel.SubkeyAd
|
|||||||
}
|
}
|
||||||
|
|
||||||
static class ViewHolder {
|
static class ViewHolder {
|
||||||
public OnAlgorithmSelectedListener mAlgorithmSelectedListener;
|
public TextView vKeyId;
|
||||||
public Spinner mAlgorithmSpinner;
|
public TextView vKeyDetails;
|
||||||
public Spinner mKeySizeSpinner;
|
public TextView vKeyExpiry;
|
||||||
public TextView mCustomKeyTextView;
|
public ImageView vCertifyIcon;
|
||||||
public EditText mCustomKeyEditText;
|
public ImageView vEncryptIcon;
|
||||||
public TextView mCustomKeyInfoTextView;
|
public ImageView vSignIcon;
|
||||||
public ImageButton vDelete;
|
public ImageButton vDelete;
|
||||||
// also hold a reference to the model item
|
// also hold a reference to the model item
|
||||||
public SaveKeyringParcel.SubkeyAdd mModel;
|
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) {
|
public View getView(final int position, View convertView, ViewGroup parent) {
|
||||||
if (convertView == null) {
|
if (convertView == null) {
|
||||||
// Not recycled, inflate a new view
|
// 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();
|
final ViewHolder holder = new ViewHolder();
|
||||||
holder.mAlgorithmSpinner = (Spinner) convertView.findViewById(R.id.create_key_algorithm);
|
holder.vKeyId = (TextView) convertView.findViewById(R.id.subkey_item_key_id);
|
||||||
holder.mKeySizeSpinner = (Spinner) convertView.findViewById(R.id.create_key_size);
|
holder.vKeyDetails = (TextView) convertView.findViewById(R.id.subkey_item_details);
|
||||||
holder.mCustomKeyTextView = (TextView) convertView.findViewById(R.id.custom_key_size_label);
|
holder.vKeyExpiry = (TextView) convertView.findViewById(R.id.subkey_item_expiry);
|
||||||
holder.mCustomKeyEditText = (EditText) convertView.findViewById(R.id.custom_key_size_input);
|
holder.vCertifyIcon = (ImageView) convertView.findViewById(R.id.subkey_item_ic_certify);
|
||||||
holder.mCustomKeyInfoTextView = (TextView) convertView.findViewById(R.id.custom_key_size_info);
|
holder.vEncryptIcon = (ImageView) convertView.findViewById(R.id.subkey_item_ic_encrypt);
|
||||||
holder.vDelete = (ImageButton) convertView.findViewById(R.id.subkey_added_item_delete);
|
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);
|
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() {
|
holder.vDelete.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
@ -136,226 +98,44 @@ public class SubkeysAddedAdapter extends ArrayAdapter<SaveKeyringParcel.SubkeyAd
|
|||||||
// save reference to model item
|
// save reference to model item
|
||||||
holder.mModel = getItem(position);
|
holder.mModel = getItem(position);
|
||||||
|
|
||||||
// TODO
|
String algorithmStr = PgpKeyHelper.getAlgorithmInfo(
|
||||||
boolean wouldBeMasterKey = false;
|
mActivity,
|
||||||
// boolean wouldBeMasterKey = (childCount == 0);
|
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>();
|
if (holder.mModel.mExpiry != null) {
|
||||||
choices.add(new Choice(Constants.choice.algorithm.dsa, mActivity.getResources().getString(
|
Date expiryDate = new Date(holder.mModel.mExpiry * 1000);
|
||||||
R.string.dsa)));
|
|
||||||
if (!wouldBeMasterKey) {
|
holder.vKeyExpiry.setText(getContext().getString(R.string.label_expiry) + ": "
|
||||||
choices.add(new Choice(Constants.choice.algorithm.elgamal, mActivity.getResources().getString(
|
+ DateFormat.getDateFormat(getContext()).format(expiryDate));
|
||||||
R.string.elgamal)));
|
} 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(
|
int flags = holder.mModel.mFlags;
|
||||||
R.string.rsa)));
|
if ((flags & KeyFlags.CERTIFY_OTHER) > 0) {
|
||||||
|
holder.vCertifyIcon.setVisibility(View.VISIBLE);
|
||||||
ArrayAdapter<Choice> adapter = new ArrayAdapter<Choice>(mActivity,
|
} else {
|
||||||
android.R.layout.simple_spinner_item, choices);
|
holder.vCertifyIcon.setVisibility(View.GONE);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
if ((flags & KeyFlags.SIGN_DATA) > 0) {
|
||||||
|
holder.vSignIcon.setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
holder.vSignIcon.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
if (((flags & KeyFlags.ENCRYPT_COMMS) > 0)
|
||||||
// dynamic ArrayAdapter must be created (instead of ArrayAdapter.getFromResource), because it's content may change
|
|| ((flags & KeyFlags.ENCRYPT_STORAGE) > 0)) {
|
||||||
ArrayAdapter<CharSequence> keySizeAdapter = new ArrayAdapter<CharSequence>(mActivity, android.R.layout.simple_spinner_item,
|
holder.vEncryptIcon.setVisibility(View.VISIBLE);
|
||||||
new ArrayList<CharSequence>(Arrays.asList(mActivity.getResources().getStringArray(R.array.rsa_key_size_spinner_values))));
|
} else {
|
||||||
keySizeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
holder.vEncryptIcon.setVisibility(View.GONE);
|
||||||
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) {
|
|
||||||
}
|
}
|
||||||
|
// TODO: missing icon for authenticate
|
||||||
@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);
|
|
||||||
|
|
||||||
return convertView;
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -252,6 +252,26 @@ public class UserIdsAdapter extends CursorAdapter implements AdapterView.OnItemC
|
|||||||
return mCursor.getString(INDEX_USER_ID);
|
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
|
@Override
|
||||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||||
View view = mInflater.inflate(R.layout.view_key_user_id_item, null);
|
View view = mInflater.inflate(R.layout.view_key_user_id_item, null);
|
||||||
|
@ -27,46 +27,64 @@ import android.support.v4.app.DialogFragment;
|
|||||||
import android.support.v4.app.FragmentActivity;
|
import android.support.v4.app.FragmentActivity;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
import android.text.TextWatcher;
|
import android.text.TextWatcher;
|
||||||
|
import android.text.format.DateUtils;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.inputmethod.InputMethodManager;
|
import android.view.inputmethod.InputMethodManager;
|
||||||
import android.widget.AdapterView;
|
import android.widget.AdapterView;
|
||||||
import android.widget.ArrayAdapter;
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.CheckBox;
|
||||||
|
import android.widget.CompoundButton;
|
||||||
|
import android.widget.DatePicker;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.Spinner;
|
import android.widget.Spinner;
|
||||||
|
import android.widget.TableRow;
|
||||||
import android.widget.TextView;
|
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.R;
|
||||||
|
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||||
import org.sufficientlysecure.keychain.util.Choice;
|
import org.sufficientlysecure.keychain.util.Choice;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
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 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 OnAlgorithmSelectedListener mAlgorithmSelectedListener;
|
||||||
|
|
||||||
|
private CheckBox mNoExpiryCheckBox;
|
||||||
|
private TableRow mExpiryRow;
|
||||||
|
private DatePicker mExpiryDatePicker;
|
||||||
private Spinner mAlgorithmSpinner;
|
private Spinner mAlgorithmSpinner;
|
||||||
private Spinner mKeySizeSpinner;
|
private Spinner mKeySizeSpinner;
|
||||||
private TextView mCustomKeyTextView;
|
private TextView mCustomKeyTextView;
|
||||||
private EditText mCustomKeyEditText;
|
private EditText mCustomKeyEditText;
|
||||||
private TextView mCustomKeyInfoTextView;
|
private TextView mCustomKeyInfoTextView;
|
||||||
|
private CheckBox mFlagCertify;
|
||||||
|
private CheckBox mFlagSign;
|
||||||
|
private CheckBox mFlagEncrypt;
|
||||||
|
private CheckBox mFlagAuthenticate;
|
||||||
|
|
||||||
public void setOnAlgorithmSelectedListener(OnAlgorithmSelectedListener listener) {
|
public void setOnAlgorithmSelectedListener(OnAlgorithmSelectedListener listener) {
|
||||||
mAlgorithmSelectedListener = listener;
|
mAlgorithmSelectedListener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CreateKeyDialogFragment newInstance(int mEditorChildCount) {
|
public static AddSubkeyDialogFragment newInstance(boolean willBeMasterKey) {
|
||||||
CreateKeyDialogFragment frag = new CreateKeyDialogFragment();
|
AddSubkeyDialogFragment frag = new AddSubkeyDialogFragment();
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
|
|
||||||
args.putInt(ARG_EDITOR_CHILD_COUNT, mEditorChildCount);
|
args.putBoolean(ARG_WILL_BE_MASTER_KEY, willBeMasterKey);
|
||||||
|
|
||||||
frag.setArguments(args);
|
frag.setArguments(args);
|
||||||
|
|
||||||
@ -78,42 +96,64 @@ public class CreateKeyDialogFragment extends DialogFragment {
|
|||||||
final FragmentActivity context = getActivity();
|
final FragmentActivity context = getActivity();
|
||||||
final LayoutInflater mInflater;
|
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();
|
mInflater = context.getLayoutInflater();
|
||||||
|
|
||||||
CustomAlertDialogBuilder dialog = new CustomAlertDialogBuilder(context);
|
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.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);
|
mNoExpiryCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||||
ArrayList<Choice> choices = new ArrayList<Choice>();
|
@Override
|
||||||
choices.add(new Choice(Constants.choice.algorithm.dsa, getResources().getString(
|
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||||
R.string.dsa)));
|
if (isChecked) {
|
||||||
if (!wouldBeMasterKey) {
|
mExpiryRow.setVisibility(View.GONE);
|
||||||
choices.add(new Choice(Constants.choice.algorithm.elgamal, getResources().getString(
|
} else {
|
||||||
R.string.elgamal)));
|
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)));
|
R.string.rsa)));
|
||||||
|
|
||||||
ArrayAdapter<Choice> adapter = new ArrayAdapter<Choice>(context,
|
ArrayAdapter<Choice> adapter = new ArrayAdapter<Choice>(context,
|
||||||
android.R.layout.simple_spinner_item, choices);
|
android.R.layout.simple_spinner_item, choices);
|
||||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||||
mAlgorithmSpinner.setAdapter(adapter);
|
mAlgorithmSpinner.setAdapter(adapter);
|
||||||
// make RSA the default
|
// make RSA the default
|
||||||
for (int i = 0; i < choices.size(); ++i) {
|
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);
|
mAlgorithmSpinner.setSelection(i);
|
||||||
break;
|
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
|
// 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,
|
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))));
|
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.setAdapter(keySizeAdapter);
|
||||||
mKeySizeSpinner.setSelection(1); // Default to 4096 for the key length
|
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,
|
dialog.setPositiveButton(android.R.string.ok,
|
||||||
new DialogInterface.OnClickListener() {
|
new DialogInterface.OnClickListener() {
|
||||||
@ -131,7 +168,39 @@ public class CreateKeyDialogFragment extends DialogFragment {
|
|||||||
di.dismiss();
|
di.dismiss();
|
||||||
Choice newKeyAlgorithmChoice = (Choice) mAlgorithmSpinner.getSelectedItem();
|
Choice newKeyAlgorithmChoice = (Choice) mAlgorithmSpinner.getSelectedItem();
|
||||||
int newKeySize = getProperKeyLength(newKeyAlgorithmChoice.getId(), getSelectedKeyLength());
|
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) {
|
public void onClick(DialogInterface di, int id) {
|
||||||
di.dismiss();
|
di.dismiss();
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
final AlertDialog alertDialog = dialog.show();
|
final AlertDialog alertDialog = dialog.show();
|
||||||
|
|
||||||
@ -224,12 +294,12 @@ public class CreateKeyDialogFragment extends DialogFragment {
|
|||||||
final int[] elGamalSupportedLengths = {1536, 2048, 3072, 4096, 8192};
|
final int[] elGamalSupportedLengths = {1536, 2048, 3072, 4096, 8192};
|
||||||
int properKeyLength = -1;
|
int properKeyLength = -1;
|
||||||
switch (algorithmId) {
|
switch (algorithmId) {
|
||||||
case Constants.choice.algorithm.rsa:
|
case PublicKeyAlgorithmTags.RSA_GENERAL:
|
||||||
if (currentKeyLength > 1024 && currentKeyLength <= 8192) {
|
if (currentKeyLength > 1024 && currentKeyLength <= 16384) {
|
||||||
properKeyLength = currentKeyLength + ((8 - (currentKeyLength % 8)) % 8);
|
properKeyLength = currentKeyLength + ((8 - (currentKeyLength % 8)) % 8);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Constants.choice.algorithm.elgamal:
|
case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT:
|
||||||
int[] elGammalKeyDiff = new int[elGamalSupportedLengths.length];
|
int[] elGammalKeyDiff = new int[elGamalSupportedLengths.length];
|
||||||
for (int i = 0; i < elGamalSupportedLengths.length; i++) {
|
for (int i = 0; i < elGamalSupportedLengths.length; i++) {
|
||||||
elGammalKeyDiff[i] = Math.abs(elGamalSupportedLengths[i] - currentKeyLength);
|
elGammalKeyDiff[i] = Math.abs(elGamalSupportedLengths[i] - currentKeyLength);
|
||||||
@ -244,7 +314,7 @@ public class CreateKeyDialogFragment extends DialogFragment {
|
|||||||
}
|
}
|
||||||
properKeyLength = elGamalSupportedLengths[minimalIndex];
|
properKeyLength = elGamalSupportedLengths[minimalIndex];
|
||||||
break;
|
break;
|
||||||
case Constants.choice.algorithm.dsa:
|
case PublicKeyAlgorithmTags.DSA:
|
||||||
if (currentKeyLength >= 512 && currentKeyLength <= 1024) {
|
if (currentKeyLength >= 512 && currentKeyLength <= 1024) {
|
||||||
properKeyLength = currentKeyLength + ((64 - (currentKeyLength % 64)) % 64);
|
properKeyLength = currentKeyLength + ((64 - (currentKeyLength % 64)) % 64);
|
||||||
}
|
}
|
||||||
@ -283,15 +353,15 @@ public class CreateKeyDialogFragment extends DialogFragment {
|
|||||||
final Object selectedItem = mKeySizeSpinner.getSelectedItem();
|
final Object selectedItem = mKeySizeSpinner.getSelectedItem();
|
||||||
keySizeAdapter.clear();
|
keySizeAdapter.clear();
|
||||||
switch (algorithmId) {
|
switch (algorithmId) {
|
||||||
case Constants.choice.algorithm.rsa:
|
case PublicKeyAlgorithmTags.RSA_GENERAL:
|
||||||
replaceArrayAdapterContent(keySizeAdapter, R.array.rsa_key_size_spinner_values);
|
replaceArrayAdapterContent(keySizeAdapter, R.array.rsa_key_size_spinner_values);
|
||||||
mCustomKeyInfoTextView.setText(getResources().getString(R.string.key_size_custom_info_rsa));
|
mCustomKeyInfoTextView.setText(getResources().getString(R.string.key_size_custom_info_rsa));
|
||||||
break;
|
break;
|
||||||
case Constants.choice.algorithm.elgamal:
|
case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT:
|
||||||
replaceArrayAdapterContent(keySizeAdapter, R.array.elgamal_key_size_spinner_values);
|
replaceArrayAdapterContent(keySizeAdapter, R.array.elgamal_key_size_spinner_values);
|
||||||
mCustomKeyInfoTextView.setText(""); // ElGamal does not support custom key length
|
mCustomKeyInfoTextView.setText(""); // ElGamal does not support custom key length
|
||||||
break;
|
break;
|
||||||
case Constants.choice.algorithm.dsa:
|
case PublicKeyAlgorithmTags.DSA:
|
||||||
replaceArrayAdapterContent(keySizeAdapter, R.array.dsa_key_size_spinner_values);
|
replaceArrayAdapterContent(keySizeAdapter, R.array.dsa_key_size_spinner_values);
|
||||||
mCustomKeyInfoTextView.setText(getResources().getString(R.string.key_size_custom_info_dsa));
|
mCustomKeyInfoTextView.setText(getResources().getString(R.string.key_size_custom_info_dsa));
|
||||||
break;
|
break;
|
@ -29,7 +29,6 @@ import android.os.Messenger;
|
|||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.support.v4.app.DialogFragment;
|
import android.support.v4.app.DialogFragment;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.text.TextWatcher;
|
import android.text.TextWatcher;
|
||||||
import android.util.Patterns;
|
import android.util.Patterns;
|
||||||
import android.view.KeyEvent;
|
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() {
|
alert.setNegativeButton(android.R.string.cancel, new OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(DialogInterface dialog, int id) {
|
public void onClick(DialogInterface dialog, int id) {
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -18,40 +18,21 @@
|
|||||||
package org.sufficientlysecure.keychain.ui.dialog;
|
package org.sufficientlysecure.keychain.ui.dialog;
|
||||||
|
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
import android.app.ProgressDialog;
|
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Message;
|
|
||||||
import android.os.Messenger;
|
|
||||||
import android.provider.DocumentsContract;
|
import android.provider.DocumentsContract;
|
||||||
import android.support.v4.app.DialogFragment;
|
import android.support.v4.app.DialogFragment;
|
||||||
import android.support.v4.app.FragmentActivity;
|
import android.support.v4.app.FragmentActivity;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
import org.sufficientlysecure.keychain.helper.FileHelper;
|
||||||
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
|
||||||
|
|
||||||
public class DeleteFileDialogFragment extends DialogFragment {
|
public class DeleteFileDialogFragment extends DialogFragment {
|
||||||
private static final String ARG_DELETE_FILE = "delete_file";
|
|
||||||
private static final String ARG_DELETE_URI = "delete_uri";
|
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
|
* Creates new instance of this delete file dialog fragment
|
||||||
*/
|
*/
|
||||||
@ -73,15 +54,15 @@ public class DeleteFileDialogFragment extends DialogFragment {
|
|||||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||||
final FragmentActivity activity = getActivity();
|
final FragmentActivity activity = getActivity();
|
||||||
|
|
||||||
final Uri deleteUri = getArguments().containsKey(ARG_DELETE_URI) ? getArguments().<Uri>getParcelable(ARG_DELETE_URI) : null;
|
final Uri deleteUri = getArguments().getParcelable(ARG_DELETE_URI);
|
||||||
final String deleteFile = getArguments().getString(ARG_DELETE_FILE);
|
final String deleteFilename = FileHelper.getFilename(getActivity(), deleteUri);
|
||||||
|
|
||||||
CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(activity);
|
CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(activity);
|
||||||
|
|
||||||
|
|
||||||
alert.setIcon(R.drawable.ic_dialog_alert_holo_light);
|
alert.setIcon(R.drawable.ic_dialog_alert_holo_light);
|
||||||
alert.setTitle(R.string.warning);
|
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() {
|
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) {
|
public void onClick(DialogInterface dialog, int id) {
|
||||||
dismiss();
|
dismiss();
|
||||||
|
|
||||||
if (deleteUri != null) {
|
// We can not securely delete Uris, so just use usual delete on them
|
||||||
// We can not securely delete Documents, so just use usual delete on them
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||||
DocumentsContract.deleteDocument(getActivity().getContentResolver(), deleteUri);
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send all information needed to service to edit key in other thread
|
Toast.makeText(getActivity(), getActivity().getString(R.string.error_file_delete_failed, deleteFilename), Toast.LENGTH_SHORT).show();
|
||||||
Intent intent = new Intent(activity, KeychainIntentService.class);
|
|
||||||
|
|
||||||
// fill values for this action
|
// Note: We can't delete every file...
|
||||||
Bundle data = new Bundle();
|
// If possible we should find out if deletion is possible before even showing the option to do so.
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
|
alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
|
||||||
|
@ -123,7 +123,7 @@ public class DeleteKeyDialogFragment extends DialogFragment {
|
|||||||
boolean success = false;
|
boolean success = false;
|
||||||
for (long masterKeyId : masterKeyIds) {
|
for (long masterKeyId : masterKeyIds) {
|
||||||
int count = activity.getContentResolver().delete(
|
int count = activity.getContentResolver().delete(
|
||||||
KeyRingData.buildPublicKeyRingUri(Long.toString(masterKeyId)), null, null
|
KeyRingData.buildPublicKeyRingUri(masterKeyId), null, null
|
||||||
);
|
);
|
||||||
success = count > 0;
|
success = count > 0;
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -32,6 +32,9 @@ import org.sufficientlysecure.keychain.util.Log;
|
|||||||
public class EditUserIdDialogFragment extends DialogFragment {
|
public class EditUserIdDialogFragment extends DialogFragment {
|
||||||
private static final String ARG_MESSENGER = "messenger";
|
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_CHANGE_PRIMARY_USER_ID = 1;
|
||||||
public static final int MESSAGE_REVOKE = 2;
|
public static final int MESSAGE_REVOKE = 2;
|
||||||
|
|
||||||
@ -40,10 +43,13 @@ public class EditUserIdDialogFragment extends DialogFragment {
|
|||||||
/**
|
/**
|
||||||
* Creates new instance of this dialog fragment
|
* 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();
|
EditUserIdDialogFragment frag = new EditUserIdDialogFragment();
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
args.putParcelable(ARG_MESSENGER, messenger);
|
args.putParcelable(ARG_MESSENGER, messenger);
|
||||||
|
args.putBoolean(ARG_IS_REVOKED, isRevoked);
|
||||||
|
args.putBoolean(ARG_IS_REVOKED_PENDING, isRevokedPending);
|
||||||
|
|
||||||
frag.setArguments(args);
|
frag.setArguments(args);
|
||||||
|
|
||||||
@ -56,13 +62,33 @@ public class EditUserIdDialogFragment extends DialogFragment {
|
|||||||
@Override
|
@Override
|
||||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||||
mMessenger = getArguments().getParcelable(ARG_MESSENGER);
|
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());
|
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);
|
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() {
|
builder.setItems(array, new DialogInterface.OnClickListener() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
switch (which) {
|
switch (which) {
|
||||||
@ -77,6 +103,8 @@ public class EditUserIdDialogFragment extends DialogFragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
|
builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(DialogInterface dialog, int id) {
|
public void onClick(DialogInterface dialog, int id) {
|
||||||
|
@ -18,18 +18,15 @@
|
|||||||
package org.sufficientlysecure.keychain.ui.dialog;
|
package org.sufficientlysecure.keychain.ui.dialog;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.AlertDialog;
|
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.database.Cursor;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
import android.os.Messenger;
|
import android.os.Messenger;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.provider.OpenableColumns;
|
|
||||||
import android.support.v4.app.DialogFragment;
|
import android.support.v4.app.DialogFragment;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@ -42,7 +39,13 @@ import org.sufficientlysecure.keychain.Constants;
|
|||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.helper.FileHelper;
|
import org.sufficientlysecure.keychain.helper.FileHelper;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
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 {
|
public class FileDialogFragment extends DialogFragment {
|
||||||
private static final String ARG_MESSENGER = "messenger";
|
private static final String ARG_MESSENGER = "messenger";
|
||||||
private static final String ARG_TITLE = "title";
|
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 int MESSAGE_OKAY = 1;
|
||||||
|
|
||||||
public static final String MESSAGE_DATA_URI = "uri";
|
public static final String MESSAGE_DATA_FILE = "file";
|
||||||
public static final String MESSAGE_DATA_FILENAME = "filename";
|
|
||||||
public static final String MESSAGE_DATA_CHECKED = "checked";
|
public static final String MESSAGE_DATA_CHECKED = "checked";
|
||||||
|
|
||||||
private Messenger mMessenger;
|
private Messenger mMessenger;
|
||||||
@ -63,8 +65,7 @@ public class FileDialogFragment extends DialogFragment {
|
|||||||
private CheckBox mCheckBox;
|
private CheckBox mCheckBox;
|
||||||
private TextView mMessageTextView;
|
private TextView mMessageTextView;
|
||||||
|
|
||||||
private String mOutputFilename;
|
private File mFile;
|
||||||
private Uri mOutputUri;
|
|
||||||
|
|
||||||
private static final int REQUEST_CODE = 0x00007004;
|
private static final int REQUEST_CODE = 0x00007004;
|
||||||
|
|
||||||
@ -72,14 +73,14 @@ public class FileDialogFragment extends DialogFragment {
|
|||||||
* Creates new instance of this file dialog fragment
|
* Creates new instance of this file dialog fragment
|
||||||
*/
|
*/
|
||||||
public static FileDialogFragment newInstance(Messenger messenger, String title, String message,
|
public static FileDialogFragment newInstance(Messenger messenger, String title, String message,
|
||||||
String defaultFile, String checkboxText) {
|
File defaultFile, String checkboxText) {
|
||||||
FileDialogFragment frag = new FileDialogFragment();
|
FileDialogFragment frag = new FileDialogFragment();
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
args.putParcelable(ARG_MESSENGER, messenger);
|
args.putParcelable(ARG_MESSENGER, messenger);
|
||||||
|
|
||||||
args.putString(ARG_TITLE, title);
|
args.putString(ARG_TITLE, title);
|
||||||
args.putString(ARG_MESSAGE, message);
|
args.putString(ARG_MESSAGE, message);
|
||||||
args.putString(ARG_DEFAULT_FILE, defaultFile);
|
args.putString(ARG_DEFAULT_FILE, defaultFile.getAbsolutePath());
|
||||||
args.putString(ARG_CHECKBOX_TEXT, checkboxText);
|
args.putString(ARG_CHECKBOX_TEXT, checkboxText);
|
||||||
|
|
||||||
frag.setArguments(args);
|
frag.setArguments(args);
|
||||||
@ -98,7 +99,11 @@ public class FileDialogFragment extends DialogFragment {
|
|||||||
|
|
||||||
String title = getArguments().getString(ARG_TITLE);
|
String title = getArguments().getString(ARG_TITLE);
|
||||||
String message = getArguments().getString(ARG_MESSAGE);
|
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);
|
String checkboxText = getArguments().getString(ARG_CHECKBOX_TEXT);
|
||||||
|
|
||||||
LayoutInflater inflater = (LayoutInflater) activity
|
LayoutInflater inflater = (LayoutInflater) activity
|
||||||
@ -112,18 +117,14 @@ public class FileDialogFragment extends DialogFragment {
|
|||||||
mMessageTextView.setText(message);
|
mMessageTextView.setText(message);
|
||||||
|
|
||||||
mFilename = (EditText) view.findViewById(R.id.input);
|
mFilename = (EditText) view.findViewById(R.id.input);
|
||||||
mFilename.setText(mOutputFilename);
|
mFilename.setText(mFile.getName());
|
||||||
mBrowse = (ImageButton) view.findViewById(R.id.btn_browse);
|
mBrowse = (ImageButton) view.findViewById(R.id.btn_browse);
|
||||||
mBrowse.setOnClickListener(new View.OnClickListener() {
|
mBrowse.setOnClickListener(new View.OnClickListener() {
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
// only .asc or .gpg files
|
// only .asc or .gpg files
|
||||||
// setting it to text/plain prevents Cynaogenmod's file manager from selecting asc
|
// setting it to text/plain prevents Cynaogenmod's file manager from selecting asc
|
||||||
// or gpg types!
|
// or gpg types!
|
||||||
if (Constants.KITKAT) {
|
FileHelper.openFile(FileDialogFragment.this, Uri.fromFile(mFile), "*/*", REQUEST_CODE);
|
||||||
FileHelper.saveDocument(FileDialogFragment.this, mOutputUri, "*/*", REQUEST_CODE);
|
|
||||||
} else {
|
|
||||||
FileHelper.openFile(FileDialogFragment.this, mOutputFilename, "*/*", REQUEST_CODE);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -146,19 +147,23 @@ public class FileDialogFragment extends DialogFragment {
|
|||||||
dismiss();
|
dismiss();
|
||||||
|
|
||||||
String currentFilename = mFilename.getText().toString();
|
String currentFilename = mFilename.getText().toString();
|
||||||
if (mOutputFilename == null || !mOutputFilename.equals(currentFilename)) {
|
if (currentFilename == null || currentFilename.isEmpty()) {
|
||||||
mOutputUri = null;
|
// No file is like pressing cancel, UI: maybe disable positive button in this case?
|
||||||
mOutputFilename = mFilename.getText().toString();
|
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();
|
boolean checked = mCheckBox.isEnabled() && mCheckBox.isChecked();
|
||||||
|
|
||||||
// return resulting data back to activity
|
// return resulting data back to activity
|
||||||
Bundle data = new Bundle();
|
Bundle data = new Bundle();
|
||||||
if (mOutputUri != null) {
|
data.putString(MESSAGE_DATA_FILE, mFile.getAbsolutePath());
|
||||||
data.putParcelable(MESSAGE_DATA_URI, mOutputUri);
|
|
||||||
}
|
|
||||||
data.putString(MESSAGE_DATA_FILENAME, mFilename.getText().toString());
|
|
||||||
data.putBoolean(MESSAGE_DATA_CHECKED, checked);
|
data.putBoolean(MESSAGE_DATA_CHECKED, checked);
|
||||||
|
|
||||||
sendMessageToHandler(MESSAGE_OKAY, data);
|
sendMessageToHandler(MESSAGE_OKAY, data);
|
||||||
@ -175,44 +180,17 @@ public class FileDialogFragment extends DialogFragment {
|
|||||||
return alert.show();
|
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
|
@Override
|
||||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
switch (requestCode & 0xFFFF) {
|
switch (requestCode & 0xFFFF) {
|
||||||
case REQUEST_CODE: {
|
case REQUEST_CODE: {
|
||||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||||
if (Constants.KITKAT) {
|
File file = new File(data.getData().getPath());
|
||||||
mOutputUri = data.getData();
|
if (file.getParentFile().exists()) {
|
||||||
Cursor cursor = getActivity().getContentResolver().query(mOutputUri, new String[]{OpenableColumns.DISPLAY_NAME}, null, null, null);
|
mFile = file;
|
||||||
if (cursor != null) {
|
mFilename.setText(mFile.getName());
|
||||||
if (cursor.moveToNext()) {
|
|
||||||
mOutputFilename = cursor.getString(0);
|
|
||||||
mFilename.setText(mOutputFilename);
|
|
||||||
}
|
|
||||||
cursor.close();
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
try {
|
Notify.showNotify(getActivity(), R.string.no_file_selected, Notify.Style.ERROR);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,7 +31,6 @@ import android.text.TextUtils;
|
|||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.WindowManager.LayoutParams;
|
|
||||||
import android.view.inputmethod.EditorInfo;
|
import android.view.inputmethod.EditorInfo;
|
||||||
import android.view.inputmethod.InputMethodManager;
|
import android.view.inputmethod.InputMethodManager;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -24,9 +24,9 @@ import android.view.LayoutInflater;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.animation.AlphaAnimation;
|
import android.view.animation.AlphaAnimation;
|
||||||
import android.view.animation.Animation;
|
import android.view.animation.Animation;
|
||||||
|
import android.widget.ImageButton;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.ImageButton;
|
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
|
|
||||||
|
@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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; }
|
|
||||||
}
|
|
@ -20,6 +20,7 @@ package org.sufficientlysecure.keychain.util;
|
|||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
|
||||||
|
import org.spongycastle.bcpg.CompressionAlgorithmTags;
|
||||||
import org.spongycastle.bcpg.HashAlgorithmTags;
|
import org.spongycastle.bcpg.HashAlgorithmTags;
|
||||||
import org.spongycastle.openpgp.PGPEncryptedData;
|
import org.spongycastle.openpgp.PGPEncryptedData;
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
@ -57,13 +58,13 @@ public class AlgorithmNames {
|
|||||||
mHashNames.put(HashAlgorithmTags.SHA384, "SHA-384");
|
mHashNames.put(HashAlgorithmTags.SHA384, "SHA-384");
|
||||||
mHashNames.put(HashAlgorithmTags.SHA512, "SHA-512");
|
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) + ")");
|
+ " (" + mActivity.getString(R.string.compression_fast) + ")");
|
||||||
mCompressionNames.put(Constants.choice.compression.zip,
|
mCompressionNames.put(CompressionAlgorithmTags.ZIP,
|
||||||
"ZIP (" + mActivity.getString(R.string.compression_fast) + ")");
|
"ZIP (" + mActivity.getString(R.string.compression_fast) + ")");
|
||||||
mCompressionNames.put(Constants.choice.compression.zlib,
|
mCompressionNames.put(CompressionAlgorithmTags.ZLIB,
|
||||||
"ZLIB (" + mActivity.getString(R.string.compression_fast) + ")");
|
"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) + ")");
|
"BZIP2 (" + mActivity.getString(R.string.compression_very_slow) + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
BIN
OpenKeychain/src/main/res/drawable-hdpi/attachment_bg_holo.9.png
Normal file
After Width: | Height: | Size: 282 B |
Before Width: | Height: | Size: 450 B |
Before Width: | Height: | Size: 485 B |
Before Width: | Height: | Size: 473 B |
BIN
OpenKeychain/src/main/res/drawable-hdpi/ic_doc_generic_am.png
Normal file
After Width: | Height: | Size: 694 B |
BIN
OpenKeychain/src/main/res/drawable-hdpi/ic_generic_man.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 2.0 KiB |