diff --git a/.travis.yml b/.travis.yml
index e4433144b..74ea7a58d 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -9,10 +9,12 @@ sudo: false
# - ADB_INSTALL_TIMEOUT=8 # minutes (2 minutes by default)
android:
components:
+ - build-tools-23.0.1
- build-tools-22.0.1
- build-tools-21.1.2
- build-tools-21.1.1
- build-tools-19.1.0
+ - android-23
- android-22
- android-21
- android-19
diff --git a/Graphics/drawables/ic_action_encrypt_paste.svg b/Graphics/drawables/ic_action_encrypt_paste.svg
new file mode 100644
index 000000000..588c1d2ca
--- /dev/null
+++ b/Graphics/drawables/ic_action_encrypt_paste.svg
@@ -0,0 +1,59 @@
+
+
diff --git a/Graphics/drawables/ic_action_encrypt_save.svg b/Graphics/drawables/ic_action_encrypt_save.svg
index 7bb435a5d..8502420f3 100644
--- a/Graphics/drawables/ic_action_encrypt_save.svg
+++ b/Graphics/drawables/ic_action_encrypt_save.svg
@@ -12,7 +12,7 @@
height="24"
viewBox="0 0 24 24"
id="svg2"
- inkscape:version="0.48.3.1 r9886"
+ inkscape:version="0.48.5 r10040"
sodipodi:docname="ic_action_encrypt_save.svg">
@@ -41,8 +41,8 @@
id="namedview6"
showgrid="false"
inkscape:zoom="9.8333333"
- inkscape:cx="12.20339"
- inkscape:cy="12"
+ inkscape:cx="-4.7288134"
+ inkscape:cy="11.949153"
inkscape:window-x="0"
inkscape:window-y="19"
inkscape:window-maximized="1"
@@ -50,10 +50,11 @@
+ id="path4-7"
+ style="fill:#000000;fill-opacity:1" />
+ style="fill:#000000;fill-opacity:1" />
diff --git a/Graphics/drawables/originals/content-paste.svg b/Graphics/drawables/originals/content-paste.svg
new file mode 100644
index 000000000..11ea8f23d
--- /dev/null
+++ b/Graphics/drawables/originals/content-paste.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/Graphics/twitter_header.png b/Graphics/twitter_header.png
new file mode 100644
index 000000000..498a3c093
Binary files /dev/null and b/Graphics/twitter_header.png differ
diff --git a/Graphics/update-drawables.sh b/Graphics/update-drawables.sh
index 7dcf1d60f..18ed1b63a 100755
--- a/Graphics/update-drawables.sh
+++ b/Graphics/update-drawables.sh
@@ -22,7 +22,7 @@ SRC_DIR=./drawables/
#inkscape -w 512 -h 512 -e "$PLAY_DIR/$NAME.png" $NAME.svg
-for NAME in "ic_cloud_search" "ic_action_encrypt_file" "ic_action_encrypt_text" "ic_action_verified_cutout" "ic_action_encrypt_copy" "ic_action_encrypt_save" "ic_action_encrypt_share" "status_lock_closed" "status_lock_error" "status_lock_open" "status_signature_expired_cutout" "status_signature_invalid_cutout" "status_signature_revoked_cutout" "status_signature_unknown_cutout" "status_signature_unverified_cutout" "status_signature_verified_cutout" "key_flag_authenticate" "key_flag_certify" "key_flag_encrypt" "key_flag_sign" "yubi_icon" "ic_stat_notify" "status_signature_verified_inner" "link" "octo_link"
+for NAME in "ic_cloud_search" "ic_action_encrypt_file" "ic_action_encrypt_text" "ic_action_verified_cutout" "ic_action_encrypt_copy" "ic_action_encrypt_paste" "ic_action_encrypt_save" "ic_action_encrypt_share" "status_lock_closed" "status_lock_error" "status_lock_open" "status_signature_expired_cutout" "status_signature_invalid_cutout" "status_signature_revoked_cutout" "status_signature_unknown_cutout" "status_signature_unverified_cutout" "status_signature_verified_cutout" "key_flag_authenticate" "key_flag_certify" "key_flag_encrypt" "key_flag_sign" "yubi_icon" "ic_stat_notify" "status_signature_verified_inner" "link" "octo_link"
do
echo $NAME
inkscape -w 24 -h 24 -e "$MDPI_DIR/${NAME}_24dp.png" "$SRC_DIR/$NAME.svg"
diff --git a/OpenKeychain/build.gradle b/OpenKeychain/build.gradle
index 73bad1dd8..8c707d4a4 100644
--- a/OpenKeychain/build.gradle
+++ b/OpenKeychain/build.gradle
@@ -8,11 +8,11 @@ dependencies {
// NOTE: libraries are pinned to a specific build, see below
// from local Android SDK
- compile 'com.android.support:support-v4:22.2.1'
- compile 'com.android.support:appcompat-v7:22.2.1'
- compile 'com.android.support:design:22.2.1'
- compile 'com.android.support:recyclerview-v7:22.2.1'
- compile 'com.android.support:cardview-v7:22.2.1'
+ compile 'com.android.support:support-v4:23.1.1'
+ compile 'com.android.support:appcompat-v7:23.1.1'
+ compile 'com.android.support:design:23.1.1'
+ compile 'com.android.support:recyclerview-v7:23.1.1'
+ compile 'com.android.support:cardview-v7:23.1.1'
// Unit tests in the local JVM with Robolectric
// https://developer.android.com/training/testing/unit-testing/local-unit-tests.html
@@ -20,7 +20,7 @@ dependencies {
// http://www.vogella.com/tutorials/Robolectric/article.html
testCompile 'junit:junit:4.12'
testCompile 'org.robolectric:robolectric:3.0'
- testCompile 'org.mockito:mockito-core:1.+'
+ testCompile 'org.mockito:mockito-core:1.10.19'
// UI testing with Espresso
androidTestCompile 'com.android.support.test:runner:0.3'
@@ -36,26 +36,26 @@ dependencies {
// Temporary workaround for bug: https://code.google.com/p/android-test-kit/issues/detail?id=136
// from https://github.com/googlesamples/android-testing/blob/master/build.gradle#L21
configurations.all {
- resolutionStrategy.force 'com.android.support:support-annotations:22.2.0'
+ resolutionStrategy.force 'com.android.support:support-annotations:23.1.1'
}
// JCenter etc.
- compile 'com.eftimoff:android-patternview:1.0.1@aar'
- compile 'com.journeyapps:zxing-android-embedded:2.3.0@aar'
- compile 'com.journeyapps:zxing-android-integration:2.3.0@aar'
+ compile 'com.eftimoff:android-patternview:1.0.3@aar'
+ compile 'com.journeyapps:zxing-android-embedded:3.0.2@aar'
compile 'com.google.zxing:core:3.2.0'
- compile 'com.jpardogo.materialtabstrip:library:1.0.9'
- compile 'com.getbase:floatingactionbutton:1.9.0'
+ compile 'com.jpardogo.materialtabstrip:library:1.1.0'
+ compile 'com.getbase:floatingactionbutton:1.10.1'
compile 'org.commonjava.googlecode.markdown4j:markdown4j:2.2-cj-1.0'
compile 'org.ocpsoft.prettytime:prettytime:3.2.7.Final'
- compile "com.splitwise:tokenautocomplete:1.3.3@aar"
- compile 'se.emilsjolander:stickylistheaders:2.6.0'
- compile 'org.sufficientlysecure:html-textview:1.2'
- compile 'com.mikepenz:materialdrawer:3.0.9@aar'
- compile 'com.mikepenz:iconics:1.0.2'
- compile 'com.mikepenz.iconics:octicons-typeface:2.2.0@aar'
- compile 'com.mikepenz.iconics:meteocons-typeface:1.1.1@aar'
- compile 'com.mikepenz.iconics:community-material-typeface:1.0.0@aar'
+ compile 'com.splitwise:tokenautocomplete:2.0.2@aar'
+ compile 'se.emilsjolander:stickylistheaders:2.7.0'
+ compile 'org.sufficientlysecure:html-textview:1.3'
+ compile 'com.mikepenz:materialdrawer:4.4.2@aar'
+ compile 'com.mikepenz:materialize:0.2.7'
+ compile 'com.mikepenz:iconics-core:1.7.9@aar'
+ compile 'com.mikepenz:google-material-typeface:1.2.0.1@aar'
+ compile 'com.mikepenz:fontawesome-typeface:4.4.0.1@aar'
+ compile 'com.mikepenz:community-material-typeface:1.2.64.1@aar'
compile 'com.nispok:snackbar:2.11.0'
compile 'com.squareup.okhttp:okhttp:2.5.0'
compile 'com.squareup.okhttp:okhttp-urlconnection:2.5.0'
@@ -80,44 +80,44 @@ dependencies {
// Comment out the libs referenced as git submodules!
dependencyVerification {
verify = [
- 'com.android.support:support-v4:c62f0d025dafa86f423f48df9185b0d89496adbc5f6a9be5a7c394d84cf91423',
- 'com.android.support:appcompat-v7:4b5ccba8c4557ef04f99aa0a80f8aa7d50f05f926a709010a54afd5c878d3618',
- 'com.android.support:design:58be3ca6a73789615f7ece0937d2f683b98b594bb90aa10565fa760fb10b07ee',
- 'com.android.support:recyclerview-v7:b0f530a5b14334d56ce0de85527ffe93ac419bc928e2884287ce1dddfedfb505',
- 'com.android.support:cardview-v7:2c2354761a4e20ba451ae903ab808f15c9acc8343b1e74001869c2d0a672c1fc',
- 'com.eftimoff:android-patternview:cec80e7265b8d8278b3c55b5fcdf551e4600ac2c8bf60d8dd76adca538af0b1e',
- 'com.journeyapps:zxing-android-embedded:702a4f58154dbd9baa80f66b6a15410f7a4d403f3e73b66537a8bfb156b4b718',
- 'com.journeyapps:zxing-android-integration:562737821b6d34c899b6fd2234ce0a8a31e02ff1fd7c59f6211961ce9767c7c8',
+ 'com.android.support:support-v4:5c7dceb6c824089fe80f502e5206264048ef8bffa4e8ddeab180b81723e79b7f',
+ 'com.android.support:appcompat-v7:0a8762214382b7e8d4b989b4ac10b5c846b957d767ccb7bccbc6be5afa885a82',
+ 'com.android.support:design:41a9cd75ca78f25df5f573db7cedf8bb66beae00c330943923ba9f3e2051736d',
+ 'com.android.support:recyclerview-v7:7606373da0931a1e62588335465a0e390cd676c98117edab29220317495faefd',
+ 'com.android.support:cardview-v7:5a5bc04a278662bfafdea5b11b2108a4b354dca6c68958b312f6f45cc5fe2e38',
+ 'com.eftimoff:android-patternview:2e7a2bbfb4fed229d4b5598aa4e69e45066fbea72c971d69461db7d916cb7ebc',
+ 'com.journeyapps:zxing-android-embedded:561c5d94391342bb77689b8d32a320d085a11853f72afda1128d595b815ef563',
'com.google.zxing:core:7fe5a8ff437635a540e56317649937b768b454795ce999ed5f244f83373dee7b',
- 'com.jpardogo.materialtabstrip:library:c6ef812fba4f74be7dc4a905faa4c2908cba261a94c13d4f96d5e67e4aad4aaa',
- 'com.getbase:floatingactionbutton:052aa2a94e49e5dccc97cb99f2add87e8698b84859f0e3ac181100c0bc7640ca',
+ 'com.jpardogo.materialtabstrip:library:24d19232b319f8c73e25793432357919a7ed972186f57a3b2c9093ea74ad8311',
+ 'com.getbase:floatingactionbutton:3edefa511aac4d90794c7b0496aca59cff2eee1e32679247b4f85acbeee05240',
'org.commonjava.googlecode.markdown4j:markdown4j:e952e825d29e1317d96f79f346bfb6786c7c5eef50bd26e54a80823704b62e13',
'org.ocpsoft.prettytime:prettytime:a6bc2641b3ab7873df604b77b6680c75b86d98e78afefb367940972f925591b5',
- 'com.splitwise:tokenautocomplete:20bee71cc59b3828eb000b684d46ddf738efd56b8fee453a509cd16fda42c8cb',
- 'se.emilsjolander:stickylistheaders:8c05981ec5725be33f7cee5e68c13f3db49cd5c75f1aaeb04024920b1ef96ad4',
- 'com.mikepenz:materialdrawer:70c3efb3842461db41df6a918ea93969a7da21e63c092be838b153e5a47a17bf',
- 'org.sufficientlysecure:html-textview:1d3bed31ef837437154de8d2362a0e6b0e59b6c3535d87ee48c2fab12c84f9bb',
- 'com.mikepenz.iconics:octicons-typeface:67ed7d456a9ce5f5307b85f955797bfb3dd674e2f6defb31c6b8bbe2ede290be',
- 'com.mikepenz:iconics:c1a02203d8e0d638959463c00af3ab9096e0a7c1ad5928762eb10ef5ce8a63cd',
- 'com.mikepenz.iconics:community-material-typeface:f1c5afee5f0f10d66beb3ed0df977246a02a9c46de4e05d7c0264bcde53b6b7f',
- 'com.mikepenz.iconics:meteocons-typeface:39a8a9e70cd8287cdb119af57a672a41dd09240dba6697f5a0dbda1ccc33298b',
+ 'com.splitwise:tokenautocomplete:2fc238424130b42155b5f2e39799a90bbbd13b148850afbe534ab08bb913c7f7',
+ 'se.emilsjolander:stickylistheaders:a08ca948aa6b220f09d82f16bbbac395f6b78897e9eeac6a9f0b0ba755928eeb',
+ 'org.sufficientlysecure:html-textview:39048e35894e582adada388e6c00631803283f8defed8e07ad58a5f284f272ee',
+ 'com.mikepenz:materialize:db365f859084048ac4e9cc4254642593dbb1ae9ce25c8fc26c93e2a5fadb3480',
+ 'com.mikepenz:materialdrawer:fe9726e0f045eb3fe63832aa5383d9e2c7bcd8b87a6f26478aba1c330e9d36fe',
+ 'com.mikepenz:google-material-typeface:a8319333a7f7ca369b9b5c62913f96787d934e312acefa8c9a5fcefd394fc6ee',
+ 'com.mikepenz:iconics-core:e1ba25442c1645b7adfb7d101871c26ed64a6c5b892e9abee8d4d2a80d948d9e',
+ 'com.mikepenz:community-material-typeface:520f1065730a1171763696ac9c4e770fedbbcf1a8dad6eb1028ba29489e1a2ce',
+ 'com.mikepenz:fontawesome-typeface:8c58117eb42efe301a170049336f7838af7559d84b0cc9a2bd7aca8b130f0a50',
'com.squareup.okhttp:okhttp:1cc716e29539adcda677949508162796daffedb4794cbf947a6f65e696f0381c',
'com.nispok:snackbar:46b5eb9d630d329e13c2ce00ee9fb115ffb66c23c72cff32ee97eedd76824c6f',
'org.apache.james:apache-mime4j-core:4d7434c68f94b81a253c12f28e6bbb4d6239c361d6086a46e22e594bb43ac660',
'com.squareup.okhttp:okhttp-urlconnection:79ec6f4e79e683105e87fe83278a531c693e538d30e3b9237000ce7c94fcb2cf',
'org.thoughtcrime.ssl.pinning:AndroidPinning:afa1d74e699257fa75cb109ff29bac50726ef269c6e306bdeffe8223cee06ef4',
'org.apache.james:apache-mime4j-dom:7e6b06ee164a1c21b7e477249ea0b74a18fddce44764e5764085f58dd8c34633',
-// 'OpenKeychain.extern.openpgp-api-lib:openpgp-api:a3f8b2ed40aaf12169e2a4e1f25e3764aa5ccb430683e1e7ca7867471eaf2bba',
+// 'OpenKeychain.extern.openpgp-api-lib:openpgp-api:262e58d318d19e8ce8a78934136c9656fa51dc5fd026caa034c41390e0ef299d',
'com.cocosw:bottomsheet:871f5f4d6c10936569caf3528271efd77594a67aa5511765c96d7096c9b05f96',
-// 'OpenKeychain.extern.spongycastle:core:6006a83fa427f4e5c8c93458176ab5e3b54d8dba7942171cb76cb134fc574c58',
-// 'OpenKeychain.extern.openkeychain-api-lib:openkeychain-intents:8582442aa26e13c5a4bdf3588a22cb94b2fa5de6c79b84244fb575aa401fc330',
-// 'OpenKeychain.extern.spongycastle:pg:0fd64a60311c5557f230bec9b2b162c9e6e690ccc83ac6b5af6a8d616309da98',
-// 'OpenKeychain.extern.spongycastle:pkix:2348474aa27cb0461a368191d4d8fe7479a212b6365b177da131f4efa4c57f24',
-// 'OpenKeychain.extern.spongycastle:prov:85c9ed6e24c5c7e5f7ff7c22d367bb553d020693350bd1c75555e6895311bb69',
-// 'OpenKeychain.extern.safeslinger-exchange:safeslinger-exchange:274b71f8a1c383fb506342fd0f614b4a0cdb25517b5b2a1dfef9a4a2575477ed',
- 'com.android.support:support-annotations:beac5cae60bdb597df9af9c916f785c2f71f8c8ae4be9a32d4298dea85496a42',
-// 'OpenKeychain.extern:minidns:25e351fa4145e2a9b0a76658c48619b307f71432db7492e9e8a6b34aa2e9bdcf',
-// 'OpenKeychain.extern.KeybaseLib:Lib:79c78c1054b58200028211e21f2c89012dc4a1eafdb00cc99a5ce1f61ad16937',
+// 'com.madgag.spongycastle:core:d898b5b81ce9d456c65a3a2fe0b0be9b74e366aabe4ee1d13499a865cd20ee19',
+// 'OpenKeychain.extern.openkeychain-api-lib:openkeychain-intents:c25d0c05cc129e2975161f8454350a8868dbe1ffca8583e900935cb0b38db842',
+// 'com.madgag.spongycastle:pg:abd30d5a3c6ab6edbf7e2b60de3dae865bb5b5e4b41925ea8ac985e2a7fce4a0',
+// 'com.madgag.spongycastle:pkix:c5fd572191d31d2b05d7143d9e22d74ee3e8a43552c26d31114a27022b7a06ce',
+// 'com.madgag.spongycastle:prov:7a28f314e20683281254f92124f137d48173ea1dc3364a709d7cc66a5e46ec4e',
+// 'OpenKeychain.extern.safeslinger-exchange:safeslinger-exchange:75a3d23eff4d5c14fdc8912b5d593bf340f07b833ceabacbcd60a8e83b5b8b79',
+ 'com.android.support:support-annotations:f347a35b9748a4103b39a6714a77e2100f488d623fd6268e259c177b200e9d82',
+// 'OpenKeychain.extern:minidns:0084c81e30a4b06edac9b00689eeaa3cbbb9ea3b26aaa4fad205bb660ea0fcdb',
+// 'OpenKeychain.extern.KeybaseLib:Lib:c11785bf613f3fad8bf1670ef9fce1a10027cefbf2b3a67d487c6b8964f0e05a',
'com.squareup.okio:okio:114bdc1f47338a68bcbc95abf2f5cdc72beeec91812f2fcd7b521c1937876266',
]
}
@@ -128,11 +128,14 @@ android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
+ // TODO: remove org.apache dependencies in LinkedTokenResource etc.
+ useLibrary 'org.apache.http.legacy'
+
defaultConfig {
minSdkVersion 15
- targetSdkVersion 22
- versionCode 36000
- versionName "3.6"
+ targetSdkVersion 23
+ versionCode 36100
+ versionName "3.6.1"
applicationId "org.sufficientlysecure.keychain"
// the androidjunitrunner is broken regarding coverage, see here:
// https://code.google.com/p/android/issues/detail?id=170607
@@ -161,18 +164,9 @@ android {
resValue "string", "account_type", "org.sufficientlysecure.keychain.account"
resValue "string", "provider_content_authority", "org.sufficientlysecure.keychain.provider"
- // Github API ID and secret are read from gradle.properties (not in git!)
- // must use double escaping in gradle.properties! For example:
- // githubClientId="\\"7a011b66275f244d3f21\\""
- // githubClientSecret="\\"eaced8a6655719d8c6848396de97b3f5d7a89fec\\""
- if (project.hasProperty('githubClientId') &&
- project.hasProperty('githubClientSecret')) {
-
- println "Found github oauth properties"
-
- buildConfigField "String", "GITHUB_CLIENT_ID", githubClientId
- buildConfigField "String", "GITHUB_CLIENT_SECRET", githubClientSecret
- }
+ // Github API
+ buildConfigField "String", "GITHUB_CLIENT_ID", "\"c942cd81844d94e7e41b\""
+ buildConfigField "String", "GITHUB_CLIENT_SECRET", "\"f1dd17e70a0614abbd9310b00a310e23c6c8edff\""
}
debug {
@@ -186,9 +180,9 @@ android {
resValue "string", "account_type", "org.sufficientlysecure.keychain.debug.account"
resValue "string", "provider_content_authority", "org.sufficientlysecure.keychain.debug.provider"
- // Github API for debug build only
- buildConfigField "String", "GITHUB_CLIENT_ID", "\"7a011b66275f244d3f21\""
- buildConfigField "String", "GITHUB_CLIENT_SECRET", "\"eaced8a6655719d8c6848396de97b3f5d7a89fec\""
+ // Github API
+ buildConfigField "String", "GITHUB_CLIENT_ID", "\"c942cd81844d94e7e41b\""
+ buildConfigField "String", "GITHUB_CLIENT_SECRET", "\"f1dd17e70a0614abbd9310b00a310e23c6c8edff\""
// Enable code coverage (Jacoco)
testCoverageEnabled true
diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml
index 3cac4ed10..79b8dbb27 100644
--- a/OpenKeychain/src/main/AndroidManifest.xml
+++ b/OpenKeychain/src/main/AndroidManifest.xml
@@ -53,19 +53,32 @@
android:name="${applicationId}.WRITE_TEMPORARY_STORAGE"
android:protectionLevel="signature" />
-
-
-
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+ android:pathPrefix="/neo"
+ android:scheme="https" />
-
+ android:parentActivityName=".ui.ViewKeyActivity">
@@ -210,6 +221,12 @@
+
+
+
+
+
+
+ android:taskAffinity=":Nfc"
+ android:theme="@style/Theme.Keychain.Light.Dialog" />
+ android:label="@string/keyserver_sync_settings_title" />
search(final String query, Preferences.CloudSearchPrefs cloudPrefs,
- final Proxy proxy)
+ public static ArrayList search(
+ @NonNull final String query, Preferences.CloudSearchPrefs cloudPrefs, @NonNull Proxy proxy)
throws Keyserver.CloudSearchFailureException {
final ArrayList servers = new ArrayList<>();
@@ -40,10 +43,10 @@ public class CloudSearch {
final Vector problems = new Vector<>();
if (cloudPrefs.searchKeyserver) {
- servers.add(new HkpKeyserver(cloudPrefs.keyserver));
+ servers.add(new HkpKeyserver(cloudPrefs.keyserver, proxy));
}
if (cloudPrefs.searchKeybase) {
- servers.add(new KeybaseKeyserver());
+ servers.add(new KeybaseKeyserver(proxy));
}
final ImportKeysList results = new ImportKeysList(servers.size());
@@ -53,7 +56,7 @@ public class CloudSearch {
@Override
public void run() {
try {
- results.addAll(keyserver.search(query, proxy));
+ results.addAll(keyserver.search(query));
} catch (Keyserver.CloudSearchFailureException e) {
problems.add(e);
}
@@ -68,7 +71,7 @@ public class CloudSearch {
// wait for either all the searches to come back, or 10 seconds. If using proxy, wait 30 seconds.
synchronized (results) {
try {
- if (proxy != null) {
+ if (proxy == Proxy.NO_PROXY) {
results.wait(30 * SECONDS);
} else {
results.wait(10 * SECONDS);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java
index 7473705f3..c2190318b 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java
@@ -46,6 +46,8 @@ import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import android.support.annotation.NonNull;
+
import de.measite.minidns.Client;
import de.measite.minidns.Question;
import de.measite.minidns.Record;
@@ -74,6 +76,7 @@ public class HkpKeyserver extends Keyserver {
private String mHost;
private short mPort;
+ private Proxy mProxy;
private boolean mSecure;
/**
@@ -150,17 +153,17 @@ public class HkpKeyserver extends Keyserver {
* connect using {@link #PORT_DEFAULT}. However, port may be specified after colon
* ("hostname:port", eg. "p80.pool.sks-keyservers.net:80").
*/
- public HkpKeyserver(String hostAndPort) {
+ public HkpKeyserver(String hostAndPort, Proxy proxy) {
String host = hostAndPort;
short port = PORT_DEFAULT;
boolean secure = false;
String[] parts = hostAndPort.split(":");
if (parts.length > 1) {
if (!parts[0].contains(".")) { // This is not a domain or ip, so it must be a protocol name
- if (parts[0].equalsIgnoreCase("hkps") || parts[0].equalsIgnoreCase("https")) {
+ if ("hkps".equalsIgnoreCase(parts[0]) || "https".equalsIgnoreCase(parts[0])) {
secure = true;
port = PORT_DEFAULT_HKPS;
- } else if (!parts[0].equalsIgnoreCase("hkp") && !parts[0].equalsIgnoreCase("http")) {
+ } else if (!"hkp".equalsIgnoreCase(parts[0]) && !"http".equalsIgnoreCase(parts[0])) {
throw new IllegalArgumentException("Protocol " + parts[0] + " is unknown");
}
host = parts[1];
@@ -177,16 +180,18 @@ public class HkpKeyserver extends Keyserver {
}
mHost = host;
mPort = port;
+ mProxy = proxy;
mSecure = secure;
}
- public HkpKeyserver(String host, short port) {
- this(host, port, false);
+ public HkpKeyserver(String host, short port, Proxy proxy) {
+ this(host, port, proxy, false);
}
- public HkpKeyserver(String host, short port, boolean secure) {
+ public HkpKeyserver(String host, short port, Proxy proxy, boolean secure) {
mHost = host;
mPort = port;
+ mProxy = proxy;
mSecure = secure;
}
@@ -226,7 +231,7 @@ public class HkpKeyserver extends Keyserver {
return client;
}
- private String query(String request, Proxy proxy) throws QueryFailedException, HttpError {
+ private String query(String request, @NonNull Proxy proxy) throws QueryFailedException, HttpError {
try {
URL url = new URL(getUrlPrefix() + mHost + ":" + mPort + request);
Log.d(Constants.TAG, "hkp keyserver query: " + url + " Proxy: " + proxy);
@@ -243,7 +248,7 @@ public class HkpKeyserver extends Keyserver {
} catch (IOException e) {
Log.e(Constants.TAG, "IOException at HkpKeyserver", e);
throw new QueryFailedException("Keyserver '" + mHost + "' is unavailable. Check your Internet connection!" +
- (proxy == null ? "" : " Using proxy " + proxy));
+ (proxy == Proxy.NO_PROXY ? "" : " Using proxy " + proxy));
}
}
@@ -251,7 +256,7 @@ public class HkpKeyserver extends Keyserver {
* Results are sorted by creation date of key!
*/
@Override
- public ArrayList search(String query, Proxy proxy) throws QueryFailedException,
+ public ArrayList search(String query) throws QueryFailedException,
QueryNeedsRepairException {
ArrayList results = new ArrayList<>();
@@ -269,7 +274,7 @@ public class HkpKeyserver extends Keyserver {
String data;
try {
- data = query(request, proxy);
+ data = query(request, mProxy);
} catch (HttpError e) {
if (e.getData() != null) {
Log.d(Constants.TAG, "returned error data: " + e.getData().toLowerCase(Locale.ENGLISH));
@@ -373,12 +378,12 @@ public class HkpKeyserver extends Keyserver {
}
@Override
- public String get(String keyIdHex, Proxy proxy) throws QueryFailedException {
+ public String get(String keyIdHex) throws QueryFailedException {
String request = "/pks/lookup?op=get&options=mr&search=" + keyIdHex;
- Log.d(Constants.TAG, "hkp keyserver get: " + request + " using Proxy: " + proxy);
+ Log.d(Constants.TAG, "hkp keyserver get: " + request + " using Proxy: " + mProxy);
String data;
try {
- data = query(request, proxy);
+ data = query(request, mProxy);
} catch (HttpError httpError) {
Log.d(Constants.TAG, "Failed to get key at HkpKeyserver", httpError);
throw new QueryFailedException("not found");
@@ -394,7 +399,7 @@ public class HkpKeyserver extends Keyserver {
}
@Override
- public void add(String armoredKey, Proxy proxy) throws AddKeyException {
+ public void add(String armoredKey) throws AddKeyException {
try {
String path = "/pks/add";
String params;
@@ -405,7 +410,7 @@ public class HkpKeyserver extends Keyserver {
}
URL url = new URL(getUrlPrefix() + mHost + ":" + mPort + path);
- Log.d(Constants.TAG, "hkp keyserver add: " + url.toString());
+ Log.d(Constants.TAG, "hkp keyserver add: " + url);
Log.d(Constants.TAG, "params: " + params);
RequestBody body = RequestBody.create(MediaType.parse("application/x-www-form-urlencoded"), params);
@@ -417,7 +422,7 @@ public class HkpKeyserver extends Keyserver {
.post(body)
.build();
- Response response = getClient(url, proxy).newCall(request).execute();
+ Response response = getClient(url, mProxy).newCall(request).execute();
Log.d(Constants.TAG, "response code: " + response.code());
Log.d(Constants.TAG, "answer: " + response.body().string());
@@ -434,16 +439,15 @@ public class HkpKeyserver extends Keyserver {
@Override
public String toString() {
- return mHost + ":" + mPort;
+ return getUrlPrefix() + mHost + ":" + mPort;
}
/**
* Tries to find a server responsible for a given domain
*
* @return A responsible Keyserver or null if not found.
- * TODO: Add proxy functionality
*/
- public static HkpKeyserver resolve(String domain) {
+ public static HkpKeyserver resolve(String domain, Proxy proxy) {
try {
Record[] records = new Client().query(new Question("_hkp._tcp." + domain, Record.TYPE.SRV)).getAnswers();
if (records.length > 0) {
@@ -458,7 +462,7 @@ public class HkpKeyserver extends Keyserver {
Record record = records[0]; // This is our best choice
if (record.getPayload().getType() == Record.TYPE.SRV) {
return new HkpKeyserver(((SRV) record.getPayload()).getName(),
- (short) ((SRV) record.getPayload()).getPort());
+ (short) ((SRV) record.getPayload()).getPort(), proxy);
}
}
} catch (Exception ignored) {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java
index 486d658f6..e4cd6738b 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java
@@ -33,10 +33,15 @@ import java.util.List;
public class KeybaseKeyserver extends Keyserver {
public static final String ORIGIN = "keybase:keybase.io";
- private String mQuery;
+
+ Proxy mProxy;
+
+ public KeybaseKeyserver(Proxy proxy) {
+ mProxy = proxy;
+ }
@Override
- public ArrayList search(String query, Proxy proxy) throws QueryFailedException,
+ public ArrayList search(String query) throws QueryFailedException,
QueryNeedsRepairException {
ArrayList results = new ArrayList<>();
@@ -47,14 +52,13 @@ public class KeybaseKeyserver extends Keyserver {
if (query.isEmpty()) {
throw new QueryTooShortException();
}
- mQuery = query;
try {
KeybaseQuery keybaseQuery = new KeybaseQuery(new OkHttpKeybaseClient());
- keybaseQuery.setProxy(proxy);
+ keybaseQuery.setProxy(mProxy);
Iterable matches = keybaseQuery.search(query);
for (Match match : matches) {
- results.add(makeEntry(match));
+ results.add(makeEntry(match, query));
}
} catch (KeybaseException e) {
Log.e(Constants.TAG, "keybase result parsing error", e);
@@ -64,9 +68,9 @@ public class KeybaseKeyserver extends Keyserver {
return results;
}
- private ImportKeysListEntry makeEntry(Match match) throws KeybaseException {
+ private ImportKeysListEntry makeEntry(Match match, String query) throws KeybaseException {
final ImportKeysListEntry entry = new ImportKeysListEntry();
- entry.setQuery(mQuery);
+ entry.setQuery(query);
entry.addOrigin(ORIGIN);
entry.setRevoked(false); // keybase doesn’t say anything about revoked keys
@@ -102,10 +106,10 @@ public class KeybaseKeyserver extends Keyserver {
}
@Override
- public String get(String id, Proxy proxy) throws QueryFailedException {
+ public String get(String id) throws QueryFailedException {
try {
KeybaseQuery keybaseQuery = new KeybaseQuery(new OkHttpKeybaseClient());
- keybaseQuery.setProxy(proxy);
+ keybaseQuery.setProxy(mProxy);
return User.keyForUsername(keybaseQuery, id);
} catch (KeybaseException e) {
throw new QueryFailedException(e.getMessage());
@@ -113,7 +117,7 @@ public class KeybaseKeyserver extends Keyserver {
}
@Override
- public void add(String armoredKey, Proxy proxy) throws AddKeyException {
+ public void add(String armoredKey) throws AddKeyException {
throw new AddKeyException();
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/Keyserver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/Keyserver.java
index 15e0d94e9..00e8d6ac5 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/Keyserver.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/Keyserver.java
@@ -69,12 +69,12 @@ public abstract class Keyserver {
private static final long serialVersionUID = -507574859137295530L;
}
- public abstract List search(String query, Proxy proxy)
+ public abstract List search(String query)
throws QueryFailedException, QueryNeedsRepairException;
- public abstract String get(String keyIdHex, Proxy proxy) throws QueryFailedException;
+ public abstract String get(String keyIdHex) throws QueryFailedException;
- public abstract void add(String armoredKey, Proxy proxy) throws AddKeyException;
+ public abstract void add(String armoredKey) throws AddKeyException;
public static String readAll(InputStream in, String encoding) throws IOException {
ByteArrayOutputStream raw = new ByteArrayOutputStream();
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/LinkedTokenResource.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/LinkedTokenResource.java
index 998ec3ad4..e5a128e32 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/LinkedTokenResource.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/LinkedTokenResource.java
@@ -1,5 +1,7 @@
package org.sufficientlysecure.keychain.linked;
+import android.content.Context;
+
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
@@ -8,7 +10,6 @@ import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.BasicHttpParams;
import org.json.JSONException;
import org.sufficientlysecure.keychain.Constants;
-import org.sufficientlysecure.keychain.linked.resources.DnsResource;
import org.sufficientlysecure.keychain.linked.resources.GenericHttpsResource;
import org.sufficientlysecure.keychain.linked.resources.GithubResource;
import org.sufficientlysecure.keychain.linked.resources.TwitterResource;
@@ -32,8 +33,6 @@ import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import android.content.Context;
-
public abstract class LinkedTokenResource extends LinkedResource {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BackupOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BackupOperation.java
index 206212387..ae9a2c180 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BackupOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BackupOperation.java
@@ -252,7 +252,7 @@ public class BackupOperation extends BaseOperation {
ring.encode(arOutStream);
} catch (PgpGeneralException e) {
- log.add(LogType.MSG_BACKUP_ERROR_KEY, 2);
+ log.add(LogType.MSG_UPLOAD_ERROR_IO, 2);
} finally {
if (arOutStream != null) {
arOutStream.close();
@@ -273,7 +273,7 @@ public class BackupOperation extends BaseOperation {
ring.encode(arOutStream);
} catch (PgpGeneralException e) {
- log.add(LogType.MSG_BACKUP_ERROR_KEY, 2);
+ log.add(LogType.MSG_UPLOAD_ERROR_IO, 2);
} finally {
if (arOutStream != null) {
arOutStream.close();
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BenchmarkOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BenchmarkOperation.java
new file mode 100644
index 000000000..f6e157c74
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BenchmarkOperation.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2015 Dominik Schürmann
+ * Copyright (C) 2015 Vincent Breitmoser
+ * Copyright (C) 2015 Adithya Abraham Philip
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package org.sufficientlysecure.keychain.operations;
+
+
+import java.util.Random;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+
+import org.spongycastle.bcpg.HashAlgorithmTags;
+import org.spongycastle.bcpg.S2K;
+import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.operator.PBEDataDecryptorFactory;
+import org.spongycastle.openpgp.operator.PGPDigestCalculatorProvider;
+import org.spongycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
+import org.spongycastle.openpgp.operator.jcajce.JcePBEDataDecryptorFactoryBuilder;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.operations.results.BenchmarkResult;
+import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
+import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
+import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
+import org.sufficientlysecure.keychain.operations.results.SignEncryptResult;
+import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel;
+import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyOperation;
+import org.sufficientlysecure.keychain.pgp.Progressable;
+import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.service.BenchmarkInputParcel;
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
+import org.sufficientlysecure.keychain.util.Log;
+import org.sufficientlysecure.keychain.util.Passphrase;
+import org.sufficientlysecure.keychain.util.ProgressScaler;
+
+
+public class BenchmarkOperation extends BaseOperation {
+
+ public BenchmarkOperation(Context context, ProviderHelper providerHelper, Progressable
+ progressable) {
+ super(context, providerHelper, progressable);
+ }
+
+ @NonNull
+ @Override
+ public BenchmarkResult execute(BenchmarkInputParcel consolidateInputParcel,
+ CryptoInputParcel cryptoInputParcel) {
+ OperationLog log = new OperationLog();
+ log.add(LogType.MSG_BENCH, 0);
+
+ // random data
+ byte[] buf = new byte[1024*1024*5];
+ new Random().nextBytes(buf);
+
+ Passphrase passphrase = new Passphrase("a");
+
+ int numRepeats = 5;
+ long totalTime = 0;
+
+ // encrypt
+ SignEncryptResult encryptResult;
+ int i = 0;
+ do {
+ SignEncryptOperation op =
+ new SignEncryptOperation(mContext, mProviderHelper,
+ new ProgressScaler(mProgressable, i*(50/numRepeats), (i+1)*(50/numRepeats), 100), mCancelled);
+ SignEncryptParcel input = new SignEncryptParcel();
+ input.setSymmetricPassphrase(passphrase);
+ input.setBytes(buf);
+ encryptResult = op.execute(input, new CryptoInputParcel());
+ log.add(encryptResult, 1);
+ log.add(LogType.MSG_BENCH_ENC_TIME, 2,
+ String.format("%.2f", encryptResult.getResults().get(0).mOperationTime / 1000.0));
+ totalTime += encryptResult.getResults().get(0).mOperationTime;
+ } while (++i < numRepeats);
+
+ long encryptionTime = totalTime / numRepeats;
+ totalTime = 0;
+
+ // decrypt
+ i = 0;
+ do {
+ DecryptVerifyResult decryptResult;
+ PgpDecryptVerifyOperation op =
+ new PgpDecryptVerifyOperation(mContext, mProviderHelper,
+ new ProgressScaler(mProgressable, 50 +i*(50/numRepeats), 50 +(i+1)*(50/numRepeats), 100));
+ PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel(encryptResult.getResultBytes());
+ input.setAllowSymmetricDecryption(true);
+ decryptResult = op.execute(input, new CryptoInputParcel(passphrase));
+ log.add(decryptResult, 1);
+ log.add(LogType.MSG_BENCH_DEC_TIME, 2, String.format("%.2f", decryptResult.mOperationTime / 1000.0));
+ totalTime += decryptResult.mOperationTime;
+ } while (++i < numRepeats);
+
+ long decryptionTime = totalTime / numRepeats;
+ totalTime = 0;
+
+ int iterationsFor100ms;
+ try {
+ PGPDigestCalculatorProvider digestCalcProvider = new JcaPGPDigestCalculatorProviderBuilder()
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build();
+ PBEDataDecryptorFactory decryptorFactory = new JcePBEDataDecryptorFactoryBuilder(
+ digestCalcProvider).setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
+ "".toCharArray());
+
+ byte[] iv = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 };
+ int iterations = 0;
+ while (iterations < 255 && totalTime < 100) {
+ iterations += 1;
+
+ S2K s2k = new S2K(HashAlgorithmTags.SHA1, iv, iterations);
+ totalTime = System.currentTimeMillis();
+ decryptorFactory.makeKeyFromPassPhrase(SymmetricKeyAlgorithmTags.AES_128, s2k);
+ totalTime = System.currentTimeMillis() -totalTime;
+
+ if ((iterations % 10) == 0) {
+ log.add(LogType.MSG_BENCH_S2K_FOR_IT, 1, Integer.toString(iterations), Long.toString(totalTime));
+ }
+
+ }
+ iterationsFor100ms = iterations;
+
+ } catch (PGPException e) {
+ Log.e(Constants.TAG, "internal error during benchmark", e);
+ log.add(LogType.MSG_INTERNAL_ERROR, 0);
+ return new BenchmarkResult(BenchmarkResult.RESULT_ERROR, log);
+ }
+
+ log.add(LogType.MSG_BENCH_S2K_100MS_ITS, 1, Integer.toString(iterationsFor100ms));
+ log.add(LogType.MSG_BENCH_ENC_TIME_AVG, 1, String.format("%.2f", encryptionTime/1000.0));
+ log.add(LogType.MSG_BENCH_DEC_TIME_AVG, 1, String.format("%.2f", decryptionTime/1000.0));
+
+ log.add(LogType.MSG_BENCH_SUCCESS, 0);
+ return new BenchmarkResult(BenchmarkResult.RESULT_OK, log);
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java
index e1daac874..7d11fa1f1 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java
@@ -208,7 +208,7 @@ public class CertifyOperation extends BaseOperation {
// these variables are used inside the following loop, but they need to be created only once
UploadOperation uploadOperation = null;
if (parcel.keyServerUri != null) {
- uploadOperation = new UploadOperation(mContext, mProviderHelper, mProgressable);
+ uploadOperation = new UploadOperation(mContext, mProviderHelper, mProgressable, mCancelled);
}
// Write all certified keys into the database
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java
index cf8928768..3b2c484be 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java
@@ -26,7 +26,6 @@ import android.support.annotation.NonNull;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.EditKeyResult;
-import org.sufficientlysecure.keychain.operations.results.InputPendingResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult;
@@ -73,7 +72,7 @@ public class EditKeyOperation extends BaseOperation {
* @return the result of the operation
*/
@NonNull
- public InputPendingResult execute(SaveKeyringParcel saveParcel, CryptoInputParcel cryptoInput) {
+ public EditKeyResult execute(SaveKeyringParcel saveParcel, CryptoInputParcel cryptoInput) {
OperationLog log = new OperationLog();
log.add(LogType.MSG_ED, 0);
@@ -100,7 +99,8 @@ public class EditKeyOperation extends BaseOperation {
modifyResult = keyOperations.modifySecretKeyRing(secRing, cryptoInput, saveParcel);
if (modifyResult.isPending()) {
- return modifyResult;
+ log.add(modifyResult, 1);
+ return new EditKeyResult(log, modifyResult);
}
} catch (NotFoundException e) {
@@ -148,19 +148,16 @@ public class EditKeyOperation extends BaseOperation {
new UploadKeyringParcel(saveParcel.getUploadKeyserver(), keyringBytes);
UploadResult uploadResult =
- new UploadOperation(mContext, mProviderHelper, mProgressable)
+ new UploadOperation(mContext, mProviderHelper, mProgressable, mCancelled)
.execute(exportKeyringParcel, cryptoInput);
+ log.add(uploadResult, 2);
+
if (uploadResult.isPending()) {
- return uploadResult;
+ return new EditKeyResult(log, uploadResult);
} else if (!uploadResult.success() && saveParcel.isUploadAtomic()) {
// if atomic, update fail implies edit operation should also fail and not save
- log.add(uploadResult, 2);
- return new EditKeyResult(log, RequiredInputParcel.createRetryUploadOperation(),
- cryptoInput);
- } else {
- // upload succeeded or not atomic so we continue
- log.add(uploadResult, 2);
+ return new EditKeyResult(log, RequiredInputParcel.createRetryUploadOperation(), cryptoInput);
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportOperation.java
index 89575338f..19a05790f 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportOperation.java
@@ -28,7 +28,7 @@ import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
-import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -82,6 +82,8 @@ import org.sufficientlysecure.keychain.util.orbot.OrbotHelper;
*/
public class ImportOperation extends BaseOperation {
+ public static final int MAX_THREADS = 10;
+
public ImportOperation(Context context, ProviderHelper providerHelper, Progressable
progressable) {
super(context, providerHelper, progressable);
@@ -133,7 +135,7 @@ public class ImportOperation extends BaseOperation {
@NonNull
private ImportKeyResult serialKeyRingImport(Iterator entries, int num,
String keyServerUri, Progressable progressable,
- Proxy proxy) {
+ @NonNull Proxy proxy) {
if (progressable != null) {
progressable.setProgress(R.string.progress_importing, 0, 100);
}
@@ -188,7 +190,7 @@ public class ImportOperation extends BaseOperation {
// Make sure we have the keyserver instance cached
if (keyServer == null) {
log.add(LogType.MSG_IMPORT_KEYSERVER, 1, keyServerUri);
- keyServer = new HkpKeyserver(keyServerUri);
+ keyServer = new HkpKeyserver(keyServerUri, proxy);
}
try {
@@ -197,11 +199,10 @@ public class ImportOperation extends BaseOperation {
if (entry.mExpectedFingerprint != null) {
log.add(LogType.MSG_IMPORT_FETCH_KEYSERVER, 2, "0x" +
entry.mExpectedFingerprint.substring(24));
- data = keyServer.get("0x" + entry.mExpectedFingerprint, proxy)
- .getBytes();
+ data = keyServer.get("0x" + entry.mExpectedFingerprint).getBytes();
} else {
log.add(LogType.MSG_IMPORT_FETCH_KEYSERVER, 2, entry.mKeyIdHex);
- data = keyServer.get(entry.mKeyIdHex, proxy).getBytes();
+ data = keyServer.get(entry.mKeyIdHex).getBytes();
}
key = UncachedKeyRing.decodeFromData(data);
if (key != null) {
@@ -219,12 +220,12 @@ public class ImportOperation extends BaseOperation {
if (entry.mKeybaseName != null) {
// Make sure we have this cached
if (keybaseServer == null) {
- keybaseServer = new KeybaseKeyserver();
+ keybaseServer = new KeybaseKeyserver(proxy);
}
try {
log.add(LogType.MSG_IMPORT_FETCH_KEYBASE, 2, entry.mKeybaseName);
- byte[] data = keybaseServer.get(entry.mKeybaseName, proxy).getBytes();
+ byte[] data = keybaseServer.get(entry.mKeybaseName).getBytes();
UncachedKeyRing keybaseKey = UncachedKeyRing.decodeFromData(data);
// If there already is a key, merge the two
@@ -261,12 +262,6 @@ public class ImportOperation extends BaseOperation {
continue;
}
- // Another check if we have been cancelled
- if (checkCancelled()) {
- cancelled = true;
- break;
- }
-
SaveKeyringResult result;
// synchronizing prevents https://github.com/open-keychain/open-keychain/issues/1221
// and https://github.com/open-keychain/open-keychain/issues/1480
@@ -365,13 +360,15 @@ public class ImportOperation extends BaseOperation {
}
}
- // Final log entry, it's easier to do this individually
- if ((newKeys > 0 || updatedKeys > 0) && badKeys > 0) {
- log.add(LogType.MSG_IMPORT_PARTIAL, 1);
- } else if (newKeys > 0 || updatedKeys > 0) {
- log.add(LogType.MSG_IMPORT_SUCCESS, 1);
- } else {
- log.add(LogType.MSG_IMPORT_ERROR, 1);
+ if (!cancelled) {
+ // Final log entry, it's easier to do this individually
+ if ((newKeys > 0 || updatedKeys > 0) && badKeys > 0) {
+ log.add(LogType.MSG_IMPORT_PARTIAL, 1);
+ } else if (newKeys > 0 || updatedKeys > 0) {
+ log.add(LogType.MSG_IMPORT_SUCCESS, 1);
+ } else {
+ log.add(LogType.MSG_IMPORT_ERROR, 1);
+ }
}
return new ImportKeyResult(resultType, log, newKeys, updatedKeys, badKeys, secret,
@@ -400,8 +397,7 @@ public class ImportOperation extends BaseOperation {
return new ImportKeyResult(null,
RequiredInputParcel.createOrbotRequiredOperation(), cryptoInput);
}
- proxy = Preferences.getPreferences(mContext).getProxyPrefs().parcelableProxy
- .getProxy();
+ proxy = Preferences.getPreferences(mContext).getProxyPrefs().getProxy();
} else {
proxy = cryptoInput.getParcelableProxy().getProxy();
}
@@ -414,62 +410,61 @@ public class ImportOperation extends BaseOperation {
}
@NonNull
- private ImportKeyResult multiThreadedKeyImport(Iterator keyListIterator,
+ private ImportKeyResult multiThreadedKeyImport(@NonNull Iterator keyListIterator,
int totKeys, final String keyServer,
final Proxy proxy) {
Log.d(Constants.TAG, "Multi-threaded key import starting");
- if (keyListIterator != null) {
- KeyImportAccumulator accumulator = new KeyImportAccumulator(totKeys, mProgressable);
+ KeyImportAccumulator accumulator = new KeyImportAccumulator(totKeys, mProgressable);
- final ProgressScaler ignoreProgressable = new ProgressScaler();
+ final ProgressScaler ignoreProgressable = new ProgressScaler();
- final int maxThreads = 200;
- ExecutorService importExecutor = new ThreadPoolExecutor(0, maxThreads,
- 30L, TimeUnit.SECONDS,
- new SynchronousQueue());
+ ExecutorService importExecutor = new ThreadPoolExecutor(0, MAX_THREADS, 30L, TimeUnit.SECONDS,
+ new LinkedBlockingQueue());
- ExecutorCompletionService importCompletionService =
- new ExecutorCompletionService<>(importExecutor);
+ ExecutorCompletionService importCompletionService =
+ new ExecutorCompletionService<>(importExecutor);
- while (keyListIterator.hasNext()) { // submit all key rings to be imported
+ while (keyListIterator.hasNext()) { // submit all key rings to be imported
- final ParcelableKeyRing pkRing = keyListIterator.next();
+ final ParcelableKeyRing pkRing = keyListIterator.next();
- Callable importOperationCallable = new Callable
- () {
+ Callable importOperationCallable = new Callable
+ () {
- @Override
- public ImportKeyResult call() {
+ @Override
+ public ImportKeyResult call() {
- ArrayList list = new ArrayList<>();
- list.add(pkRing);
-
- return serialKeyRingImport(list.iterator(), 1, keyServer,
- ignoreProgressable, proxy);
+ if (checkCancelled()) {
+ return null;
}
- };
- importCompletionService.submit(importOperationCallable);
- }
+ ArrayList list = new ArrayList<>();
+ list.add(pkRing);
- while (!accumulator.isImportFinished()) { // accumulate the results of each import
- try {
- accumulator.accumulateKeyImport(importCompletionService.take().get());
- } catch (InterruptedException | ExecutionException e) {
- Log.e(Constants.TAG, "A key could not be imported during multi-threaded " +
- "import", e);
- // do nothing?
- if (e instanceof ExecutionException) {
- // Since serialKeyRingImport does not throw any exceptions, this is what
- // would have happened if
- // we were importing the key on this thread
- throw new RuntimeException();
- }
+ return serialKeyRingImport(list.iterator(), 1, keyServer, ignoreProgressable, proxy);
+ }
+ };
+
+ importCompletionService.submit(importOperationCallable);
+ }
+
+ while (!accumulator.isImportFinished()) { // accumulate the results of each import
+ try {
+ accumulator.accumulateKeyImport(importCompletionService.take().get());
+ } catch (InterruptedException | ExecutionException e) {
+ Log.e(Constants.TAG, "A key could not be imported during multi-threaded " +
+ "import", e);
+ // do nothing?
+ if (e instanceof ExecutionException) {
+ // Since serialKeyRingImport does not throw any exceptions, this is what
+ // would have happened if
+ // we were importing the key on this thread
+ throw new RuntimeException();
}
}
- return accumulator.getConsolidatedResult();
}
- return new ImportKeyResult(ImportKeyResult.RESULT_FAIL_NOTHING, new OperationLog());
+ return accumulator.getConsolidatedResult();
+
}
/**
@@ -486,6 +481,7 @@ public class ImportOperation extends BaseOperation {
private int mUpdatedKeys = 0;
private int mSecret = 0;
private int mResultType = 0;
+ private boolean mHasCancelledResult;
/**
* Accumulates keyring imports and updates the progressable whenever a new key is imported.
@@ -503,14 +499,25 @@ public class ImportOperation extends BaseOperation {
}
}
- public synchronized void accumulateKeyImport(ImportKeyResult result) {
+ public void accumulateKeyImport(ImportKeyResult result) {
mImportedKeys++;
+ if (result == null) {
+ return;
+ }
+
if (mProgressable != null) {
mProgressable.setProgress(mImportedKeys, mTotalKeys);
}
- mImportLog.addAll(result.getLog().toList());//accumulates log
+ boolean notCancelledOrFirstCancelled = !result.cancelled() || !mHasCancelledResult;
+ if (notCancelledOrFirstCancelled) {
+ mImportLog.addAll(result.getLog().toList()); //accumulates log
+ if (result.cancelled()) {
+ mHasCancelledResult = true;
+ }
+ }
+
mBadKeys += result.mBadKeys;
mNewKeys += result.mNewKeys;
mUpdatedKeys += result.mUpdatedKeys;
@@ -533,7 +540,9 @@ public class ImportOperation extends BaseOperation {
// adding required information to mResultType
// special case,no keys requested for import
- if (mBadKeys == 0 && mNewKeys == 0 && mUpdatedKeys == 0) {
+ if (mBadKeys == 0 && mNewKeys == 0 && mUpdatedKeys == 0
+ && (mResultType & ImportKeyResult.RESULT_CANCELLED)
+ != ImportKeyResult.RESULT_CANCELLED) {
mResultType = ImportKeyResult.RESULT_FAIL_NOTHING;
} else {
if (mNewKeys > 0) {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java
index a09cf4f27..bb8d6ad73 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java
@@ -110,10 +110,9 @@ public class InputDataOperation extends BaseOperation {
if (decryptResult.isPending()) {
return new InputDataResult(log, decryptResult);
}
- log.addByMerge(decryptResult, 2);
+ log.addByMerge(decryptResult, 1);
- if (!decryptResult.success()) {
- log.add(LogType.MSG_DATA_ERROR_OPENPGP, 1);
+ if ( ! decryptResult.success()) {
return new InputDataResult(InputDataResult.RESULT_ERROR, log);
}
@@ -165,6 +164,7 @@ public class InputDataOperation extends BaseOperation {
parser.setContentDecoding(true);
parser.setRecurse();
parser.setContentHandler(new AbstractContentHandler() {
+ private boolean mFoundHeaderWithFields = false;
private Uri uncheckedSignedDataUri;
String mFilename;
@@ -220,12 +220,20 @@ public class InputDataOperation extends BaseOperation {
mFilename = null;
}
+ @Override
+ public void endHeader() throws MimeException {
+ if ( ! mFoundHeaderWithFields) {
+ parser.stop();
+ }
+ }
+
@Override
public void field(Field field) throws MimeException {
field = DefaultFieldParser.getParser().parse(field, DecodeMonitor.SILENT);
if (field instanceof ContentDispositionField) {
mFilename = ((ContentDispositionField) field).getFilename();
}
+ mFoundHeaderWithFields = true;
}
private void bodySignature(BodyDescriptor bd, InputStream is) throws MimeException, IOException {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/RevokeOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/RevokeOperation.java
index 975cf541a..3e787560a 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/RevokeOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/RevokeOperation.java
@@ -19,12 +19,13 @@
package org.sufficientlysecure.keychain.operations;
+
import android.content.Context;
import android.net.Uri;
import android.support.annotation.NonNull;
import org.sufficientlysecure.keychain.Constants;
-import org.sufficientlysecure.keychain.operations.results.InputPendingResult;
+import org.sufficientlysecure.keychain.operations.results.EditKeyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.operations.results.RevokeResult;
import org.sufficientlysecure.keychain.pgp.Progressable;
@@ -79,9 +80,8 @@ public class RevokeOperation extends BaseOperation {
saveKeyringParcel.mRevokeSubKeys.add(masterKeyId);
- InputPendingResult revokeAndUploadResult = new EditKeyOperation(mContext,
- mProviderHelper, mProgressable, mCancelled)
- .execute(saveKeyringParcel, cryptoInputParcel);
+ EditKeyResult revokeAndUploadResult = new EditKeyOperation(mContext,
+ mProviderHelper, mProgressable, mCancelled).execute(saveKeyringParcel, cryptoInputParcel);
if (revokeAndUploadResult.isPending()) {
return revokeAndUploadResult;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/UploadOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/UploadOperation.java
index bac9a7975..e5f11eaa6 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/UploadOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/UploadOperation.java
@@ -27,6 +27,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
import android.content.Context;
import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import org.spongycastle.bcpg.ArmoredOutputStream;
import org.sufficientlysecure.keychain.Constants;
@@ -47,26 +48,17 @@ import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.Log;
+import org.sufficientlysecure.keychain.util.ParcelableProxy;
import org.sufficientlysecure.keychain.util.Preferences;
+import org.sufficientlysecure.keychain.util.Preferences.ProxyPrefs;
import org.sufficientlysecure.keychain.util.orbot.OrbotHelper;
/**
- * An operation class which implements high level export operations.
- * This class receives a source and/or destination of keys as input and performs
- * all steps for this export.
- *
- * @see org.sufficientlysecure.keychain.ui.adapter.ImportKeysAdapter#getSelectedEntries()
- * For the export operation, the input consists of a set of key ids and
- * either the name of a file or an output uri to write to.
+ * An operation class which implements the upload of a single key to a key server.
*/
public class UploadOperation extends BaseOperation {
- public UploadOperation(Context context, ProviderHelper providerHelper, Progressable
- progressable) {
- super(context, providerHelper, progressable);
- }
-
public UploadOperation(Context context, ProviderHelper providerHelper,
Progressable progressable, AtomicBoolean cancelled) {
super(context, providerHelper, progressable, cancelled);
@@ -74,57 +66,99 @@ public class UploadOperation extends BaseOperation {
@NonNull
public UploadResult execute(UploadKeyringParcel uploadInput, CryptoInputParcel cryptoInput) {
+ OperationLog log = new OperationLog();
+
+ log.add(LogType.MSG_UPLOAD, 0);
+ updateProgress(R.string.progress_uploading, 0, 1);
+
Proxy proxy;
- if (cryptoInput.getParcelableProxy() == null) {
- // explicit proxy not set
- if (!OrbotHelper.isOrbotInRequiredState(mContext)) {
- return new UploadResult(null, RequiredInputParcel.createOrbotRequiredOperation(), cryptoInput);
+ {
+ boolean proxyIsTor = false;
+
+ // Proxy priorities:
+ // 1. explicit proxy
+ // 2. orbot proxy state
+ // 3. proxy from preferences
+ ParcelableProxy parcelableProxy = cryptoInput.getParcelableProxy();
+ if (parcelableProxy != null) {
+ proxy = parcelableProxy.getProxy();
+ } else {
+ if ( ! OrbotHelper.isOrbotInRequiredState(mContext)) {
+ return new UploadResult(log, RequiredInputParcel.createOrbotRequiredOperation(), cryptoInput);
+ }
+ ProxyPrefs proxyPrefs = Preferences.getPreferences(mContext).getProxyPrefs();
+ if (proxyPrefs.torEnabled) {
+ proxyIsTor = true;
+ }
+ proxy = proxyPrefs.getProxy();
}
- proxy = Preferences.getPreferences(mContext).getProxyPrefs().parcelableProxy.getProxy();
- } else {
- proxy = cryptoInput.getParcelableProxy().getProxy();
+
+ if (proxyIsTor) {
+ log.add(LogType.MSG_UPLOAD_PROXY_TOR, 1);
+ } else if (proxy == Proxy.NO_PROXY) {
+ log.add(LogType.MSG_UPLOAD_PROXY_DIRECT, 1);
+ } else {
+ log.add(LogType.MSG_UPLOAD_PROXY, 1, proxy.toString());
+ }
+
}
- HkpKeyserver hkpKeyserver = new HkpKeyserver(uploadInput.mKeyserver);
- try {
- CanonicalizedPublicKeyRing keyring;
- if (uploadInput.mMasterKeyId != null) {
- keyring = mProviderHelper.getCanonicalizedPublicKeyRing(
- uploadInput.mMasterKeyId);
- } else if (uploadInput.mUncachedKeyringBytes != null) {
- CanonicalizedKeyRing canonicalizedRing =
- UncachedKeyRing.decodeFromData(uploadInput.mUncachedKeyringBytes)
- .canonicalize(new OperationLog(), 0, true);
- if ( ! CanonicalizedPublicKeyRing.class.isInstance(canonicalizedRing)) {
- throw new AssertionError("keyring bytes must contain public key ring!");
- }
- keyring = (CanonicalizedPublicKeyRing) canonicalizedRing;
- } else {
- throw new AssertionError("key id or bytes must be non-null!");
- }
- return uploadKeyRingToServer(hkpKeyserver, keyring, proxy);
- } catch (ProviderHelper.NotFoundException e) {
- Log.e(Constants.TAG, "error uploading key", e);
- return new UploadResult(UploadResult.RESULT_ERROR, new OperationLog());
- } catch (IOException e) {
- e.printStackTrace();
- return new UploadResult(UploadResult.RESULT_ERROR, new OperationLog());
- } catch (PgpGeneralException e) {
- e.printStackTrace();
- return new UploadResult(UploadResult.RESULT_ERROR, new OperationLog());
+ HkpKeyserver hkpKeyserver;
+ {
+ hkpKeyserver = new HkpKeyserver(uploadInput.mKeyserver, proxy);
+ log.add(LogType.MSG_UPLOAD_SERVER, 1, hkpKeyserver.toString());
}
+
+ CanonicalizedPublicKeyRing keyring = getPublicKeyringFromInput(log, uploadInput);
+ if (keyring == null) {
+ return new UploadResult(UploadResult.RESULT_ERROR, log);
+ }
+
+ return uploadKeyRingToServer(log, hkpKeyserver, keyring);
}
- UploadResult uploadKeyRingToServer(HkpKeyserver server, CanonicalizedPublicKeyRing keyring, Proxy proxy) {
+ @Nullable
+ private CanonicalizedPublicKeyRing getPublicKeyringFromInput(OperationLog log, UploadKeyringParcel uploadInput) {
- mProgressable.setProgress(R.string.progress_uploading, 0, 1);
+ boolean hasMasterKeyId = uploadInput.mMasterKeyId != null;
+ boolean hasKeyringBytes = uploadInput.mUncachedKeyringBytes != null;
+ if (hasMasterKeyId == hasKeyringBytes) {
+ throw new IllegalArgumentException("either keyid xor bytes must be non-null for this method call!");
+ }
+
+ try {
+
+ if (hasMasterKeyId) {
+ log.add(LogType.MSG_UPLOAD_KEY, 0, KeyFormattingUtils.convertKeyIdToHex(uploadInput.mMasterKeyId));
+ return mProviderHelper.getCanonicalizedPublicKeyRing(uploadInput.mMasterKeyId);
+ }
+
+ CanonicalizedKeyRing canonicalizedRing =
+ UncachedKeyRing.decodeFromData(uploadInput.mUncachedKeyringBytes)
+ .canonicalize(new OperationLog(), 0, true);
+ if ( ! CanonicalizedPublicKeyRing.class.isInstance(canonicalizedRing)) {
+ throw new IllegalArgumentException("keyring bytes must contain public key ring!");
+ }
+ log.add(LogType.MSG_UPLOAD_KEY, 0, KeyFormattingUtils.convertKeyIdToHex(canonicalizedRing.getMasterKeyId()));
+ return (CanonicalizedPublicKeyRing) canonicalizedRing;
+
+ } catch (ProviderHelper.NotFoundException e) {
+ log.add(LogType.MSG_UPLOAD_ERROR_NOT_FOUND, 1);
+ return null;
+ } catch (IOException | PgpGeneralException e) {
+ log.add(LogType.MSG_UPLOAD_ERROR_IO, 1);
+ Log.e(Constants.TAG, "error uploading key", e);
+ return null;
+ }
+
+ }
+
+ @NonNull
+ private UploadResult uploadKeyRingToServer(
+ OperationLog log, HkpKeyserver server, CanonicalizedPublicKeyRing keyring) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ArmoredOutputStream aos = null;
- OperationLog log = new OperationLog();
- log.add(LogType.MSG_BACKUP_UPLOAD_PUBLIC, 0, KeyFormattingUtils.convertKeyIdToHex(
- keyring.getPublicKey().getKeyId()
- ));
try {
aos = new ArmoredOutputStream(bos);
@@ -132,22 +166,23 @@ public class UploadOperation extends BaseOperation {
aos.close();
String armoredKey = bos.toString("UTF-8");
- server.add(armoredKey, proxy);
+ server.add(armoredKey);
- log.add(LogType.MSG_BACKUP_UPLOAD_SUCCESS, 1);
+ updateProgress(R.string.progress_uploading, 1, 1);
+
+ log.add(LogType.MSG_UPLOAD_SUCCESS, 1);
return new UploadResult(UploadResult.RESULT_OK, log);
} catch (IOException e) {
Log.e(Constants.TAG, "IOException", e);
- log.add(LogType.MSG_BACKUP_ERROR_KEY, 1);
+ log.add(LogType.MSG_UPLOAD_ERROR_IO, 1);
return new UploadResult(UploadResult.RESULT_ERROR, log);
} catch (AddKeyException e) {
Log.e(Constants.TAG, "AddKeyException", e);
- log.add(LogType.MSG_BACKUP_ERROR_UPLOAD, 1);
+ log.add(LogType.MSG_UPLOAD_ERROR_UPLOAD, 1);
return new UploadResult(UploadResult.RESULT_ERROR, log);
} finally {
- mProgressable.setProgress(R.string.progress_uploading, 1, 1);
try {
if (aos != null) {
aos.close();
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/BenchmarkResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/BenchmarkResult.java
new file mode 100644
index 000000000..473ae9886
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/BenchmarkResult.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2014 Dominik Schürmann
+ * Copyright (C) 2014 Vincent Breitmoser
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package org.sufficientlysecure.keychain.operations.results;
+
+import android.os.Parcel;
+
+
+public class BenchmarkResult extends OperationResult {
+
+ public BenchmarkResult(int result, OperationLog log) {
+ super(result, log);
+ }
+
+ /** Construct from a parcel - trivial because we have no extra data. */
+ public BenchmarkResult(Parcel source) {
+ super(source);
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ }
+
+ public static Creator CREATOR = new Creator() {
+ public BenchmarkResult createFromParcel(final Parcel source) {
+ return new BenchmarkResult(source);
+ }
+
+ public BenchmarkResult[] newArray(final int size) {
+ return new BenchmarkResult[size];
+ }
+ };
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DecryptVerifyResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DecryptVerifyResult.java
index 95cf179af..f19ba5250 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DecryptVerifyResult.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DecryptVerifyResult.java
@@ -39,6 +39,8 @@ public class DecryptVerifyResult extends InputPendingResult {
byte[] mOutputBytes;
+ public long mOperationTime;
+
public DecryptVerifyResult(int result, OperationLog log) {
super(result, log);
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/EditKeyResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/EditKeyResult.java
index 6098d59d5..fa383a7b5 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/EditKeyResult.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/EditKeyResult.java
@@ -38,6 +38,11 @@ public class EditKeyResult extends InputPendingResult {
mMasterKeyId = null;
}
+ public EditKeyResult(OperationLog log, InputPendingResult result) {
+ super(log, result);
+ mMasterKeyId = null;
+ }
+
public EditKeyResult(Parcel source) {
super(source);
mMasterKeyId = source.readInt() != 0 ? source.readLong() : null;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputPendingResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputPendingResult.java
index 0a8c1f653..ed6674ef7 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputPendingResult.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputPendingResult.java
@@ -19,6 +19,7 @@
package org.sufficientlysecure.keychain.operations.results;
import android.os.Parcel;
+import android.support.annotation.NonNull;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
@@ -32,13 +33,13 @@ public class InputPendingResult extends OperationResult {
// in case operation needs to add to/changes the cryptoInputParcel sent to it
public final CryptoInputParcel mCryptoInputParcel;
- public InputPendingResult(int result, OperationLog log) {
+ public InputPendingResult(int result, @NonNull OperationLog log) {
super(result, log);
mRequiredInput = null;
mCryptoInputParcel = null;
}
- public InputPendingResult(OperationLog log, InputPendingResult result) {
+ public InputPendingResult(@NonNull OperationLog log, @NonNull InputPendingResult result) {
super(RESULT_PENDING, log);
if (!result.isPending()) {
throw new AssertionError("sub result must be pending!");
@@ -47,7 +48,7 @@ public class InputPendingResult extends OperationResult {
mCryptoInputParcel = result.mCryptoInputParcel;
}
- public InputPendingResult(OperationLog log, RequiredInputParcel requiredInput,
+ public InputPendingResult(@NonNull OperationLog log, RequiredInputParcel requiredInput,
CryptoInputParcel cryptoInputParcel) {
super(RESULT_PENDING, log);
mRequiredInput = requiredInput;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java
index 0b8c3e6c7..9877f2318 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java
@@ -635,6 +635,8 @@ public abstract class OperationResult implements Parcelable {
MSG_EK_ERROR_NOT_FOUND (LogLevel.ERROR, R.string.msg_ek_error_not_found),
// decryptverify
+ MSG_DC_ASKIP_BAD_FLAGS (LogLevel.DEBUG, R.string.msg_dc_askip_bad_flags),
+ MSG_DC_ASKIP_UNAVAILABLE (LogLevel.DEBUG, R.string.msg_dc_askip_unavailable),
MSG_DC_ASKIP_NO_KEY (LogLevel.DEBUG, R.string.msg_dc_askip_no_key),
MSG_DC_ASKIP_NOT_ALLOWED (LogLevel.DEBUG, R.string.msg_dc_askip_not_allowed),
MSG_DC_ASYM (LogLevel.DEBUG, R.string.msg_dc_asym),
@@ -766,17 +768,24 @@ public abstract class OperationResult implements Parcelable {
MSG_IMPORT_SUCCESS (LogLevel.OK, R.string.msg_import_success),
MSG_BACKUP(LogLevel.START, R.plurals.msg_backup),
- MSG_BACKUP_UPLOAD_PUBLIC(LogLevel.START, R.string.msg_backup_upload_public),
MSG_BACKUP_PUBLIC(LogLevel.DEBUG, R.string.msg_backup_public),
MSG_BACKUP_SECRET(LogLevel.DEBUG, R.string.msg_backup_secret),
MSG_BACKUP_ALL(LogLevel.START, R.string.msg_backup_all),
MSG_BACKUP_ERROR_URI_OPEN(LogLevel.ERROR, R.string.msg_backup_error_uri_open),
MSG_BACKUP_ERROR_DB(LogLevel.ERROR, R.string.msg_backup_error_db),
MSG_BACKUP_ERROR_IO(LogLevel.ERROR, R.string.msg_backup_error_io),
- MSG_BACKUP_ERROR_KEY(LogLevel.ERROR, R.string.msg_backup_error_key),
- MSG_BACKUP_ERROR_UPLOAD(LogLevel.ERROR, R.string.msg_backup_error_upload),
MSG_BACKUP_SUCCESS(LogLevel.OK, R.string.msg_backup_success),
- MSG_BACKUP_UPLOAD_SUCCESS(LogLevel.OK, R.string.msg_backup_upload_success),
+
+ MSG_UPLOAD(LogLevel.START, R.string.msg_upload),
+ MSG_UPLOAD_KEY(LogLevel.INFO, R.string.msg_upload_key),
+ MSG_UPLOAD_PROXY_DIRECT(LogLevel.DEBUG, R.string.msg_upload_proxy_direct),
+ MSG_UPLOAD_PROXY_TOR(LogLevel.DEBUG, R.string.msg_upload_proxy_tor),
+ MSG_UPLOAD_PROXY(LogLevel.DEBUG, R.string.msg_upload_proxy),
+ MSG_UPLOAD_SERVER(LogLevel.DEBUG, R.string.msg_upload_server),
+ MSG_UPLOAD_SUCCESS(LogLevel.OK, R.string.msg_upload_success),
+ MSG_UPLOAD_ERROR_NOT_FOUND(LogLevel.ERROR, R.string.msg_upload_error_not_found),
+ MSG_UPLOAD_ERROR_IO(LogLevel.ERROR, R.string.msg_upload_error_key),
+ MSG_UPLOAD_ERROR_UPLOAD(LogLevel.ERROR, R.string.msg_upload_error_upload),
MSG_CRT_UPLOAD_SUCCESS (LogLevel.OK, R.string.msg_crt_upload_success),
@@ -827,7 +836,6 @@ public abstract class OperationResult implements Parcelable {
MSG_DATA (LogLevel.START, R.string.msg_data),
MSG_DATA_OPENPGP (LogLevel.DEBUG, R.string.msg_data_openpgp),
MSG_DATA_ERROR_IO (LogLevel.ERROR, R.string.msg_data_error_io),
- MSG_DATA_ERROR_OPENPGP (LogLevel.ERROR, R.string.msg_data_error_openpgp),
MSG_DATA_DETACHED (LogLevel.INFO, R.string.msg_data_detached),
MSG_DATA_DETACHED_CLEAR (LogLevel.WARN, R.string.msg_data_detached_clear),
MSG_DATA_DETACHED_SIG (LogLevel.DEBUG, R.string.msg_data_detached_sig),
@@ -867,6 +875,16 @@ public abstract class OperationResult implements Parcelable {
MSG_LV_FETCH_ERROR_IO (LogLevel.ERROR, R.string.msg_lv_fetch_error_io),
MSG_LV_FETCH_ERROR_FORMAT(LogLevel.ERROR, R.string.msg_lv_fetch_error_format),
MSG_LV_FETCH_ERROR_NOTHING (LogLevel.ERROR, R.string.msg_lv_fetch_error_nothing),
+
+ MSG_BENCH (LogLevel.START, R.string.msg_bench),
+ MSG_BENCH_ENC_TIME (LogLevel.DEBUG, R.string.msg_bench_enc_time),
+ MSG_BENCH_ENC_TIME_AVG (LogLevel.INFO, R.string.msg_bench_enc_time_avg),
+ MSG_BENCH_DEC_TIME (LogLevel.DEBUG, R.string.msg_bench_dec_time),
+ MSG_BENCH_DEC_TIME_AVG (LogLevel.INFO, R.string.msg_bench_enc_time_avg),
+ MSG_BENCH_S2K_FOR_IT (LogLevel.DEBUG, R.string.msg_bench_s2k_for_it),
+ MSG_BENCH_S2K_100MS_ITS (LogLevel.INFO, R.string.msg_bench_s2k_100ms_its),
+ MSG_BENCH_SUCCESS (LogLevel.OK, R.string.msg_bench_success),
+
;
public final int mMsgId;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PgpSignEncryptResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PgpSignEncryptResult.java
index 2b33b8ace..12b091e32 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PgpSignEncryptResult.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PgpSignEncryptResult.java
@@ -26,6 +26,7 @@ import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
public class PgpSignEncryptResult extends InputPendingResult {
byte[] mDetachedSignature;
+ public long mOperationTime;
public void setDetachedSignature(byte[] detachedSignature) {
mDetachedSignature = detachedSignature;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/SignEncryptResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/SignEncryptResult.java
index 0e0c5d598..60f47be3c 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/SignEncryptResult.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/SignEncryptResult.java
@@ -56,6 +56,10 @@ public class SignEncryptResult extends InputPendingResult {
return mResultBytes;
}
+ public ArrayList getResults() {
+ return mResults;
+ }
+
public int describeContents() {
return 0;
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/UploadResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/UploadResult.java
index a88072de3..ea2b373a9 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/UploadResult.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/UploadResult.java
@@ -18,6 +18,7 @@
package org.sufficientlysecure.keychain.operations.results;
import android.os.Parcel;
+import android.support.annotation.NonNull;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
@@ -38,7 +39,7 @@ public class UploadResult extends InputPendingResult {
}
- public UploadResult(OperationLog log, RequiredInputParcel requiredInputParcel,
+ public UploadResult(@NonNull OperationLog log, RequiredInputParcel requiredInputParcel,
CryptoInputParcel cryptoInputParcel) {
super(log, requiredInputParcel, cryptoInputParcel);
// we won't use these values
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java
index d85652a51..ea7465209 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java
@@ -48,7 +48,7 @@ import org.spongycastle.openpgp.PGPPBEEncryptedData;
import org.spongycastle.openpgp.PGPPublicKeyEncryptedData;
import org.spongycastle.openpgp.PGPSignatureList;
import org.spongycastle.openpgp.PGPUtil;
-import org.spongycastle.openpgp.jcajce.JcaPGPObjectFactory;
+import org.spongycastle.openpgp.jcajce.JcaSkipMarkerPGPObjectFactory;
import org.spongycastle.openpgp.operator.PBEDataDecryptorFactory;
import org.spongycastle.openpgp.operator.PGPDigestCalculatorProvider;
import org.spongycastle.openpgp.operator.jcajce.CachingDataDecryptorFactory;
@@ -87,6 +87,8 @@ public class PgpDecryptVerifyOperation extends BaseOperation 0) {
// Log.d(Constants.TAG, "read bytes: " + length);
@@ -456,6 +462,20 @@ public class PgpDecryptVerifyOperation extends BaseOperation
+ * Copyright (C) 2015 Vincent Breitmoser
+ * Copyright (C) 2015 Adithya Abraham Philip
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package org.sufficientlysecure.keychain.service;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+
+public class BenchmarkInputParcel implements Parcelable {
+
+ public BenchmarkInputParcel() {
+ }
+
+ protected BenchmarkInputParcel(Parcel in) {
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ }
+
+ public static final Creator CREATOR = new Creator() {
+ @Override
+ public BenchmarkInputParcel createFromParcel(Parcel in) {
+ return new BenchmarkInputParcel(in);
+ }
+
+ @Override
+ public BenchmarkInputParcel[] newArray(int size) {
+ return new BenchmarkInputParcel[size];
+ }
+ };
+}
\ No newline at end of file
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java
index ee953b060..cf51e3b55 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java
@@ -29,6 +29,7 @@ import android.os.RemoteException;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.operations.BaseOperation;
+import org.sufficientlysecure.keychain.operations.BenchmarkOperation;
import org.sufficientlysecure.keychain.operations.CertifyOperation;
import org.sufficientlysecure.keychain.operations.ConsolidateOperation;
import org.sufficientlysecure.keychain.operations.DeleteOperation;
@@ -135,6 +136,8 @@ public class KeychainService extends Service implements Progressable {
op = new KeybaseVerificationOperation(outerThis, new ProviderHelper(outerThis), outerThis);
} else if (inputParcel instanceof InputDataParcel) {
op = new InputDataOperation(outerThis, new ProviderHelper(outerThis), outerThis);
+ } else if (inputParcel instanceof BenchmarkInputParcel) {
+ op = new BenchmarkOperation(outerThis, new ProviderHelper(outerThis), outerThis);
} else {
throw new AssertionError("Unrecognized input parcel in KeychainService!");
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeyserverSyncAdapterService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeyserverSyncAdapterService.java
index 8aebae7aa..122eb6cf4 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeyserverSyncAdapterService.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeyserverSyncAdapterService.java
@@ -59,11 +59,12 @@ public class KeyserverSyncAdapterService extends Service {
// time since last update after which a key should be updated again, in s
public static final long KEY_UPDATE_LIMIT =
Constants.DEBUG_KEYSERVER_SYNC ? 1 : TimeUnit.DAYS.toSeconds(7);
- // time by which a sync is postponed in case of a
+ // time by which a sync is postponed in case screen is on
public static final long SYNC_POSTPONE_TIME =
Constants.DEBUG_KEYSERVER_SYNC ? 30 * 1000 : TimeUnit.MINUTES.toMillis(5);
// Time taken by Orbot before a new circuit is created
- public static final int ORBOT_CIRCUIT_TIMEOUT = (int) TimeUnit.MINUTES.toMillis(10);
+ public static final int ORBOT_CIRCUIT_TIMEOUT_SECONDS =
+ Constants.DEBUG_KEYSERVER_SYNC ? 2 : (int) TimeUnit.MINUTES.toSeconds(10);
private static final String ACTION_IGNORE_TOR = "ignore_tor";
@@ -77,10 +78,14 @@ public class KeyserverSyncAdapterService extends Service {
@Override
public int onStartCommand(final Intent intent, int flags, final int startId) {
+ if (intent == null || intent.getAction() == null) {
+ // introduced due to https://github.com/open-keychain/open-keychain/issues/1573
+ return START_NOT_STICKY; // we can't act on this Intent and don't want it redelivered
+ }
switch (intent.getAction()) {
case ACTION_CANCEL: {
mCancelled.set(true);
- break;
+ return START_NOT_STICKY;
}
// the reason for the separation betweyeen SYNC_NOW and UPDATE_ALL is so that starting
// the sync directly from the notification is possible while the screen is on with
@@ -92,44 +97,47 @@ public class KeyserverSyncAdapterService extends Service {
Constants.PROVIDER_AUTHORITY,
new Bundle()
);
- break;
+ return START_NOT_STICKY;
}
case ACTION_UPDATE_ALL: {
// does not check for screen on/off
- asyncKeyUpdate(this, new CryptoInputParcel());
- break;
+ asyncKeyUpdate(this, new CryptoInputParcel(), startId);
+ // we depend on handleUpdateResult to call stopSelf when it is no longer necessary
+ // for the intent to be redelivered
+ return START_REDELIVER_INTENT;
}
case ACTION_IGNORE_TOR: {
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
manager.cancel(Constants.Notification.KEYSERVER_SYNC_FAIL_ORBOT);
- asyncKeyUpdate(this, new CryptoInputParcel(ParcelableProxy.getForNoProxy()));
- break;
+ asyncKeyUpdate(this, new CryptoInputParcel(ParcelableProxy.getForNoProxy()),
+ startId);
+ // we depend on handleUpdateResult to call stopSelf when it is no longer necessary
+ // for the intent to be redelivered
+ return START_REDELIVER_INTENT;
}
case ACTION_START_ORBOT: {
- NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+ NotificationManager manager = (NotificationManager)
+ getSystemService(NOTIFICATION_SERVICE);
manager.cancel(Constants.Notification.KEYSERVER_SYNC_FAIL_ORBOT);
+
Intent startOrbot = new Intent(this, OrbotRequiredDialogActivity.class);
startOrbot.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startOrbot.putExtra(OrbotRequiredDialogActivity.EXTRA_START_ORBOT, true);
+
Messenger messenger = new Messenger(
new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case OrbotRequiredDialogActivity.MESSAGE_ORBOT_STARTED: {
- asyncKeyUpdate(KeyserverSyncAdapterService.this,
- new CryptoInputParcel());
- break;
- }
- case OrbotRequiredDialogActivity.MESSAGE_ORBOT_IGNORE: {
- asyncKeyUpdate(KeyserverSyncAdapterService.this,
- new CryptoInputParcel(
- ParcelableProxy.getForNoProxy()));
+ startServiceWithUpdateAll();
break;
}
+ case OrbotRequiredDialogActivity.MESSAGE_ORBOT_IGNORE:
case OrbotRequiredDialogActivity.MESSAGE_DIALOG_CANCEL: {
- // just stop service
- stopSelf();
+ // not possible since we proceed to Orbot's Activity
+ // directly, by starting OrbotRequiredDialogActivity with
+ // EXTRA_START_ORBOT set to true
break;
}
}
@@ -138,13 +146,17 @@ public class KeyserverSyncAdapterService extends Service {
);
startOrbot.putExtra(OrbotRequiredDialogActivity.EXTRA_MESSENGER, messenger);
startActivity(startOrbot);
- break;
+ // since we return START_NOT_STICKY, we also postpone the sync as a backup in case
+ // the service is killed before OrbotRequiredDialogActivity can get back to us
+ postponeSync();
+ // if use START_REDELIVER_INTENT, we might annoy the user by repeatedly starting the
+ // Orbot Activity when our service is killed and restarted
+ return START_NOT_STICKY;
}
case ACTION_DISMISS_NOTIFICATION: {
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
manager.cancel(Constants.Notification.KEYSERVER_SYNC_FAIL_ORBOT);
- stopSelf(startId);
- break;
+ return START_NOT_STICKY;
}
}
return START_NOT_STICKY;
@@ -167,10 +179,7 @@ public class KeyserverSyncAdapterService extends Service {
boolean isScreenOn = pm.isScreenOn();
if (!isScreenOn) {
- Intent serviceIntent = new Intent(KeyserverSyncAdapterService.this,
- KeyserverSyncAdapterService.class);
- serviceIntent.setAction(ACTION_UPDATE_ALL);
- startService(serviceIntent);
+ startServiceWithUpdateAll();
} else {
postponeSync();
}
@@ -188,16 +197,24 @@ public class KeyserverSyncAdapterService extends Service {
return new KeyserverSyncAdapter().getSyncAdapterBinder();
}
- private void handleUpdateResult(ImportKeyResult result) {
+ /**
+ * Since we're returning START_REDELIVER_INTENT in onStartCommand, we need to remember to call
+ * stopSelf(int) to prevent the Intent from being redelivered if our work is already done
+ *
+ * @param result result of keyserver sync
+ * @param startId startId provided to the onStartCommand call which resulted in this sync
+ */
+ private void handleUpdateResult(ImportKeyResult result, final int startId) {
if (result.isPending()) {
+ Log.d(Constants.TAG, "Orbot required for sync but not running, attempting to start");
// result is pending due to Orbot not being started
// try to start it silently, if disabled show notifications
new OrbotHelper.SilentStartManager() {
@Override
protected void onOrbotStarted() {
// retry the update
- asyncKeyUpdate(KeyserverSyncAdapterService.this,
- new CryptoInputParcel());
+ startServiceWithUpdateAll();
+ stopSelf(startId); // startServiceWithUpdateAll will deliver a new Intent
}
@Override
@@ -207,16 +224,24 @@ public class KeyserverSyncAdapterService extends Service {
(NotificationManager) getSystemService(NOTIFICATION_SERVICE);
manager.notify(Constants.Notification.KEYSERVER_SYNC_FAIL_ORBOT,
getOrbotNoification(KeyserverSyncAdapterService.this));
+ // further action on user interaction with notification, intent should not be
+ // redelivered, therefore:
+ stopSelf(startId);
}
}.startOrbotAndListen(this, false);
+ // if we're killed before we get a response from Orbot, we need the intent to be
+ // redelivered, so no stopSelf(int) here
} else if (isUpdateCancelled()) {
Log.d(Constants.TAG, "Keyserver sync cancelled, postponing by" + SYNC_POSTPONE_TIME
+ "ms");
postponeSync();
+ // postponeSync creates a new intent, so we don't need this to be redelivered
+ stopSelf(startId);
} else {
Log.d(Constants.TAG, "Keyserver sync completed: Updated: " + result.mUpdatedKeys
+ " Failed: " + result.mBadKeys);
- stopSelf();
+ // key sync completed successfully, we can stop
+ stopSelf(startId);
}
}
@@ -234,12 +259,12 @@ public class KeyserverSyncAdapterService extends Service {
}
private void asyncKeyUpdate(final Context context,
- final CryptoInputParcel cryptoInputParcel) {
+ final CryptoInputParcel cryptoInputParcel, final int startId) {
new Thread(new Runnable() {
@Override
public void run() {
ImportKeyResult result = updateKeysFromKeyserver(context, cryptoInputParcel);
- handleUpdateResult(result);
+ handleUpdateResult(result, startId);
}
}).start();
}
@@ -278,7 +303,6 @@ public class KeyserverSyncAdapterService extends Service {
);
}
-
/**
* will perform a staggered update of user's keys using delays to ensure new Tor circuits, as
* performed by parcimonie. Relevant issue and method at:
@@ -290,17 +314,31 @@ public class KeyserverSyncAdapterService extends Service {
CryptoInputParcel cryptoInputParcel) {
Log.d(Constants.TAG, "Starting staggered update");
// final int WEEK_IN_SECONDS = (int) TimeUnit.DAYS.toSeconds(7);
+ // we are limiting our randomness to ORBOT_CIRCUIT_TIMEOUT_SECONDS for now
final int WEEK_IN_SECONDS = 0;
+
ImportOperation.KeyImportAccumulator accumulator
= new ImportOperation.KeyImportAccumulator(keyList.size(), null);
+
+ // so that the first key can be updated without waiting. This is so that there isn't a
+ // large gap between a "Start Orbot" notification and the next key update
+ boolean first = true;
+
for (ParcelableKeyRing keyRing : keyList) {
int waitTime;
int staggeredTime = new Random().nextInt(1 + 2 * (WEEK_IN_SECONDS / keyList.size()));
- if (staggeredTime >= ORBOT_CIRCUIT_TIMEOUT) {
+ if (staggeredTime >= ORBOT_CIRCUIT_TIMEOUT_SECONDS) {
waitTime = staggeredTime;
} else {
- waitTime = ORBOT_CIRCUIT_TIMEOUT + new Random().nextInt(ORBOT_CIRCUIT_TIMEOUT);
+ waitTime = ORBOT_CIRCUIT_TIMEOUT_SECONDS
+ + new Random().nextInt(1 + ORBOT_CIRCUIT_TIMEOUT_SECONDS);
}
+
+ if (first) {
+ waitTime = 0;
+ first = false;
+ }
+
Log.d(Constants.TAG, "Updating key with fingerprint " + keyRing.mExpectedFingerprint +
" with a wait time of " + waitTime + "s");
try {
@@ -362,13 +400,15 @@ public class KeyserverSyncAdapterService extends Service {
);
ArrayList ignoreMasterKeyIds = new ArrayList<>();
- while (updatedKeysCursor.moveToNext()) {
+ while (updatedKeysCursor != null && updatedKeysCursor.moveToNext()) {
long masterKeyId = updatedKeysCursor.getLong(INDEX_UPDATED_KEYS_MASTER_KEY_ID);
Log.d(Constants.TAG, "Keyserver sync: Ignoring {" + masterKeyId + "} last updated at {"
+ updatedKeysCursor.getLong(INDEX_LAST_UPDATED) + "}s");
ignoreMasterKeyIds.add(masterKeyId);
}
- updatedKeysCursor.close();
+ if (updatedKeysCursor != null) {
+ updatedKeysCursor.close();
+ }
// 2. Make a list of public keys which should be updated
final int INDEX_MASTER_KEY_ID = 0;
@@ -413,7 +453,7 @@ public class KeyserverSyncAdapterService extends Service {
/**
* will cancel an update already in progress. We send an Intent to cancel it instead of simply
- * modifying a static variable sync the service is running in a process that is different from
+ * modifying a static variable since the service is running in a process that is different from
* the default application process where the UI code runs.
*
* @param context used to send an Intent to the service requesting cancellation.
@@ -491,6 +531,12 @@ public class KeyserverSyncAdapterService extends Service {
}
}
+ private void startServiceWithUpdateAll() {
+ Intent serviceIntent = new Intent(this, KeyserverSyncAdapterService.class);
+ serviceIntent.setAction(ACTION_UPDATE_ALL);
+ this.startService(serviceIntent);
+ }
+
// from de.azapps.mirakel.helper.Helpers from https://github.com/MirakelX/mirakel-android
private Bitmap getBitmap(int resId, Context context) {
int mLargeIconWidth = (int) context.getResources().getDimension(
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java
index 0d8569fe6..849418905 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java
@@ -23,6 +23,7 @@ import android.os.Parcelable;
import org.sufficientlysecure.keychain.util.ParcelableProxy;
import org.sufficientlysecure.keychain.util.Passphrase;
+import java.net.Proxy;
import java.nio.ByteBuffer;
import java.util.Date;
import java.util.HashMap;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java
index a2aff2029..cf7a0b1d7 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java
@@ -200,7 +200,7 @@ public class DecryptActivity extends BaseActivity {
}
// clean up ascii armored message, fixing newlines and stuff
- String cleanedText = PgpHelper.getPgpContent(text);
+ String cleanedText = PgpHelper.getPgpMessageContent(text);
if (cleanedText == null) {
return null;
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java
index 1d2bf6b9c..c45a641e0 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java
@@ -24,12 +24,14 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
+import android.Manifest;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.ClipDescription;
import android.content.Context;
import android.content.Intent;
import android.content.pm.LabeledIntent;
+import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.Bitmap;
import android.graphics.Point;
@@ -37,9 +39,12 @@ import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
+import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.os.Parcelable;
+import android.support.annotation.NonNull;
+import android.support.v4.content.ContextCompat;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
@@ -91,6 +96,22 @@ import org.sufficientlysecure.keychain.util.ParcelableHashMap;
import org.sufficientlysecure.keychain.util.Preferences;
+/** Displays a list of decrypted inputs.
+ *
+ * This class has a complex control flow to manage its input URIs. Each URI
+ * which is in mInputUris is also in exactly one of mPendingInputUris,
+ * mCancelledInputUris, mCurrentInputUri, or a key in mInputDataResults.
+ *
+ * Processing of URIs happens using a looping approach:
+ * - There is always exactly one method running which works on mCurrentInputUri
+ * - Processing starts in cryptoOperation(), which pops a new mCurrentInputUri
+ * from the list of mPendingInputUris.
+ * - Once a mCurrentInputUri is finished processing, it should be set to null and
+ * control handed back to cryptoOperation()
+ * - Control flow can move through asynchronous calls, and resume in callbacks
+ * like onActivityResult() or onPermissionRequestResult().
+ *
+ */
public class DecryptListFragment
extends QueueingCryptoOperationFragment
implements OnMenuItemClickListener {
@@ -102,7 +123,7 @@ public class DecryptListFragment
public static final String ARG_CAN_DELETE = "can_delete";
private static final int REQUEST_CODE_OUTPUT = 0x00007007;
- public static final String ARG_CURRENT_URI = "current_uri";
+ private static final int REQUEST_PERMISSION_READ_EXTERNAL_STORAGE = 12;
private ArrayList mInputUris;
private HashMap mInputDataResults;
@@ -118,7 +139,7 @@ public class DecryptListFragment
/**
* Creates new instance of this fragment
*/
- public static DecryptListFragment newInstance(ArrayList uris, boolean canDelete) {
+ public static DecryptListFragment newInstance(@NonNull ArrayList uris, boolean canDelete) {
DecryptListFragment frag = new DecryptListFragment();
Bundle args = new Bundle();
@@ -175,9 +196,12 @@ public class DecryptListFragment
outState.putParcelable(ARG_RESULTS, new ParcelableHashMap<>(results));
outState.putParcelable(ARG_OUTPUT_URIS, new ParcelableHashMap<>(mInputDataResults));
outState.putParcelableArrayList(ARG_CANCELLED_URIS, mCancelledInputUris);
- outState.putParcelable(ARG_CURRENT_URI, mCurrentInputUri);
outState.putBoolean(ARG_CAN_DELETE, mCanDelete);
+ // this does not save mCurrentInputUri - if anything is being
+ // processed at fragment recreation time, the operation in
+ // progress will be lost!
+
}
@Override
@@ -189,20 +213,21 @@ public class DecryptListFragment
ArrayList inputUris = getArguments().getParcelableArrayList(ARG_INPUT_URIS);
ArrayList cancelledUris = args.getParcelableArrayList(ARG_CANCELLED_URIS);
ParcelableHashMap results = args.getParcelable(ARG_RESULTS);
- Uri currentInputUri = args.getParcelable(ARG_CURRENT_URI);
mCanDelete = args.getBoolean(ARG_CAN_DELETE, false);
- displayInputUris(inputUris, currentInputUri, cancelledUris,
+ displayInputUris(inputUris, cancelledUris,
results != null ? results.getMap() : null
);
}
- private void displayInputUris(ArrayList inputUris, Uri currentInputUri,
- ArrayList cancelledUris, HashMap results) {
+ private void displayInputUris(
+ ArrayList inputUris,
+ ArrayList cancelledUris,
+ HashMap results) {
mInputUris = inputUris;
- mCurrentInputUri = currentInputUri;
+ mCurrentInputUri = null;
mInputDataResults = results != null ? results : new HashMap(inputUris.size());
mCancelledInputUris = cancelledUris != null ? cancelledUris : new ArrayList();
@@ -211,30 +236,23 @@ public class DecryptListFragment
for (final Uri uri : inputUris) {
mAdapter.add(uri);
- if (uri.equals(mCurrentInputUri)) {
+ boolean uriIsCancelled = mCancelledInputUris.contains(uri);
+ if (uriIsCancelled) {
+ mAdapter.setCancelled(uri, true);
continue;
}
- if (mCancelledInputUris.contains(uri)) {
- mAdapter.setCancelled(uri, new OnClickListener() {
- @Override
- public void onClick(View v) {
- retryUri(uri);
- }
- });
- continue;
- }
-
- if (results != null && results.containsKey(uri)) {
+ boolean uriHasResult = results != null && results.containsKey(uri);
+ if (uriHasResult) {
processResult(uri);
- } else {
- mPendingInputUris.add(uri);
+ continue;
}
+
+ mPendingInputUris.add(uri);
}
- if (mCurrentInputUri == null) {
- cryptoOperation();
- }
+ // check if there are any pending input uris
+ cryptoOperation();
}
@Override
@@ -364,12 +382,7 @@ public class DecryptListFragment
mCurrentInputUri = null;
mCancelledInputUris.add(uri);
- mAdapter.setCancelled(uri, new OnClickListener() {
- @Override
- public void onClick(View v) {
- retryUri(uri);
- }
- });
+ mAdapter.setCancelled(uri, true);
cryptoOperation();
@@ -463,8 +476,8 @@ public class DecryptListFragment
mPendingInputUris.add(uri);
mAdapter.resetItemData(uri);
+ // check if there are any pending input uris
cryptoOperation();
-
}
public void displayBottomSheet(final InputDataResult result, final int index) {
@@ -585,6 +598,11 @@ public class DecryptListFragment
@Override
public InputDataParcel createOperationInput() {
+ Activity activity = getActivity();
+ if (activity == null) {
+ return null;
+ }
+
if (mCurrentInputUri == null) {
if (mPendingInputUris.isEmpty()) {
// nothing left to do
@@ -594,7 +612,11 @@ public class DecryptListFragment
mCurrentInputUri = mPendingInputUris.remove(0);
}
- Log.d(Constants.TAG, "mInputUri=" + mCurrentInputUri);
+ Log.d(Constants.TAG, "mCurrentInputUri=" + mCurrentInputUri);
+
+ if ( ! checkAndRequestReadPermission(activity, mCurrentInputUri)) {
+ return null;
+ }
PgpDecryptVerifyInputParcel decryptInput = new PgpDecryptVerifyInputParcel()
.setAllowSymmetricDecryption(true);
@@ -602,6 +624,87 @@ public class DecryptListFragment
}
+ /**
+ * Request READ_EXTERNAL_STORAGE permission on Android >= 6.0 to read content from "file" Uris.
+ *
+ * This method returns true on Android < 6, or if permission is already granted. It
+ * requests the permission and returns false otherwise, taking over responsibility
+ * for mCurrentInputUri.
+ *
+ * see https://commonsware.com/blog/2015/10/07/runtime-permissions-files-action-send.html
+ */
+ private boolean checkAndRequestReadPermission(Activity activity, final Uri uri) {
+ if ( ! "file".equals(uri.getScheme())) {
+ return true;
+ }
+
+ if (Build.VERSION.SDK_INT < VERSION_CODES.M) {
+ return true;
+ }
+
+ // Additional check due to https://commonsware.com/blog/2015/11/09/you-cannot-hold-nonexistent-permissions.html
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
+ return true;
+ }
+
+ if (ContextCompat.checkSelfPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE)
+ == PackageManager.PERMISSION_GRANTED) {
+ return true;
+ }
+
+ requestPermissions(
+ new String[] { Manifest.permission.READ_EXTERNAL_STORAGE },
+ REQUEST_PERMISSION_READ_EXTERNAL_STORAGE);
+
+ return false;
+
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode,
+ @NonNull String[] permissions,
+ @NonNull int[] grantResults) {
+
+ if (requestCode != REQUEST_PERMISSION_READ_EXTERNAL_STORAGE) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ return;
+ }
+
+ boolean permissionWasGranted = grantResults.length > 0
+ && grantResults[0] == PackageManager.PERMISSION_GRANTED;
+
+ if (permissionWasGranted) {
+
+ // permission granted -> retry all cancelled file uris
+ for (Uri uri : mCancelledInputUris) {
+ if ( ! "file".equals(uri.getScheme())) {
+ continue;
+ }
+ mCancelledInputUris.remove(uri);
+ mPendingInputUris.add(uri);
+ mAdapter.setCancelled(uri, false);
+ }
+
+ } else {
+
+ // permission denied -> cancel current, and all pending file uris
+ mCurrentInputUri = null;
+ for (final Uri uri : mPendingInputUris) {
+ if ( ! "file".equals(uri.getScheme())) {
+ continue;
+ }
+ mPendingInputUris.remove(uri);
+ mCancelledInputUris.add(uri);
+ mAdapter.setCancelled(uri, true);
+ }
+
+ }
+
+ // hand control flow back
+ cryptoOperation();
+
+ }
+
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
if (mAdapter.mMenuClickedModel == null || !mAdapter.mMenuClickedModel.hasResult()) {
@@ -780,8 +883,10 @@ public class DecryptListFragment
return false;
}
ViewModel viewModel = (ViewModel) o;
- return !(mInputUri != null ? !mInputUri.equals(viewModel.mInputUri)
- : viewModel.mInputUri != null);
+ if (mInputUri == null) {
+ return viewModel.mInputUri == null;
+ }
+ return mInputUri.equals(viewModel.mInputUri);
}
// Depends on inputUri only
@@ -1017,10 +1122,19 @@ public class DecryptListFragment
notifyItemChanged(pos);
}
- public void setCancelled(Uri uri, OnClickListener retryListener) {
+ public void setCancelled(final Uri uri, boolean isCancelled) {
ViewModel newModel = new ViewModel(uri);
int pos = mDataset.indexOf(newModel);
- mDataset.get(pos).setCancelled(retryListener);
+ if (isCancelled) {
+ mDataset.get(pos).setCancelled(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ retryUri(uri);
+ }
+ });
+ } else {
+ mDataset.get(pos).setCancelled(null);
+ }
notifyItemChanged(pos);
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java
index 201465b52..50dbc9a8b 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java
@@ -28,6 +28,7 @@ import android.os.Bundle;
import android.support.v4.app.FragmentTransaction;
import android.widget.Toast;
+import org.apache.james.mime4j.util.MimeUtil;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.intents.OpenKeychainIntents;
@@ -59,12 +60,15 @@ public class EncryptTextActivity extends EncryptActivity {
extras = new Bundle();
}
+ String textData = extras.getString(EXTRA_TEXT);
+ boolean returnProcessText = false;
+
// When sending to OpenKeychain Encrypt via share menu
if (Intent.ACTION_SEND.equals(action) && type != null) {
Log.logDebugBundle(extras, "extras");
// When sending to OpenKeychain Encrypt via share menu
- if ("text/plain".equals(type)) {
+ if ( ! MimeUtil.isSameMimeType("text/plain", type)) {
Toast.makeText(this, R.string.toast_wrong_mimetype, Toast.LENGTH_LONG).show();
finish();
return;
@@ -94,12 +98,33 @@ public class EncryptTextActivity extends EncryptActivity {
}
// handle like normal text encryption, override action and extras to later
// executeServiceMethod ACTION_ENCRYPT_TEXT in main actions
- extras.putString(EXTRA_TEXT, sharedText);
+ textData = sharedText;
}
}
- String textData = extras.getString(EXTRA_TEXT);
+ // Android 6, PROCESS_TEXT Intent
+ if (Intent.ACTION_PROCESS_TEXT.equals(action) && type != null) {
+
+ String sharedText = null;
+ if (extras.containsKey(Intent.EXTRA_PROCESS_TEXT)) {
+ sharedText = extras.getString(Intent.EXTRA_PROCESS_TEXT);
+ returnProcessText = true;
+ } else if (extras.containsKey(Intent.EXTRA_PROCESS_TEXT_READONLY)) {
+ sharedText = extras.getString(Intent.EXTRA_PROCESS_TEXT_READONLY);
+ }
+
+ if (sharedText != null) {
+ if (sharedText.length() > Constants.TEXT_LENGTH_LIMIT) {
+ sharedText = sharedText.substring(0, Constants.TEXT_LENGTH_LIMIT);
+ Notify.create(this, R.string.snack_shared_text_too_long, Style.WARN).show();
+ }
+ // handle like normal text encryption, override action and extras to later
+ // executeServiceMethod ACTION_ENCRYPT_TEXT in main actions
+ textData = sharedText;
+ }
+ }
+
if (textData == null) {
textData = "";
}
@@ -107,7 +132,7 @@ public class EncryptTextActivity extends EncryptActivity {
if (savedInstanceState == null) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
- EncryptTextFragment encryptFragment = EncryptTextFragment.newInstance(textData);
+ EncryptTextFragment encryptFragment = EncryptTextFragment.newInstance(textData, returnProcessText);
transaction.replace(R.id.encrypt_text_container, encryptFragment);
transaction.commit();
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java
index 4ce241c02..10d88253d 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java
@@ -56,8 +56,10 @@ public class EncryptTextFragment
public static final String ARG_TEXT = "text";
public static final String ARG_USE_COMPRESSION = "use_compression";
+ public static final String ARG_RETURN_PROCESS_TEXT = "return_process_text";
private boolean mShareAfterEncrypt;
+ private boolean mReturnProcessTextAfterEncrypt;
private boolean mUseCompression;
private boolean mHiddenRecipients = false;
@@ -66,11 +68,12 @@ public class EncryptTextFragment
/**
* Creates new instance of this fragment
*/
- public static EncryptTextFragment newInstance(String text) {
+ public static EncryptTextFragment newInstance(String text, boolean returnProcessTextAfterEncrypt) {
EncryptTextFragment frag = new EncryptTextFragment();
Bundle args = new Bundle();
args.putString(ARG_TEXT, text);
+ args.putBoolean(ARG_RETURN_PROCESS_TEXT, returnProcessTextAfterEncrypt);
frag.setArguments(args);
return frag;
@@ -128,6 +131,7 @@ public class EncryptTextFragment
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
mMessage = getArguments().getString(ARG_TEXT);
+ mReturnProcessTextAfterEncrypt = getArguments().getBoolean(ARG_RETURN_PROCESS_TEXT, false);
}
Preferences prefs = Preferences.getPreferences(getActivity());
@@ -151,6 +155,12 @@ public class EncryptTextFragment
inflater.inflate(R.menu.encrypt_text_fragment, menu);
menu.findItem(R.id.check_enable_compression).setChecked(mUseCompression);
+
+ if (mReturnProcessTextAfterEncrypt) {
+ menu.findItem(R.id.encrypt_paste).setVisible(true);
+ menu.findItem(R.id.encrypt_copy).setVisible(false);
+ menu.findItem(R.id.encrypt_share).setVisible(false);
+ }
}
@Override
@@ -177,6 +187,11 @@ public class EncryptTextFragment
cryptoOperation(new CryptoInputParcel(new Date()));
break;
}
+ case R.id.encrypt_paste: {
+ hideKeyboard();
+ cryptoOperation(new CryptoInputParcel(new Date()));
+ break;
+ }
default: {
return super.onOptionsItemSelected(item);
}
@@ -328,6 +343,11 @@ public class EncryptTextFragment
// Share encrypted message/file
startActivity(Intent.createChooser(createSendIntent(result.getResultBytes()),
getString(R.string.title_share_message)));
+ } else if (mReturnProcessTextAfterEncrypt) {
+ Intent resultIntent = new Intent();
+ resultIntent.putExtra(Intent.EXTRA_PROCESS_TEXT, new String(result.getResultBytes()));
+ getActivity().setResult(Activity.RESULT_OK, resultIntent);
+ getActivity().finish();
} else {
// Copy to clipboard
copyToClipboard(result);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java
index 746c75600..8de60dfd3 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java
@@ -29,6 +29,9 @@ import android.view.ViewGroup;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
+import org.sufficientlysecure.keychain.pgp.PgpHelper;
+import org.sufficientlysecure.keychain.ui.util.Notify;
+import org.sufficientlysecure.keychain.ui.util.Notify.Style;
import org.sufficientlysecure.keychain.util.FileHelper;
public class ImportKeysFileFragment extends Fragment {
@@ -78,12 +81,16 @@ public class ImportKeysFileFragment extends Fragment {
String sendText = "";
if (clipboardText != null) {
sendText = clipboardText.toString();
+ sendText = PgpHelper.getPgpKeyContent(sendText);
+ if (sendText == null) {
+ Notify.create(mImportActivity, "Bad data!", Style.ERROR).show();
+ return;
+ }
mImportActivity.loadCallback(new ImportKeysListFragment.BytesLoaderState(sendText.getBytes(), null));
}
}
});
-
return view;
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java
index 864283b0a..7aed3176c 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java
@@ -17,6 +17,11 @@
package org.sufficientlysecure.keychain.ui;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
import android.app.Activity;
import android.net.Uri;
import android.os.Bundle;
@@ -40,22 +45,12 @@ import org.sufficientlysecure.keychain.ui.adapter.AsyncTaskResultWrapper;
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysAdapter;
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListCloudLoader;
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListLoader;
-import org.sufficientlysecure.keychain.util.FileHelper;
-import org.sufficientlysecure.keychain.util.InputData;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize;
import org.sufficientlysecure.keychain.util.ParcelableProxy;
import org.sufficientlysecure.keychain.util.Preferences;
import org.sufficientlysecure.keychain.util.orbot.OrbotHelper;
-import java.io.ByteArrayInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-
public class ImportKeysListFragment extends ListFragment implements
LoaderManager.LoaderCallbacks>> {
@@ -180,8 +175,8 @@ public class ImportKeysListFragment extends ListFragment implements
}
static public class BytesLoaderState extends LoaderState {
- byte[] mKeyBytes;
- Uri mDataUri;
+ public byte[] mKeyBytes;
+ public Uri mDataUri;
BytesLoaderState(byte[] keyBytes, Uri dataUri) {
mKeyBytes = keyBytes;
@@ -305,9 +300,7 @@ public class ImportKeysListFragment extends ListFragment implements
onCreateLoader(int id, Bundle args) {
switch (id) {
case LOADER_ID_BYTES: {
- BytesLoaderState ls = (BytesLoaderState) mLoaderState;
- InputData inputData = getInputData(ls.mKeyBytes, ls.mDataUri);
- return new ImportKeysListLoader(mActivity, inputData);
+ return new ImportKeysListLoader(mActivity, (BytesLoaderState) mLoaderState);
}
case LOADER_ID_CLOUD: {
CloudLoaderState ls = (CloudLoaderState) mLoaderState;
@@ -432,23 +425,4 @@ public class ImportKeysListFragment extends ListFragment implements
}
}
- private InputData getInputData(byte[] importBytes, Uri dataUri) {
- InputData inputData = null;
- if (importBytes != null) {
- inputData = new InputData(new ByteArrayInputStream(importBytes), importBytes.length);
- } else if (dataUri != null) {
- try {
- InputStream inputStream = getActivity().getContentResolver().openInputStream(dataUri);
- long length = FileHelper.getFileSize(getActivity(), dataUri, -1);
-
- inputData = new InputData(inputStream, length);
- } catch (FileNotFoundException e) {
- Log.e(Constants.TAG, "FileNotFoundException!", e);
- return null;
- }
- }
-
- return inputData;
- }
-
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java
index b60f3984c..45ce604c3 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java
@@ -87,12 +87,7 @@ public class ImportKeysProxyActivity extends FragmentActivity
processScannedContent(dataUri);
} else if (ACTION_SCAN_WITH_RESULT.equals(action)
|| ACTION_SCAN_IMPORT.equals(action) || ACTION_QR_CODE_API.equals(action)) {
- IntentIntegrator integrator = new IntentIntegrator(this);
- integrator.setDesiredBarcodeFormats(IntentIntegrator.QR_CODE_TYPES)
- .setPrompt(getString(R.string.import_qr_code_text))
- .setResultDisplayDuration(0);
- integrator.setOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
- integrator.initiateScan();
+ new IntentIntegrator(this).setCaptureActivity(QrCodeCaptureActivity.class).initiateScan();
} else if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) {
// Check to see if the Activity started due to an Android Beam
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java
index ce6994ba4..23c1250d0 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java
@@ -30,6 +30,7 @@ import android.graphics.Color;
import android.net.Uri;
import android.os.Build;
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;
@@ -46,14 +47,17 @@ import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AbsListView.MultiChoiceModeListener;
import android.widget.AdapterView;
+import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
+import android.widget.ViewAnimator;
import com.getbase.floatingactionbutton.FloatingActionButton;
import com.getbase.floatingactionbutton.FloatingActionsMenu;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
+import org.sufficientlysecure.keychain.operations.results.BenchmarkResult;
import org.sufficientlysecure.keychain.operations.results.ConsolidateResult;
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
@@ -61,6 +65,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.service.BenchmarkInputParcel;
import org.sufficientlysecure.keychain.service.ConsolidateInputParcel;
import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter;
@@ -97,6 +102,8 @@ public class KeyListFragment extends LoaderFragment
// saves the mode object for multiselect, needed for reset at some point
private ActionMode mActionMode = null;
+ private Button vSearchButton;
+ private ViewAnimator vSearchContainer;
private String mQuery;
private FloatingActionsMenu mFab;
@@ -161,7 +168,9 @@ public class KeyListFragment extends LoaderFragment
super.onActivityCreated(savedInstanceState);
// show app name instead of "keys" from nav drawer
- getActivity().setTitle(R.string.app_name);
+ final FragmentActivity activity = getActivity();
+
+ activity.setTitle(R.string.app_name);
mStickyList.setOnItemClickListener(this);
mStickyList.setAreHeadersSticky(true);
@@ -170,7 +179,7 @@ public class KeyListFragment extends LoaderFragment
// Adds an empty footer view so that the Floating Action Button won't block content
// in last few rows.
- View footer = new View(getActivity());
+ View footer = new View(activity);
int spacing = (int) android.util.TypedValue.applyDimension(
android.util.TypedValue.COMPLEX_UNIT_DIP, 72, getResources().getDisplayMetrics()
@@ -194,7 +203,7 @@ public class KeyListFragment extends LoaderFragment
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
- android.view.MenuInflater inflater = getActivity().getMenuInflater();
+ android.view.MenuInflater inflater = activity.getMenuInflater();
inflater.inflate(R.menu.key_list_multi, menu);
mActionMode = mode;
return true;
@@ -234,7 +243,7 @@ public class KeyListFragment extends LoaderFragment
@Override
public void onItemCheckedStateChanged(ActionMode mode, int position, long id,
- boolean checked) {
+ boolean checked) {
if (checked) {
mAdapter.setNewSelection(position, true);
} else {
@@ -254,8 +263,21 @@ public class KeyListFragment extends LoaderFragment
// Start out with a progress indicator.
setContentShown(false);
+ // this view is made visible if no data is available
+ mStickyList.setEmptyView(activity.findViewById(R.id.key_list_empty));
+
+ // click on search button (in empty view) starts query for search string
+ vSearchContainer = (ViewAnimator) activity.findViewById(R.id.search_container);
+ vSearchButton = (Button) activity.findViewById(R.id.search_button);
+ vSearchButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ startSearchForQuery();
+ }
+ });
+
// Create an empty adapter we will use to display the loaded data.
- mAdapter = new KeyListAdapter(getActivity(), null, 0);
+ mAdapter = new KeyListAdapter(activity, null, 0);
mStickyList.setAdapter(mAdapter);
// Prepare the loader. Either re-connect with an existing one,
@@ -263,8 +285,20 @@ public class KeyListFragment extends LoaderFragment
getLoaderManager().initLoader(0, null, this);
}
+ private void startSearchForQuery() {
+ Activity activity = getActivity();
+ if (activity == null) {
+ return;
+ }
+
+ Intent searchIntent = new Intent(activity, ImportKeysActivity.class);
+ searchIntent.putExtra(ImportKeysActivity.EXTRA_QUERY, mQuery);
+ searchIntent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER);
+ startActivity(searchIntent);
+ }
+
static final String ORDER =
- KeyRings.HAS_ANY_SECRET + " DESC, UPPER(" + KeyRings.USER_ID + ") ASC";
+ KeyRings.HAS_ANY_SECRET + " DESC, " + KeyRings.USER_ID + " COLLATE NOCASE ASC";
@Override
public Loader onCreateLoader(int id, Bundle args) {
@@ -318,9 +352,6 @@ public class KeyListFragment extends LoaderFragment
mStickyList.setAdapter(mAdapter);
- // this view is made visible if no data is available
- mStickyList.setEmptyView(getActivity().findViewById(R.id.key_list_empty));
-
// end action mode, if any
if (mActionMode != null) {
mActionMode.finish();
@@ -386,6 +417,7 @@ public class KeyListFragment extends LoaderFragment
if (Constants.DEBUG) {
menu.findItem(R.id.menu_key_list_debug_cons).setVisible(true);
+ menu.findItem(R.id.menu_key_list_debug_bench).setVisible(true);
menu.findItem(R.id.menu_key_list_debug_read).setVisible(true);
menu.findItem(R.id.menu_key_list_debug_write).setVisible(true);
menu.findItem(R.id.menu_key_list_debug_first_time).setVisible(true);
@@ -469,6 +501,10 @@ public class KeyListFragment extends LoaderFragment
consolidate();
return true;
+ case R.id.menu_key_list_debug_bench:
+ benchmark();
+ return true;
+
default:
return super.onOptionsItemSelected(item);
}
@@ -482,17 +518,25 @@ public class KeyListFragment extends LoaderFragment
@Override
public boolean onQueryTextChange(String s) {
Log.d(Constants.TAG, "onQueryTextChange s:" + s);
- // Called when the action bar search text has changed. Update
- // the search filter, and restart the loader to do a new query
- // with this filter.
+ // Called when the action bar search text has changed. Update the
+ // search filter, and restart the loader to do a new query with this
+ // filter.
// If the nav drawer is opened, onQueryTextChange("") is executed.
// This hack prevents restarting the loader.
- // TODO: better way to fix this?
- String tmp = (mQuery == null) ? "" : mQuery;
- if (!s.equals(tmp)) {
+ if (!s.equals(mQuery)) {
mQuery = s;
getLoaderManager().restartLoader(0, null, this);
}
+
+ if (s.length() > 2) {
+ vSearchButton.setText(getString(R.string.btn_search_for_query, mQuery));
+ vSearchContainer.setDisplayedChild(1);
+ vSearchContainer.setVisibility(View.VISIBLE);
+ } else {
+ vSearchContainer.setDisplayedChild(0);
+ vSearchContainer.setVisibility(View.GONE);
+ }
+
return true;
}
@@ -559,8 +603,8 @@ public class KeyListFragment extends LoaderFragment
mKeyserver = cloudPrefs.keyserver;
}
- mImportOpHelper = new CryptoOperationHelper<>(1, this,
- this, R.string.progress_updating);
+ mImportOpHelper = new CryptoOperationHelper<>(1, this, this, R.string.progress_updating);
+ mImportOpHelper.setProgressCancellable(true);
mImportOpHelper.cryptoOperation();
}
@@ -601,6 +645,43 @@ public class KeyListFragment extends LoaderFragment
mConsolidateOpHelper.cryptoOperation();
}
+ private void benchmark() {
+
+ CryptoOperationHelper.Callback callback
+ = new CryptoOperationHelper.Callback() {
+
+ @Override
+ public BenchmarkInputParcel createOperationInput() {
+ return new BenchmarkInputParcel(); // we want to perform a full consolidate
+ }
+
+ @Override
+ public void onCryptoOperationSuccess(BenchmarkResult result) {
+ result.createNotify(getActivity()).show();
+ }
+
+ @Override
+ public void onCryptoOperationCancelled() {
+
+ }
+
+ @Override
+ public void onCryptoOperationError(BenchmarkResult result) {
+ result.createNotify(getActivity()).show();
+ }
+
+ @Override
+ public boolean onCryptoSetProgress(String msg, int progress, int max) {
+ return false;
+ }
+ };
+
+ CryptoOperationHelper opHelper =
+ new CryptoOperationHelper<>(2, this, callback, R.string.progress_importing);
+
+ opHelper.cryptoOperation();
+ }
+
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (mImportOpHelper != null) {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java
index 7e9b4953c..7bd7bafcc 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2014 Dominik Schürmann
+ * Copyright (C) 2012-2015 Dominik Schürmann
* Copyright (C) 2014 Vincent Breitmoser
* Copyright (C) 2015 Kai Jiang
*
@@ -27,13 +27,13 @@ import android.support.v4.app.FragmentManager.OnBackStackChangedListener;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.widget.Toolbar;
import android.view.View;
-import android.widget.AdapterView;
import com.mikepenz.community_material_typeface_library.CommunityMaterial;
+import com.mikepenz.fontawesome_typeface_library.FontAwesome;
import com.mikepenz.google_material_typeface_library.GoogleMaterial;
-import com.mikepenz.iconics.typeface.FontAwesome;
import com.mikepenz.materialdrawer.Drawer;
import com.mikepenz.materialdrawer.DrawerBuilder;
+import com.mikepenz.materialdrawer.model.DividerDrawerItem;
import com.mikepenz.materialdrawer.model.PrimaryDrawerItem;
import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem;
@@ -75,25 +75,23 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac
.withToolbar(mToolbar)
.addDrawerItems(
new PrimaryDrawerItem().withName(R.string.nav_keys).withIcon(CommunityMaterial.Icon.cmd_key)
- .withIdentifier(ID_KEYS).withCheckable(false),
+ .withIdentifier(ID_KEYS).withSelectable(false),
new PrimaryDrawerItem().withName(R.string.nav_encrypt_decrypt).withIcon(FontAwesome.Icon.faw_lock)
- .withIdentifier(ID_ENCRYPT_DECRYPT).withCheckable(false),
+ .withIdentifier(ID_ENCRYPT_DECRYPT).withSelectable(false),
new PrimaryDrawerItem().withName(R.string.title_api_registered_apps).withIcon(CommunityMaterial.Icon.cmd_apps)
- .withIdentifier(ID_APPS).withCheckable(false),
+ .withIdentifier(ID_APPS).withSelectable(false),
new PrimaryDrawerItem().withName(R.string.nav_backup).withIcon(CommunityMaterial.Icon.cmd_backup_restore)
- .withIdentifier(ID_BACKUP).withCheckable(false)
- )
- .addStickyDrawerItems(
- // display and stick on bottom of drawer
- new PrimaryDrawerItem().withName(R.string.menu_preferences).withIcon(GoogleMaterial.Icon.gmd_settings).withIdentifier(ID_SETTINGS).withCheckable(false),
- new PrimaryDrawerItem().withName(R.string.menu_help).withIcon(CommunityMaterial.Icon.cmd_help_circle).withIdentifier(ID_HELP).withCheckable(false)
+ .withIdentifier(ID_BACKUP).withSelectable(false),
+ new DividerDrawerItem(),
+ new PrimaryDrawerItem().withName(R.string.menu_preferences).withIcon(GoogleMaterial.Icon.gmd_settings).withIdentifier(ID_SETTINGS).withSelectable(false),
+ new PrimaryDrawerItem().withName(R.string.menu_help).withIcon(CommunityMaterial.Icon.cmd_help_circle).withIdentifier(ID_HELP).withSelectable(false)
)
.withOnDrawerItemClickListener(new Drawer.OnDrawerItemClickListener() {
@Override
- public boolean onItemClick(AdapterView> parent, View view, int position, long id, IDrawerItem drawerItem) {
+ public boolean onItemClick(View view, int position, IDrawerItem drawerItem) {
if (drawerItem != null) {
Intent intent = null;
- switch(drawerItem.getIdentifier()) {
+ switch (drawerItem.getIdentifier()) {
case ID_KEYS:
onKeysSelected();
break;
@@ -182,28 +180,28 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac
private void onKeysSelected() {
mToolbar.setTitle(R.string.app_name);
- mDrawer.setSelectionByIdentifier(ID_KEYS, false);
+ mDrawer.setSelection(ID_KEYS, false);
Fragment frag = new KeyListFragment();
setFragment(frag, false);
}
private void onEnDecryptSelected() {
mToolbar.setTitle(R.string.nav_encrypt_decrypt);
- mDrawer.setSelectionByIdentifier(ID_ENCRYPT_DECRYPT, false);
+ mDrawer.setSelection(ID_ENCRYPT_DECRYPT, false);
Fragment frag = new EncryptDecryptFragment();
setFragment(frag, true);
}
private void onAppsSelected() {
mToolbar.setTitle(R.string.nav_apps);
- mDrawer.setSelectionByIdentifier(ID_APPS, false);
+ mDrawer.setSelection(ID_APPS, false);
Fragment frag = new AppsListFragment();
setFragment(frag, true);
}
private void onBackupSelected() {
mToolbar.setTitle(R.string.nav_backup);
- mDrawer.setSelectionByIdentifier(ID_BACKUP, false);
+ mDrawer.setSelection(ID_BACKUP, false);
Fragment frag = new BackupRestoreFragment();
setFragment(frag, true);
}
@@ -258,16 +256,16 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac
// make sure the selected icon is the one shown at this point
if (frag instanceof KeyListFragment) {
mToolbar.setTitle(R.string.app_name);
- mDrawer.setSelection(mDrawer.getPositionFromIdentifier(ID_KEYS), false);
+ mDrawer.setSelection(mDrawer.getPosition(ID_KEYS), false);
} else if (frag instanceof EncryptDecryptFragment) {
mToolbar.setTitle(R.string.nav_encrypt_decrypt);
- mDrawer.setSelection(mDrawer.getPositionFromIdentifier(ID_ENCRYPT_DECRYPT), false);
+ mDrawer.setSelection(mDrawer.getPosition(ID_ENCRYPT_DECRYPT), false);
} else if (frag instanceof AppsListFragment) {
mToolbar.setTitle(R.string.nav_apps);
- mDrawer.setSelection(mDrawer.getPositionFromIdentifier(ID_APPS), false);
+ mDrawer.setSelection(mDrawer.getPosition(ID_APPS), false);
} else if (frag instanceof BackupRestoreFragment) {
mToolbar.setTitle(R.string.nav_backup);
- mDrawer.setSelection(mDrawer.getPositionFromIdentifier(ID_BACKUP), false);
+ mDrawer.setSelection(mDrawer.getPosition(ID_BACKUP), false);
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/QrCodeCaptureActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/QrCodeCaptureActivity.java
new file mode 100644
index 000000000..b5d3948be
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/QrCodeCaptureActivity.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2015 Dominik Schürmann
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package org.sufficientlysecure.keychain.ui;
+
+import android.Manifest;
+import android.app.Activity;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.content.ContextCompat;
+import android.view.KeyEvent;
+
+import com.journeyapps.barcodescanner.CaptureManager;
+import com.journeyapps.barcodescanner.CompoundBarcodeView;
+
+import org.sufficientlysecure.keychain.R;
+
+public class QrCodeCaptureActivity extends FragmentActivity {
+ private CaptureManager capture;
+ private CompoundBarcodeView barcodeScannerView;
+
+ public static final int MY_PERMISSIONS_REQUEST_CAMERA = 42;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.qr_code_capture_activity);
+
+ barcodeScannerView = (CompoundBarcodeView) findViewById(R.id.zxing_barcode_scanner);
+ barcodeScannerView.setStatusText(getString(R.string.import_qr_code_text));
+
+ if (savedInstanceState != null) {
+ init(barcodeScannerView, getIntent(), savedInstanceState);
+ }
+
+ // check Android 6 permission
+ if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
+ == PackageManager.PERMISSION_GRANTED) {
+ init(barcodeScannerView, getIntent(), null);
+ } else {
+ ActivityCompat.requestPermissions(this,
+ new String[]{Manifest.permission.CAMERA},
+ MY_PERMISSIONS_REQUEST_CAMERA);
+ }
+ }
+
+ private void init(CompoundBarcodeView barcodeScannerView, Intent intent, Bundle savedInstanceState) {
+ capture = new CaptureManager(this, barcodeScannerView);
+ capture.initializeFromIntent(intent, savedInstanceState);
+ capture.decode();
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[],
+ @NonNull int[] grantResults) {
+ switch (requestCode) {
+ case MY_PERMISSIONS_REQUEST_CAMERA: {
+ if (grantResults.length > 0
+ && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ // permission was granted
+ init(barcodeScannerView, getIntent(), null);
+ } else {
+ setResult(Activity.RESULT_CANCELED);
+ finish();
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ if (capture != null) {
+ capture.onResume();
+ }
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ if (capture != null) {
+ capture.onPause();
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ if (capture != null) {
+ capture.onDestroy();
+ }
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ if (capture != null) {
+ capture.onSaveInstanceState(outState);
+ }
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ return barcodeScannerView.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event);
+ }
+}
\ No newline at end of file
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java
index 8118c2fa7..f5c239558 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java
@@ -54,14 +54,8 @@ import java.util.List;
public class SettingsActivity extends AppCompatPreferenceActivity {
- public static final String ACTION_PREFS_CLOUD = "org.sufficientlysecure.keychain.ui.PREFS_CLOUD";
- public static final String ACTION_PREFS_ADV = "org.sufficientlysecure.keychain.ui.PREFS_ADV";
- public static final String ACTION_PREFS_PROXY = "org.sufficientlysecure.keychain.ui.PREFS_PROXY";
- public static final String ACTION_PREFS_GUI = "org.sufficientlysecure.keychain.ui.PREFS_GUI";
-
public static final int REQUEST_CODE_KEYSERVER_PREF = 0x00007005;
- private PreferenceScreen mKeyServerPreference = null;
private static Preferences sPreferences;
private ThemeChanger mThemeChanger;
@@ -74,49 +68,6 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
super.onCreate(savedInstanceState);
setupToolbar();
-
- String action = getIntent().getAction();
-
- if (ACTION_PREFS_CLOUD.equals(action)) {
- addPreferencesFromResource(R.xml.cloud_search_prefs);
-
- mKeyServerPreference = (PreferenceScreen) findPreference(Constants.Pref.KEY_SERVERS);
- mKeyServerPreference.setSummary(keyserverSummary(this));
- mKeyServerPreference
- .setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
- public boolean onPreferenceClick(Preference preference) {
- Intent intent = new Intent(SettingsActivity.this,
- SettingsKeyServerActivity.class);
- intent.putExtra(SettingsKeyServerActivity.EXTRA_KEY_SERVERS,
- sPreferences.getKeyServers());
- startActivityForResult(intent, REQUEST_CODE_KEYSERVER_PREF);
- return false;
- }
- });
- initializeSearchKeyserver(
- (SwitchPreference) findPreference(Constants.Pref.SEARCH_KEYSERVER)
- );
- initializeSearchKeybase(
- (SwitchPreference) findPreference(Constants.Pref.SEARCH_KEYBASE)
- );
-
- } else if (ACTION_PREFS_ADV.equals(action)) {
- addPreferencesFromResource(R.xml.passphrase_preferences);
-
- initializePassphraseCacheSubs(
- (CheckBoxPreference) findPreference(Constants.Pref.PASSPHRASE_CACHE_SUBS));
-
- initializePassphraseCacheTtl(
- (IntegerListPreference) findPreference(Constants.Pref.PASSPHRASE_CACHE_TTL));
-
- initializeUseNumKeypadForYubiKeyPin(
- (CheckBoxPreference) findPreference(Constants.Pref.USE_NUMKEYPAD_FOR_YUBIKEY_PIN));
-
- } else if (ACTION_PREFS_GUI.equals(action)) {
- addPreferencesFromResource(R.xml.gui_preferences);
-
- initializeTheme((ListPreference) findPreference(Constants.Pref.THEME));
- }
}
@Override
@@ -447,23 +398,6 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
}
}
- /**
- * This fragment shows gui preferences.
- */
- public static class GuiPrefsFragment extends PreferenceFragment {
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
-
- // Load the preferences from an XML resource
- addPreferencesFromResource(R.xml.gui_preferences);
-
- initializeTheme((ListPreference) findPreference(Constants.Pref.THEME));
- }
- }
-
/**
* This fragment shows the keyserver/contacts sync preferences
*/
@@ -576,7 +510,6 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
return PassphrasePrefsFragment.class.getName().equals(fragmentName)
|| CloudSearchPrefsFragment.class.getName().equals(fragmentName)
|| ProxyPrefsFragment.class.getName().equals(fragmentName)
- || GuiPrefsFragment.class.getName().equals(fragmentName)
|| SyncPrefsFragment.class.getName().equals(fragmentName)
|| ExperimentalPrefsFragment.class.getName().equals(fragmentName)
|| super.isValidFragment(fragmentName);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java
index cbc7b88bf..0184527b7 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java
@@ -38,6 +38,7 @@ import android.os.Handler;
import android.provider.ContactsContract;
import android.support.design.widget.AppBarLayout;
import android.support.design.widget.CollapsingToolbarLayout;
+import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.FloatingActionButton;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.FragmentManager;
@@ -869,7 +870,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements
mActionEncryptFile.setVisibility(View.INVISIBLE);
mActionEncryptText.setVisibility(View.INVISIBLE);
mActionNfc.setVisibility(View.INVISIBLE);
- mFab.setVisibility(View.GONE);
+ hideFab();
mQrCodeLayout.setVisibility(View.GONE);
} else if (mIsExpired) {
if (mIsSecret) {
@@ -885,7 +886,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements
mActionEncryptFile.setVisibility(View.INVISIBLE);
mActionEncryptText.setVisibility(View.INVISIBLE);
mActionNfc.setVisibility(View.INVISIBLE);
- mFab.setVisibility(View.GONE);
+ hideFab();
mQrCodeLayout.setVisibility(View.GONE);
} else if (mIsSecret) {
mStatusText.setText(R.string.view_key_my_key);
@@ -927,7 +928,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements
} else {
mActionNfc.setVisibility(View.GONE);
}
- mFab.setVisibility(View.VISIBLE);
+ showFab();
// noinspection deprecation (no getDrawable with theme at current minApi level 15!)
mFab.setImageDrawable(getResources().getDrawable(R.drawable.ic_repeat_white_24dp));
} else {
@@ -944,7 +945,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements
color = getResources().getColor(R.color.key_flag_green);
photoTask.execute(mMasterKeyId);
- mFab.setVisibility(View.GONE);
+ hideFab();
} else {
mStatusText.setText(R.string.view_key_unverified);
mStatusImage.setVisibility(View.VISIBLE);
@@ -952,7 +953,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements
State.UNVERIFIED, R.color.icons, true);
color = getResources().getColor(R.color.key_flag_orange);
- mFab.setVisibility(View.VISIBLE);
+ showFab();
}
}
@@ -982,6 +983,28 @@ public class ViewKeyActivity extends BaseNfcActivity implements
}
}
+ /**
+ * Helper to show Fab, from http://stackoverflow.com/a/31047038
+ */
+ private void showFab() {
+ CoordinatorLayout.LayoutParams p = (CoordinatorLayout.LayoutParams) mFab.getLayoutParams();
+ p.setBehavior(new FloatingActionButton.Behavior());
+ p.setAnchorId(R.id.app_bar_layout);
+ mFab.setLayoutParams(p);
+ mFab.setVisibility(View.VISIBLE);
+ }
+
+ /**
+ * Helper to hide Fab, from http://stackoverflow.com/a/31047038
+ */
+ private void hideFab() {
+ CoordinatorLayout.LayoutParams p = (CoordinatorLayout.LayoutParams) mFab.getLayoutParams();
+ p.setBehavior(null); //should disable default animations
+ p.setAnchorId(View.NO_ID); //should let you set visibility
+ mFab.setLayoutParams(p);
+ mFab.setVisibility(View.GONE);
+ }
+
@Override
public void onLoaderReset(Loader loader) {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java
index 139512ba9..038ebd5dd 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java
@@ -17,6 +17,14 @@
package org.sufficientlysecure.keychain.ui.adapter;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+
import android.content.Context;
import android.support.v4.content.AsyncTaskLoader;
import android.support.v4.util.LongSparseArray;
@@ -28,28 +36,26 @@ import org.sufficientlysecure.keychain.operations.results.GetKeyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing.IteratorWithIOThrow;
+import org.sufficientlysecure.keychain.ui.ImportKeysListFragment.BytesLoaderState;
+import org.sufficientlysecure.keychain.util.FileHelper;
import org.sufficientlysecure.keychain.util.InputData;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.PositionAwareInputStream;
-import java.io.BufferedInputStream;
-import java.io.IOException;
-import java.util.ArrayList;
-
public class ImportKeysListLoader
extends AsyncTaskLoader>> {
final Context mContext;
- final InputData mInputData;
+ final BytesLoaderState mLoaderState;
ArrayList mData = new ArrayList<>();
LongSparseArray mParcelableRings = new LongSparseArray<>();
AsyncTaskResultWrapper> mEntryListWrapper;
- public ImportKeysListLoader(Context context, InputData inputData) {
+ public ImportKeysListLoader(Context context, BytesLoaderState inputData) {
super(context);
this.mContext = context;
- this.mInputData = inputData;
+ this.mLoaderState = inputData;
}
@Override
@@ -62,12 +68,13 @@ public class ImportKeysListLoader
GetKeyResult getKeyResult = new GetKeyResult(GetKeyResult.RESULT_OK, null);
mEntryListWrapper = new AsyncTaskResultWrapper<>(mData, getKeyResult);
- if (mInputData == null) {
+ if (mLoaderState == null) {
Log.e(Constants.TAG, "Input data is null!");
return mEntryListWrapper;
}
- generateListOfKeyrings(mInputData);
+ InputData inputData = getInputData(getContext(), mLoaderState);
+ generateListOfKeyrings(inputData);
return mEntryListWrapper;
}
@@ -99,12 +106,7 @@ public class ImportKeysListLoader
return mParcelableRings;
}
- /**
- * Reads all PGPKeyRing objects from input
- *
- * @param inputData
- * @return
- */
+ /** Reads all PGPKeyRing objects from the bytes of an InputData object. */
private void generateListOfKeyrings(InputData inputData) {
PositionAwareInputStream progressIn = new PositionAwareInputStream(
inputData.getInputStream());
@@ -132,4 +134,23 @@ public class ImportKeysListLoader
}
}
+ private static InputData getInputData(Context context, BytesLoaderState loaderState) {
+ InputData inputData = null;
+ if (loaderState.mKeyBytes != null) {
+ inputData = new InputData(new ByteArrayInputStream(loaderState.mKeyBytes), loaderState.mKeyBytes.length);
+ } else if (loaderState.mDataUri != null) {
+ try {
+ InputStream inputStream = context.getContentResolver().openInputStream(loaderState.mDataUri);
+ long length = FileHelper.getFileSize(context, loaderState.mDataUri, -1);
+
+ inputData = new InputData(inputStream, length);
+ } catch (FileNotFoundException e) {
+ Log.e(Constants.TAG, "FileNotFoundException!", e);
+ return null;
+ }
+ }
+
+ return inputData;
+ }
+
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeySelectableAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeySelectableAdapter.java
index 471a20411..7cc37b3a3 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeySelectableAdapter.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeySelectableAdapter.java
@@ -1,13 +1,8 @@
package org.sufficientlysecure.keychain.ui.adapter;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-
import android.content.Context;
import android.database.Cursor;
-import android.support.v7.internal.widget.AdapterViewCompat;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
@@ -18,6 +13,10 @@ import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.util.Log;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
public class KeySelectableAdapter extends KeyAdapter implements OnItemClickListener {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/LinkedIdsAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/LinkedIdsAdapter.java
index 5cf0e6e08..5566c725b 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/LinkedIdsAdapter.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/LinkedIdsAdapter.java
@@ -24,6 +24,7 @@ import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
+import android.os.Build.VERSION_CODES;
import android.support.v4.content.CursorLoader;
import android.util.Log;
import android.view.LayoutInflater;
@@ -228,9 +229,11 @@ public class LinkedIdsAdapter extends UserAttributesAdapter {
}
public void seekAttention() {
- ObjectAnimator anim = SubtleAttentionSeeker.tintText(vComment, 1000);
- anim.setStartDelay(200);
- anim.start();
+ if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
+ ObjectAnimator anim = SubtleAttentionSeeker.tintText(vComment, 1000);
+ anim.setStartDelay(200);
+ anim.start();
+ }
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java
index d2877d542..7ab9c7237 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java
@@ -84,6 +84,7 @@ public class CryptoOperationHelper
implements LoaderCallbacks {
public static final String ARG_QUERY = "query";
private KeyAdapter mAdapter;
private LoaderManager mLoaderManager;
- private String mPrefix;
+ private CharSequence mPrefix;
public EncryptKeyCompletionView(Context context) {
super(context);
@@ -79,30 +81,27 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView
}
@Override
- public void setPrefix(String p) {
+ public void setPrefix(CharSequence p) {
// this one is private in the superclass, but we need it here
mPrefix = p;
super.setPrefix(p);
}
@Override
- protected View getViewForObject(Object object) {
- if (object instanceof KeyItem) {
- LayoutInflater l = LayoutInflater.from(getContext());
- View view = l.inflate(R.layout.recipient_box_entry, null);
- ((TextView) view.findViewById(android.R.id.text1)).setText(((KeyItem) object).getReadableName());
- return view;
- }
- return null;
+ protected View getViewForObject(KeyItem keyItem) {
+ LayoutInflater l = LayoutInflater.from(getContext());
+ View view = l.inflate(R.layout.recipient_box_entry, null);
+ ((TextView) view.findViewById(android.R.id.text1)).setText(keyItem.getReadableName());
+ return view;
}
@Override
- protected Object defaultObject(String completionText) {
+ protected KeyItem 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 "";
+ return null;
}
@Override
@@ -128,7 +127,7 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView
// These are the rows that we will retrieve.
Uri baseUri = KeyRings.buildUnifiedKeyRingsUri();
- String[] projection = KeyAdapter.getProjectionWith(new String[] {
+ String[] projection = KeyAdapter.getProjectionWith(new String[]{
KeychainContract.KeyRings.HAS_ENCRYPT,
});
@@ -136,18 +135,19 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView
+ KeyRings.IS_EXPIRED + " = 0 AND "
+ Tables.KEYS + "." + KeyRings.IS_REVOKED + " = 0";
- if (args != null && args.containsKey(ARG_QUERY)) {
- String query = args.getString(ARG_QUERY);
- mAdapter.setSearchQuery(query);
-
- where += " AND " + KeyRings.USER_ID + " LIKE ?";
-
- return new CursorLoader(getContext(), baseUri, projection, where,
- new String[]{"%" + query + "%"}, null);
+ if (args == null || !args.containsKey(ARG_QUERY)) {
+ // mAdapter.setSearchQuery(null);
+ // return new CursorLoader(getContext(), baseUri, projection, where, null, null);
+ return null;
}
- mAdapter.setSearchQuery(null);
- return new CursorLoader(getContext(), baseUri, projection, where, null, null);
+ String query = args.getString(ARG_QUERY);
+ mAdapter.setSearchQuery(query);
+
+ where += " AND " + KeyRings.USER_ID + " LIKE ?";
+
+ return new CursorLoader(getContext(), baseUri, projection, where,
+ new String[]{"%" + query + "%"}, null);
}
@@ -169,6 +169,8 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView
super.showDropDown();
}
+
+
@Override
public void onFocusChanged(boolean hasFocus, int direction, Rect previous) {
super.onFocusChanged(hasFocus, direction, previous);
@@ -179,13 +181,18 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView
}
@Override
- protected void performFiltering(CharSequence text, int start, int end, int keyCode) {
+ protected void performFiltering(@NonNull CharSequence text, int start, int end, int keyCode) {
super.performFiltering(text, start, end, keyCode);
if (start < mPrefix.length()) {
start = mPrefix.length();
}
+ String query = text.subSequence(start, end).toString();
+ if (TextUtils.isEmpty(query) || query.length() < 2) {
+ mLoaderManager.destroyLoader(0);
+ return;
+ }
Bundle args = new Bundle();
- args.putString(ARG_QUERY, text.subSequence(start, end).toString());
+ args.putString(ARG_QUERY, query);
mLoaderManager.restartLoader(0, args, this);
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/EmailKeyHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/EmailKeyHelper.java
index 9a6d33260..a55249842 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/EmailKeyHelper.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/EmailKeyHelper.java
@@ -74,9 +74,9 @@ public class EmailKeyHelper {
// Try _hkp._tcp SRV record first
String[] mailparts = mail.split("@");
if (mailparts.length == 2) {
- HkpKeyserver hkp = HkpKeyserver.resolve(mailparts[1]);
+ HkpKeyserver hkp = HkpKeyserver.resolve(mailparts[1], proxy);
if (hkp != null) {
- keys.addAll(getEmailKeys(mail, hkp, proxy));
+ keys.addAll(getEmailKeys(mail, hkp));
}
}
@@ -84,18 +84,17 @@ public class EmailKeyHelper {
// Most users don't have the SRV record, so ask a default server as well
String server = Preferences.getPreferences(context).getPreferredKeyserver();
if (server != null) {
- HkpKeyserver hkp = new HkpKeyserver(server);
- keys.addAll(getEmailKeys(mail, hkp, proxy));
+ HkpKeyserver hkp = new HkpKeyserver(server, proxy);
+ keys.addAll(getEmailKeys(mail, hkp));
}
}
return keys;
}
- public static List getEmailKeys(String mail, Keyserver keyServer,
- Proxy proxy) {
+ public static List getEmailKeys(String mail, Keyserver keyServer) {
Set keys = new HashSet<>();
try {
- for (ImportKeysListEntry key : keyServer.search(mail, proxy)) {
+ for (ImportKeysListEntry key : keyServer.search(mail)) {
if (key.isRevoked() || key.isExpired()) continue;
for (String userId : key.getUserIds()) {
if (userId.toLowerCase().contains(mail.toLowerCase(Locale.ENGLISH))) {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelper.java
index 236913be7..7345faad9 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelper.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelper.java
@@ -272,57 +272,21 @@ public class FileHelper {
return true;
}
- /**
- * Tests whether a file is readable by others
- */
- @TargetApi(VERSION_CODES.LOLLIPOP)
- public static boolean S_IROTH(int mode) {
- return (mode & S_IROTH) == S_IROTH;
- }
-
- /**
- * A replacement for ContentResolver.openInputStream() that does not allow the usage of
- * "file" Uris that point to private files owned by the application only.
+ /** A replacement for ContentResolver.openInputStream() that does not allow
+ * the usage of "file" Uris that point to private files owned by the
+ * application only, *on Lollipop devices*.
*
- * This is not allowed:
- * am start -a android.intent.action.SEND -t text/plain -n
- * "org.sufficientlysecure.keychain.debug/org.sufficientlysecure.keychain.ui.EncryptFilesActivity" --eu
- * android.intent.extra.STREAM
- * file:///data/data/org.sufficientlysecure.keychain.debug/databases/openkeychain.db
+ * The check will be performed on devices >= Lollipop only, which have the
+ * necessary API to stat filedescriptors.
*
- * @throws FileNotFoundException
+ * @see FileHelperLollipop
*/
- @TargetApi(VERSION_CODES.LOLLIPOP)
public static InputStream openInputStreamSafe(ContentResolver resolver, Uri uri)
- throws FileNotFoundException {
+ throws FileNotFoundException {
// Not supported on Android < 5
- if (Build.VERSION.SDK_INT < VERSION_CODES.LOLLIPOP) {
- return resolver.openInputStream(uri);
- }
-
- String scheme = uri.getScheme();
- if (ContentResolver.SCHEME_FILE.equals(scheme)) {
- ParcelFileDescriptor pfd = ParcelFileDescriptor.open(
- new File(uri.getPath()), ParcelFileDescriptor.parseMode("r"));
-
- try {
- final StructStat st = Os.fstat(pfd.getFileDescriptor());
- if (!S_IROTH(st.st_mode)) {
- Log.e(Constants.TAG, "File is not readable by others, aborting!");
- throw new FileNotFoundException("Unable to create stream");
- }
- } catch (ErrnoException e) {
- Log.e(Constants.TAG, "fstat() failed: " + e);
- throw new FileNotFoundException("fstat() failed");
- }
-
- AssetFileDescriptor fd = new AssetFileDescriptor(pfd, 0, -1);
- try {
- return fd.createInputStream();
- } catch (IOException e) {
- throw new FileNotFoundException("Unable to create stream");
- }
+ if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
+ return FileHelperLollipop.openInputStreamSafe(resolver, uri);
} else {
return resolver.openInputStream(uri);
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelperLollipop.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelperLollipop.java
new file mode 100644
index 000000000..f89d679bc
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelperLollipop.java
@@ -0,0 +1,82 @@
+package org.sufficientlysecure.keychain.util;
+
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+
+import android.annotation.TargetApi;
+import android.content.ContentResolver;
+import android.content.res.AssetFileDescriptor;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Build.VERSION_CODES;
+import android.os.ParcelFileDescriptor;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.StructStat;
+
+import org.sufficientlysecure.keychain.Constants;
+
+import static android.system.OsConstants.S_IROTH;
+
+
+/** FileHelper methods which use Lollipop-exclusive API.
+ * Some of the methods and static fields used here cause VerifyErrors because
+ * they do not exist in pre-lollipop API, so they must be kept in a
+ * lollipop-only class. All methods here should only be called by FileHelper,
+ * and consequently have package visibility.
+ */
+@TargetApi(VERSION_CODES.LOLLIPOP)
+class FileHelperLollipop {
+ /**
+ * Tests whether a file is readable by others
+ */
+ private static boolean S_IROTH(int mode) {
+ return (mode & S_IROTH) == S_IROTH;
+ }
+
+ /**
+ * A replacement for ContentResolver.openInputStream() that does not allow the usage of
+ * "file" Uris that point to private files owned by the application only.
+ *
+ * This is not allowed:
+ * am start -a android.intent.action.SEND -t text/plain -n
+ * "org.sufficientlysecure.keychain.debug/org.sufficientlysecure.keychain.ui.EncryptFilesActivity" --eu
+ * android.intent.extra.STREAM
+ * file:///data/data/org.sufficientlysecure.keychain.debug/databases/openkeychain.db
+ *
+ * @throws FileNotFoundException
+ */
+ static InputStream openInputStreamSafe(ContentResolver resolver, Uri uri)
+ throws FileNotFoundException {
+
+ String scheme = uri.getScheme();
+ if (ContentResolver.SCHEME_FILE.equals(scheme)) {
+ ParcelFileDescriptor pfd = ParcelFileDescriptor.open(
+ new File(uri.getPath()), ParcelFileDescriptor.parseMode("r"));
+
+ try {
+ final StructStat st = Os.fstat(pfd.getFileDescriptor());
+ if (!S_IROTH(st.st_mode)) {
+ Log.e(Constants.TAG, "File is not readable by others, aborting!");
+ throw new FileNotFoundException("Unable to create stream");
+ }
+ } catch (ErrnoException e) {
+ Log.e(Constants.TAG, "fstat() failed: " + e);
+ throw new FileNotFoundException("fstat() failed");
+ }
+
+ AssetFileDescriptor fd = new AssetFileDescriptor(pfd, 0, -1);
+ try {
+ return fd.createInputStream();
+ } catch (IOException e) {
+ throw new FileNotFoundException("Unable to create stream");
+ }
+ } else {
+ return resolver.openInputStream(uri);
+ }
+
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ParcelableProxy.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ParcelableProxy.java
index 7e788d04c..7e2328e99 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ParcelableProxy.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ParcelableProxy.java
@@ -17,12 +17,14 @@
package org.sufficientlysecure.keychain.util;
-import android.os.Parcel;
-import android.os.Parcelable;
import java.net.InetSocketAddress;
import java.net.Proxy;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.annotation.NonNull;
+
/**
* used to simply transport java.net.Proxy objects created using InetSockets between services/activities
*/
@@ -47,9 +49,10 @@ public class ParcelableProxy implements Parcelable {
return new ParcelableProxy(null, -1, null);
}
+ @NonNull
public Proxy getProxy() {
if (mProxyHost == null) {
- return null;
+ return Proxy.NO_PROXY;
}
/*
* InetSocketAddress.createUnresolved so we can use this method even in the main thread
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java
index 8b2c3c66a..559c5556f 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java
@@ -23,6 +23,9 @@ import android.content.SharedPreferences;
import android.content.res.Resources;
import android.preference.PreferenceManager;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Constants.Pref;
import org.sufficientlysecure.keychain.service.KeyserverSyncAdapterService;
@@ -322,6 +325,12 @@ public class Preferences {
if (!torEnabled && !normalPorxyEnabled) this.parcelableProxy = new ParcelableProxy(null, -1, null);
else this.parcelableProxy = new ParcelableProxy(hostName, port, type);
}
+
+ @NonNull
+ public Proxy getProxy() {
+ return parcelableProxy.getProxy();
+ }
+
}
// cloud prefs
diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_action_encrypt_paste_24dp.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_action_encrypt_paste_24dp.png
new file mode 100644
index 000000000..356cfbe43
Binary files /dev/null and b/OpenKeychain/src/main/res/drawable-hdpi/ic_action_encrypt_paste_24dp.png differ
diff --git a/OpenKeychain/src/main/res/drawable-mdpi/ic_action_encrypt_paste_24dp.png b/OpenKeychain/src/main/res/drawable-mdpi/ic_action_encrypt_paste_24dp.png
new file mode 100644
index 000000000..a546e152b
Binary files /dev/null and b/OpenKeychain/src/main/res/drawable-mdpi/ic_action_encrypt_paste_24dp.png differ
diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_encrypt_paste_24dp.png b/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_encrypt_paste_24dp.png
new file mode 100644
index 000000000..ed7fdb732
Binary files /dev/null and b/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_encrypt_paste_24dp.png differ
diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/status_signature_expired_cutout_96dp.png b/OpenKeychain/src/main/res/drawable-xhdpi/status_signature_expired_cutout_96dp.png
index e69de29bb..568b48c43 100644
Binary files a/OpenKeychain/src/main/res/drawable-xhdpi/status_signature_expired_cutout_96dp.png and b/OpenKeychain/src/main/res/drawable-xhdpi/status_signature_expired_cutout_96dp.png differ
diff --git a/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_encrypt_paste_24dp.png b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_encrypt_paste_24dp.png
new file mode 100644
index 000000000..50987bdd6
Binary files /dev/null and b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_encrypt_paste_24dp.png differ
diff --git a/OpenKeychain/src/main/res/drawable-xxxhdpi/ic_action_encrypt_paste_24dp.png b/OpenKeychain/src/main/res/drawable-xxxhdpi/ic_action_encrypt_paste_24dp.png
new file mode 100644
index 000000000..11ad7e219
Binary files /dev/null and b/OpenKeychain/src/main/res/drawable-xxxhdpi/ic_action_encrypt_paste_24dp.png differ
diff --git a/OpenKeychain/src/main/res/layout/key_list_fragment.xml b/OpenKeychain/src/main/res/layout/key_list_fragment.xml
index 26cedd362..6aaf5be25 100644
--- a/OpenKeychain/src/main/res/layout/key_list_fragment.xml
+++ b/OpenKeychain/src/main/res/layout/key_list_fragment.xml
@@ -3,6 +3,8 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent"
+ xmlns:tools="http://schemas.android.com/tools"
+ xmlns:custom="http://schemas.android.com/apk/res-auto"
>
@@ -23,10 +25,11 @@
+ android:animateLayoutChanges="true"
+ >
+
+
+
+
+
+
+
+
diff --git a/OpenKeychain/src/main/res/layout/qr_code_capture_activity.xml b/OpenKeychain/src/main/res/layout/qr_code_capture_activity.xml
new file mode 100644
index 000000000..094901740
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/qr_code_capture_activity.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/toolbar_tabs.xml b/OpenKeychain/src/main/res/layout/toolbar_tabs.xml
index ed42ef52e..315aaf75a 100644
--- a/OpenKeychain/src/main/res/layout/toolbar_tabs.xml
+++ b/OpenKeychain/src/main/res/layout/toolbar_tabs.xml
@@ -18,7 +18,7 @@
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:textColor="?attr/colorTabText"
- app:pstsTextColorSelected="?attr/colorTabTextSelected"
+ app:pstsTabTextColor="?attr/colorTabTextSelected"
app:pstsIndicatorColor="?attr/colorTabIndicator" />
diff --git a/OpenKeychain/src/main/res/menu/encrypt_text_fragment.xml b/OpenKeychain/src/main/res/menu/encrypt_text_fragment.xml
index 80b78457d..4d3d53870 100644
--- a/OpenKeychain/src/main/res/menu/encrypt_text_fragment.xml
+++ b/OpenKeychain/src/main/res/menu/encrypt_text_fragment.xml
@@ -2,6 +2,14 @@