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 @@ + + + + + + image/svg+xml + + + + + + + + + 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" + > + + + + +