Merge branch 'master' into v/decrypt-key-lookup

This commit is contained in:
Vincent Breitmoser
2015-11-15 03:16:46 +01:00
118 changed files with 2145 additions and 956 deletions

View File

@@ -9,10 +9,12 @@ sudo: false
# - ADB_INSTALL_TIMEOUT=8 # minutes (2 minutes by default) # - ADB_INSTALL_TIMEOUT=8 # minutes (2 minutes by default)
android: android:
components: components:
- build-tools-23.0.1
- build-tools-22.0.1 - build-tools-22.0.1
- build-tools-21.1.2 - build-tools-21.1.2
- build-tools-21.1.1 - build-tools-21.1.1
- build-tools-19.1.0 - build-tools-19.1.0
- android-23
- android-22 - android-22
- android-21 - android-21
- android-19 - android-19

View File

@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
width="24"
height="24"
viewBox="0 0 24 24"
id="svg2"
inkscape:version="0.48.5 r10040"
sodipodi:docname="ic_action_encrypt_paste.svg">
<metadata
id="metadata10">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs8" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="2558"
inkscape:window-height="1419"
id="namedview6"
showgrid="false"
inkscape:zoom="9.8333333"
inkscape:cx="0.10169504"
inkscape:cy="12"
inkscape:window-x="0"
inkscape:window-y="19"
inkscape:window-maximized="1"
inkscape:current-layer="svg2" />
<path
id="path3076"
d="m 17.363197,9.8980016 a 0.84079901,0.84079901 0 0 0 0.840798,-0.8408 c 0,-0.466643 -0.378359,-0.840799 -0.840798,-0.840799 a 0.84079901,0.84079901 0 0 0 -0.8408,0.840799 0.84079901,0.84079901 0 0 0 0.8408,0.8408 m 2.522396,-3.783596 a 0.84079901,0.84079901 0 0 1 0.8408,0.840799 V 11.159199 A 0.84079901,0.84079901 0 0 1 19.885593,12 H 14.840799 A 0.84079901,0.84079901 0 0 1 14,11.159199 V 6.9552046 c 0,-0.466644 0.37836,-0.840799 0.840799,-0.840799 h 0.4204 v -0.840799 a 2.1019975,2.1019975 0 0 1 2.101998,-2.101997 2.1019975,2.1019975 0 0 1 2.101997,2.101997 v 0.840799 h 0.420399 m -2.522396,-2.101998 a 1.2611986,1.2611986 0 0 0 -1.261198,1.261199 v 0.840799 h 2.522396 v -0.840799 a 1.2611986,1.2611986 0 0 0 -1.261198,-1.261199 z"
inkscape:connector-curvature="0"
style="fill:#000000" />
<path
id="path3053"
d="M 15.283513,19.502312 H 6.4607096 V 9.4191068 H 7.7211105 V 11.309707 H 14.023111 V 9.4191068 h 1.260402 M 10.87211,8.1587061 a 0.63020031,0.63020031 0 0 1 0.630201,0.6302004 0.63020031,0.63020031 0 0 1 -0.630201,0.6302003 0.63020031,0.63020031 0 0 1 -0.6302,-0.6302003 0.63020031,0.63020031 0 0 1 0.6302,-0.6302004 m 4.411405,0 h -2.63424 C 12.384592,7.427674 11.691372,6.8983055 10.87211,6.8983055 c -0.819259,0 -1.5124797,0.5293685 -1.7771628,1.2604006 H 6.4607096 A 1.2604005,1.2604005 0 0 0 5.200309,9.4191068 V 19.502312 a 1.2604005,1.2604005 0 0 0 1.2604006,1.260399 h 8.8228034 a 1.2604005,1.2604005 0 0 0 1.2604,-1.260399 V 9.4191068 a 1.2604005,1.2604005 0 0 0 -1.2604,-1.2604007 z"
inkscape:connector-curvature="0" />
</svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@@ -12,7 +12,7 @@
height="24" height="24"
viewBox="0 0 24 24" viewBox="0 0 24 24"
id="svg2" id="svg2"
inkscape:version="0.48.3.1 r9886" inkscape:version="0.48.5 r10040"
sodipodi:docname="ic_action_encrypt_save.svg"> sodipodi:docname="ic_action_encrypt_save.svg">
<metadata <metadata
id="metadata10"> id="metadata10">
@@ -41,8 +41,8 @@
id="namedview6" id="namedview6"
showgrid="false" showgrid="false"
inkscape:zoom="9.8333333" inkscape:zoom="9.8333333"
inkscape:cx="12.20339" inkscape:cx="-4.7288134"
inkscape:cy="12" inkscape:cy="11.949153"
inkscape:window-x="0" inkscape:window-x="0"
inkscape:window-y="19" inkscape:window-y="19"
inkscape:window-maximized="1" inkscape:window-maximized="1"
@@ -50,10 +50,11 @@
<path <path
inkscape:connector-curvature="0" inkscape:connector-curvature="0"
d="M 12.355932,11.847458 H 4.7288135 V 8.7966102 H 12.355932 M 10.067797,19.474576 a 2.2881356,2.2881356 0 0 1 -2.288136,-2.288135 2.2881356,2.2881356 0 0 1 2.288136,-2.288136 2.2881356,2.2881356 0 0 1 2.288135,2.288136 2.2881356,2.2881356 0 0 1 -2.288135,2.288135 M 13.881356,7.2711864 H 4.7288135 c -0.8466101,0 -1.5254237,0.6864407 -1.5254237,1.5254238 V 19.474576 A 1.5254237,1.5254237 0 0 0 4.7288135,21 H 15.40678 a 1.5254237,1.5254237 0 0 0 1.525424,-1.525424 V 10.322034 L 13.881356,7.2711864 z" d="M 12.355932,11.847458 H 4.7288135 V 8.7966102 H 12.355932 M 10.067797,19.474576 a 2.2881356,2.2881356 0 0 1 -2.288136,-2.288135 2.2881356,2.2881356 0 0 1 2.288136,-2.288136 2.2881356,2.2881356 0 0 1 2.288135,2.288136 2.2881356,2.2881356 0 0 1 -2.288135,2.288135 M 13.881356,7.2711864 H 4.7288135 c -0.8466101,0 -1.5254237,0.6864407 -1.5254237,1.5254238 V 19.474576 A 1.5254237,1.5254237 0 0 0 4.7288135,21 H 15.40678 a 1.5254237,1.5254237 0 0 0 1.525424,-1.525424 V 10.322034 L 13.881356,7.2711864 z"
id="path4-7" /> id="path4-7"
style="fill:#000000;fill-opacity:1" />
<path <path
id="path3076-1" id="path3076-1"
d="m 17.363197,9.8980016 a 0.84079901,0.84079901 0 0 0 0.840798,-0.8408 c 0,-0.466643 -0.378359,-0.840799 -0.840798,-0.840799 a 0.84079901,0.84079901 0 0 0 -0.8408,0.840799 0.84079901,0.84079901 0 0 0 0.8408,0.8408 m 2.522396,-3.783596 a 0.84079901,0.84079901 0 0 1 0.8408,0.840799 V 11.159199 A 0.84079901,0.84079901 0 0 1 19.885593,12 H 14.840799 A 0.84079901,0.84079901 0 0 1 14,11.159199 V 6.9552046 c 0,-0.466644 0.37836,-0.840799 0.840799,-0.840799 h 0.4204 v -0.840799 a 2.1019975,2.1019975 0 0 1 2.101998,-2.101997 2.1019975,2.1019975 0 0 1 2.101997,2.101997 v 0.840799 h 0.420399 m -2.522396,-2.101998 a 1.2611986,1.2611986 0 0 0 -1.261198,1.261199 v 0.840799 h 2.522396 v -0.840799 a 1.2611986,1.2611986 0 0 0 -1.261198,-1.261199 z" d="m 17.363197,9.8980016 a 0.84079901,0.84079901 0 0 0 0.840798,-0.8408 c 0,-0.466643 -0.378359,-0.840799 -0.840798,-0.840799 a 0.84079901,0.84079901 0 0 0 -0.8408,0.840799 0.84079901,0.84079901 0 0 0 0.8408,0.8408 m 2.522396,-3.783596 a 0.84079901,0.84079901 0 0 1 0.8408,0.840799 V 11.159199 A 0.84079901,0.84079901 0 0 1 19.885593,12 H 14.840799 A 0.84079901,0.84079901 0 0 1 14,11.159199 V 6.9552046 c 0,-0.466644 0.37836,-0.840799 0.840799,-0.840799 h 0.4204 v -0.840799 a 2.1019975,2.1019975 0 0 1 2.101998,-2.101997 2.1019975,2.1019975 0 0 1 2.101997,2.101997 v 0.840799 h 0.420399 m -2.522396,-2.101998 a 1.2611986,1.2611986 0 0 0 -1.261198,1.261199 v 0.840799 h 2.522396 v -0.840799 a 1.2611986,1.2611986 0 0 0 -1.261198,-1.261199 z"
inkscape:connector-curvature="0" inkscape:connector-curvature="0"
style="fill:#000000" /> style="fill:#000000;fill-opacity:1" />
</svg> </svg>

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M19,20H5V4H7V7H17V4H19M12,2A1,1 0 0,1 13,3A1,1 0 0,1 12,4A1,1 0 0,1 11,3A1,1 0 0,1 12,2M19,2H14.82C14.4,0.84 13.3,0 12,0C10.7,0 9.6,0.84 9.18,2H5A2,2 0 0,0 3,4V20A2,2 0 0,0 5,22H19A2,2 0 0,0 21,20V4A2,2 0 0,0 19,2Z" /></svg>

After

Width:  |  Height:  |  Size: 509 B

BIN
Graphics/twitter_header.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

View File

@@ -22,7 +22,7 @@ SRC_DIR=./drawables/
#inkscape -w 512 -h 512 -e "$PLAY_DIR/$NAME.png" $NAME.svg #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 do
echo $NAME echo $NAME
inkscape -w 24 -h 24 -e "$MDPI_DIR/${NAME}_24dp.png" "$SRC_DIR/$NAME.svg" inkscape -w 24 -h 24 -e "$MDPI_DIR/${NAME}_24dp.png" "$SRC_DIR/$NAME.svg"

View File

@@ -8,11 +8,11 @@ dependencies {
// NOTE: libraries are pinned to a specific build, see below // NOTE: libraries are pinned to a specific build, see below
// from local Android SDK // from local Android SDK
compile 'com.android.support:support-v4:22.2.1' compile 'com.android.support:support-v4:23.1.1'
compile 'com.android.support:appcompat-v7:22.2.1' compile 'com.android.support:appcompat-v7:23.1.1'
compile 'com.android.support:design:22.2.1' compile 'com.android.support:design:23.1.1'
compile 'com.android.support:recyclerview-v7:22.2.1' compile 'com.android.support:recyclerview-v7:23.1.1'
compile 'com.android.support:cardview-v7:22.2.1' compile 'com.android.support:cardview-v7:23.1.1'
// Unit tests in the local JVM with Robolectric // Unit tests in the local JVM with Robolectric
// https://developer.android.com/training/testing/unit-testing/local-unit-tests.html // 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 // http://www.vogella.com/tutorials/Robolectric/article.html
testCompile 'junit:junit:4.12' testCompile 'junit:junit:4.12'
testCompile 'org.robolectric:robolectric:3.0' testCompile 'org.robolectric:robolectric:3.0'
testCompile 'org.mockito:mockito-core:1.+' testCompile 'org.mockito:mockito-core:1.10.19'
// UI testing with Espresso // UI testing with Espresso
androidTestCompile 'com.android.support.test:runner:0.3' 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 // 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 // from https://github.com/googlesamples/android-testing/blob/master/build.gradle#L21
configurations.all { configurations.all {
resolutionStrategy.force 'com.android.support:support-annotations:22.2.0' resolutionStrategy.force 'com.android.support:support-annotations:23.1.1'
} }
// JCenter etc. // JCenter etc.
compile 'com.eftimoff:android-patternview:1.0.1@aar' compile 'com.eftimoff:android-patternview:1.0.3@aar'
compile 'com.journeyapps:zxing-android-embedded:2.3.0@aar' compile 'com.journeyapps:zxing-android-embedded:3.0.2@aar'
compile 'com.journeyapps:zxing-android-integration:2.3.0@aar'
compile 'com.google.zxing:core:3.2.0' compile 'com.google.zxing:core:3.2.0'
compile 'com.jpardogo.materialtabstrip:library:1.0.9' compile 'com.jpardogo.materialtabstrip:library:1.1.0'
compile 'com.getbase:floatingactionbutton:1.9.0' compile 'com.getbase:floatingactionbutton:1.10.1'
compile 'org.commonjava.googlecode.markdown4j:markdown4j:2.2-cj-1.0' compile 'org.commonjava.googlecode.markdown4j:markdown4j:2.2-cj-1.0'
compile 'org.ocpsoft.prettytime:prettytime:3.2.7.Final' compile 'org.ocpsoft.prettytime:prettytime:3.2.7.Final'
compile "com.splitwise:tokenautocomplete:1.3.3@aar" compile 'com.splitwise:tokenautocomplete:2.0.2@aar'
compile 'se.emilsjolander:stickylistheaders:2.6.0' compile 'se.emilsjolander:stickylistheaders:2.7.0'
compile 'org.sufficientlysecure:html-textview:1.2' compile 'org.sufficientlysecure:html-textview:1.3'
compile 'com.mikepenz:materialdrawer:3.0.9@aar' compile 'com.mikepenz:materialdrawer:4.4.2@aar'
compile 'com.mikepenz:iconics:1.0.2' compile 'com.mikepenz:materialize:0.2.7'
compile 'com.mikepenz.iconics:octicons-typeface:2.2.0@aar' compile 'com.mikepenz:iconics-core:1.7.9@aar'
compile 'com.mikepenz.iconics:meteocons-typeface:1.1.1@aar' compile 'com.mikepenz:google-material-typeface:1.2.0.1@aar'
compile 'com.mikepenz.iconics:community-material-typeface:1.0.0@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.nispok:snackbar:2.11.0'
compile 'com.squareup.okhttp:okhttp:2.5.0' compile 'com.squareup.okhttp:okhttp:2.5.0'
compile 'com.squareup.okhttp:okhttp-urlconnection: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! // Comment out the libs referenced as git submodules!
dependencyVerification { dependencyVerification {
verify = [ verify = [
'com.android.support:support-v4:c62f0d025dafa86f423f48df9185b0d89496adbc5f6a9be5a7c394d84cf91423', 'com.android.support:support-v4:5c7dceb6c824089fe80f502e5206264048ef8bffa4e8ddeab180b81723e79b7f',
'com.android.support:appcompat-v7:4b5ccba8c4557ef04f99aa0a80f8aa7d50f05f926a709010a54afd5c878d3618', 'com.android.support:appcompat-v7:0a8762214382b7e8d4b989b4ac10b5c846b957d767ccb7bccbc6be5afa885a82',
'com.android.support:design:58be3ca6a73789615f7ece0937d2f683b98b594bb90aa10565fa760fb10b07ee', 'com.android.support:design:41a9cd75ca78f25df5f573db7cedf8bb66beae00c330943923ba9f3e2051736d',
'com.android.support:recyclerview-v7:b0f530a5b14334d56ce0de85527ffe93ac419bc928e2884287ce1dddfedfb505', 'com.android.support:recyclerview-v7:7606373da0931a1e62588335465a0e390cd676c98117edab29220317495faefd',
'com.android.support:cardview-v7:2c2354761a4e20ba451ae903ab808f15c9acc8343b1e74001869c2d0a672c1fc', 'com.android.support:cardview-v7:5a5bc04a278662bfafdea5b11b2108a4b354dca6c68958b312f6f45cc5fe2e38',
'com.eftimoff:android-patternview:cec80e7265b8d8278b3c55b5fcdf551e4600ac2c8bf60d8dd76adca538af0b1e', 'com.eftimoff:android-patternview:2e7a2bbfb4fed229d4b5598aa4e69e45066fbea72c971d69461db7d916cb7ebc',
'com.journeyapps:zxing-android-embedded:702a4f58154dbd9baa80f66b6a15410f7a4d403f3e73b66537a8bfb156b4b718', 'com.journeyapps:zxing-android-embedded:561c5d94391342bb77689b8d32a320d085a11853f72afda1128d595b815ef563',
'com.journeyapps:zxing-android-integration:562737821b6d34c899b6fd2234ce0a8a31e02ff1fd7c59f6211961ce9767c7c8',
'com.google.zxing:core:7fe5a8ff437635a540e56317649937b768b454795ce999ed5f244f83373dee7b', 'com.google.zxing:core:7fe5a8ff437635a540e56317649937b768b454795ce999ed5f244f83373dee7b',
'com.jpardogo.materialtabstrip:library:c6ef812fba4f74be7dc4a905faa4c2908cba261a94c13d4f96d5e67e4aad4aaa', 'com.jpardogo.materialtabstrip:library:24d19232b319f8c73e25793432357919a7ed972186f57a3b2c9093ea74ad8311',
'com.getbase:floatingactionbutton:052aa2a94e49e5dccc97cb99f2add87e8698b84859f0e3ac181100c0bc7640ca', 'com.getbase:floatingactionbutton:3edefa511aac4d90794c7b0496aca59cff2eee1e32679247b4f85acbeee05240',
'org.commonjava.googlecode.markdown4j:markdown4j:e952e825d29e1317d96f79f346bfb6786c7c5eef50bd26e54a80823704b62e13', 'org.commonjava.googlecode.markdown4j:markdown4j:e952e825d29e1317d96f79f346bfb6786c7c5eef50bd26e54a80823704b62e13',
'org.ocpsoft.prettytime:prettytime:a6bc2641b3ab7873df604b77b6680c75b86d98e78afefb367940972f925591b5', 'org.ocpsoft.prettytime:prettytime:a6bc2641b3ab7873df604b77b6680c75b86d98e78afefb367940972f925591b5',
'com.splitwise:tokenautocomplete:20bee71cc59b3828eb000b684d46ddf738efd56b8fee453a509cd16fda42c8cb', 'com.splitwise:tokenautocomplete:2fc238424130b42155b5f2e39799a90bbbd13b148850afbe534ab08bb913c7f7',
'se.emilsjolander:stickylistheaders:8c05981ec5725be33f7cee5e68c13f3db49cd5c75f1aaeb04024920b1ef96ad4', 'se.emilsjolander:stickylistheaders:a08ca948aa6b220f09d82f16bbbac395f6b78897e9eeac6a9f0b0ba755928eeb',
'com.mikepenz:materialdrawer:70c3efb3842461db41df6a918ea93969a7da21e63c092be838b153e5a47a17bf', 'org.sufficientlysecure:html-textview:39048e35894e582adada388e6c00631803283f8defed8e07ad58a5f284f272ee',
'org.sufficientlysecure:html-textview:1d3bed31ef837437154de8d2362a0e6b0e59b6c3535d87ee48c2fab12c84f9bb', 'com.mikepenz:materialize:db365f859084048ac4e9cc4254642593dbb1ae9ce25c8fc26c93e2a5fadb3480',
'com.mikepenz.iconics:octicons-typeface:67ed7d456a9ce5f5307b85f955797bfb3dd674e2f6defb31c6b8bbe2ede290be', 'com.mikepenz:materialdrawer:fe9726e0f045eb3fe63832aa5383d9e2c7bcd8b87a6f26478aba1c330e9d36fe',
'com.mikepenz:iconics:c1a02203d8e0d638959463c00af3ab9096e0a7c1ad5928762eb10ef5ce8a63cd', 'com.mikepenz:google-material-typeface:a8319333a7f7ca369b9b5c62913f96787d934e312acefa8c9a5fcefd394fc6ee',
'com.mikepenz.iconics:community-material-typeface:f1c5afee5f0f10d66beb3ed0df977246a02a9c46de4e05d7c0264bcde53b6b7f', 'com.mikepenz:iconics-core:e1ba25442c1645b7adfb7d101871c26ed64a6c5b892e9abee8d4d2a80d948d9e',
'com.mikepenz.iconics:meteocons-typeface:39a8a9e70cd8287cdb119af57a672a41dd09240dba6697f5a0dbda1ccc33298b', 'com.mikepenz:community-material-typeface:520f1065730a1171763696ac9c4e770fedbbcf1a8dad6eb1028ba29489e1a2ce',
'com.mikepenz:fontawesome-typeface:8c58117eb42efe301a170049336f7838af7559d84b0cc9a2bd7aca8b130f0a50',
'com.squareup.okhttp:okhttp:1cc716e29539adcda677949508162796daffedb4794cbf947a6f65e696f0381c', 'com.squareup.okhttp:okhttp:1cc716e29539adcda677949508162796daffedb4794cbf947a6f65e696f0381c',
'com.nispok:snackbar:46b5eb9d630d329e13c2ce00ee9fb115ffb66c23c72cff32ee97eedd76824c6f', 'com.nispok:snackbar:46b5eb9d630d329e13c2ce00ee9fb115ffb66c23c72cff32ee97eedd76824c6f',
'org.apache.james:apache-mime4j-core:4d7434c68f94b81a253c12f28e6bbb4d6239c361d6086a46e22e594bb43ac660', 'org.apache.james:apache-mime4j-core:4d7434c68f94b81a253c12f28e6bbb4d6239c361d6086a46e22e594bb43ac660',
'com.squareup.okhttp:okhttp-urlconnection:79ec6f4e79e683105e87fe83278a531c693e538d30e3b9237000ce7c94fcb2cf', 'com.squareup.okhttp:okhttp-urlconnection:79ec6f4e79e683105e87fe83278a531c693e538d30e3b9237000ce7c94fcb2cf',
'org.thoughtcrime.ssl.pinning:AndroidPinning:afa1d74e699257fa75cb109ff29bac50726ef269c6e306bdeffe8223cee06ef4', 'org.thoughtcrime.ssl.pinning:AndroidPinning:afa1d74e699257fa75cb109ff29bac50726ef269c6e306bdeffe8223cee06ef4',
'org.apache.james:apache-mime4j-dom:7e6b06ee164a1c21b7e477249ea0b74a18fddce44764e5764085f58dd8c34633', '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', 'com.cocosw:bottomsheet:871f5f4d6c10936569caf3528271efd77594a67aa5511765c96d7096c9b05f96',
// 'OpenKeychain.extern.spongycastle:core:6006a83fa427f4e5c8c93458176ab5e3b54d8dba7942171cb76cb134fc574c58', // 'com.madgag.spongycastle:core:d898b5b81ce9d456c65a3a2fe0b0be9b74e366aabe4ee1d13499a865cd20ee19',
// 'OpenKeychain.extern.openkeychain-api-lib:openkeychain-intents:8582442aa26e13c5a4bdf3588a22cb94b2fa5de6c79b84244fb575aa401fc330', // 'OpenKeychain.extern.openkeychain-api-lib:openkeychain-intents:c25d0c05cc129e2975161f8454350a8868dbe1ffca8583e900935cb0b38db842',
// 'OpenKeychain.extern.spongycastle:pg:0fd64a60311c5557f230bec9b2b162c9e6e690ccc83ac6b5af6a8d616309da98', // 'com.madgag.spongycastle:pg:abd30d5a3c6ab6edbf7e2b60de3dae865bb5b5e4b41925ea8ac985e2a7fce4a0',
// 'OpenKeychain.extern.spongycastle:pkix:2348474aa27cb0461a368191d4d8fe7479a212b6365b177da131f4efa4c57f24', // 'com.madgag.spongycastle:pkix:c5fd572191d31d2b05d7143d9e22d74ee3e8a43552c26d31114a27022b7a06ce',
// 'OpenKeychain.extern.spongycastle:prov:85c9ed6e24c5c7e5f7ff7c22d367bb553d020693350bd1c75555e6895311bb69', // 'com.madgag.spongycastle:prov:7a28f314e20683281254f92124f137d48173ea1dc3364a709d7cc66a5e46ec4e',
// 'OpenKeychain.extern.safeslinger-exchange:safeslinger-exchange:274b71f8a1c383fb506342fd0f614b4a0cdb25517b5b2a1dfef9a4a2575477ed', // 'OpenKeychain.extern.safeslinger-exchange:safeslinger-exchange:75a3d23eff4d5c14fdc8912b5d593bf340f07b833ceabacbcd60a8e83b5b8b79',
'com.android.support:support-annotations:beac5cae60bdb597df9af9c916f785c2f71f8c8ae4be9a32d4298dea85496a42', 'com.android.support:support-annotations:f347a35b9748a4103b39a6714a77e2100f488d623fd6268e259c177b200e9d82',
// 'OpenKeychain.extern:minidns:25e351fa4145e2a9b0a76658c48619b307f71432db7492e9e8a6b34aa2e9bdcf', // 'OpenKeychain.extern:minidns:0084c81e30a4b06edac9b00689eeaa3cbbb9ea3b26aaa4fad205bb660ea0fcdb',
// 'OpenKeychain.extern.KeybaseLib:Lib:79c78c1054b58200028211e21f2c89012dc4a1eafdb00cc99a5ce1f61ad16937', // 'OpenKeychain.extern.KeybaseLib:Lib:c11785bf613f3fad8bf1670ef9fce1a10027cefbf2b3a67d487c6b8964f0e05a',
'com.squareup.okio:okio:114bdc1f47338a68bcbc95abf2f5cdc72beeec91812f2fcd7b521c1937876266', 'com.squareup.okio:okio:114bdc1f47338a68bcbc95abf2f5cdc72beeec91812f2fcd7b521c1937876266',
] ]
} }
@@ -128,11 +128,14 @@ android {
compileSdkVersion rootProject.ext.compileSdkVersion compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion buildToolsVersion rootProject.ext.buildToolsVersion
// TODO: remove org.apache dependencies in LinkedTokenResource etc.
useLibrary 'org.apache.http.legacy'
defaultConfig { defaultConfig {
minSdkVersion 15 minSdkVersion 15
targetSdkVersion 22 targetSdkVersion 23
versionCode 36000 versionCode 36100
versionName "3.6" versionName "3.6.1"
applicationId "org.sufficientlysecure.keychain" applicationId "org.sufficientlysecure.keychain"
// the androidjunitrunner is broken regarding coverage, see here: // the androidjunitrunner is broken regarding coverage, see here:
// https://code.google.com/p/android/issues/detail?id=170607 // 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", "account_type", "org.sufficientlysecure.keychain.account"
resValue "string", "provider_content_authority", "org.sufficientlysecure.keychain.provider" resValue "string", "provider_content_authority", "org.sufficientlysecure.keychain.provider"
// Github API ID and secret are read from gradle.properties (not in git!) // Github API
// must use double escaping in gradle.properties! For example: buildConfigField "String", "GITHUB_CLIENT_ID", "\"c942cd81844d94e7e41b\""
// githubClientId="\\"7a011b66275f244d3f21\\"" buildConfigField "String", "GITHUB_CLIENT_SECRET", "\"f1dd17e70a0614abbd9310b00a310e23c6c8edff\""
// 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
}
} }
debug { debug {
@@ -186,9 +180,9 @@ android {
resValue "string", "account_type", "org.sufficientlysecure.keychain.debug.account" resValue "string", "account_type", "org.sufficientlysecure.keychain.debug.account"
resValue "string", "provider_content_authority", "org.sufficientlysecure.keychain.debug.provider" resValue "string", "provider_content_authority", "org.sufficientlysecure.keychain.debug.provider"
// Github API for debug build only // Github API
buildConfigField "String", "GITHUB_CLIENT_ID", "\"7a011b66275f244d3f21\"" buildConfigField "String", "GITHUB_CLIENT_ID", "\"c942cd81844d94e7e41b\""
buildConfigField "String", "GITHUB_CLIENT_SECRET", "\"eaced8a6655719d8c6848396de97b3f5d7a89fec\"" buildConfigField "String", "GITHUB_CLIENT_SECRET", "\"f1dd17e70a0614abbd9310b00a310e23c6c8edff\""
// Enable code coverage (Jacoco) // Enable code coverage (Jacoco)
testCoverageEnabled true testCoverageEnabled true

View File

@@ -53,19 +53,32 @@
android:name="${applicationId}.WRITE_TEMPORARY_STORAGE" android:name="${applicationId}.WRITE_TEMPORARY_STORAGE"
android:protectionLevel="signature" /> android:protectionLevel="signature" />
<uses-permission android:name="android.permission.INTERNET" /> <!-- CAMERA permission requested by ZXing library -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.NFC" /> <!-- contact group -->
<uses-permission android:name="android.permission.GET_ACCOUNTS" /> <uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" /> <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" /> <uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
<uses-permission android:name="android.permission.READ_CONTACTS" /> <uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" /> <uses-permission android:name="android.permission.WRITE_CONTACTS" />
<uses-permission android:name="android.permission.READ_PROFILE" /> <uses-permission android:name="android.permission.READ_PROFILE" />
<uses-permission android:name="android.permission.WRITE_PROFILE" /> <uses-permission android:name="android.permission.WRITE_PROFILE" />
<!-- storage group -->
<!--
No need on >= Android 4.4 for WRITE_EXTERNAL_STORAGE, because we use Storage Access Framework,
but better not use maxSdkVersion as it causes problems: https://code.google.com/p/android/issues/detail?id=63895
-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- READ_EXTERNAL_STORAGE is now dangerous on Android >= 6 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- other group (for free) -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.NFC" />
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
<!-- android:allowBackup="false": Don't allow backup over adb backup or other apps! --> <!-- android:allowBackup="false": Don't allow backup over adb backup or other apps! -->
<application <application
android:name=".KeychainApplication" android:name=".KeychainApplication"
@@ -97,12 +110,12 @@
android:value=".ui.MainActivity" /> android:value=".ui.MainActivity" />
<!-- Connect with YubiKeys. This Activity will automatically show/import/create YubiKeys --> <!-- Connect with YubiKeys. This Activity will automatically show/import/create YubiKeys -->
<intent-filter android:label="@string/app_name"> <intent-filter android:label="@string/app_name">
<action android:name="android.nfc.action.NDEF_DISCOVERED"/> <action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.DEFAULT" />
<data <data
android:scheme="https"
android:host="my.yubico.com" android:host="my.yubico.com"
android:pathPrefix="/neo"/> android:pathPrefix="/neo"
android:scheme="https" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity <activity
@@ -114,9 +127,7 @@
android:name=".ui.linked.LinkedIdWizard" android:name=".ui.linked.LinkedIdWizard"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard" android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/title_linked_create" android:label="@string/title_linked_create"
android:parentActivityName=".ui.ViewKeyActivity" android:parentActivityName=".ui.ViewKeyActivity"></activity>
>
</activity>
<activity <activity
android:name=".ui.QrCodeViewActivity" android:name=".ui.QrCodeViewActivity"
android:label="@string/share_qr_code_dialog_title" /> android:label="@string/share_qr_code_dialog_title" />
@@ -210,6 +221,12 @@
<data android:mimeType="text/*" /> <data android:mimeType="text/*" />
<data android:mimeType="message/*" /> <data android:mimeType="message/*" />
</intent-filter> </intent-filter>
<!-- Android 6 Floating Action Mode -->
<intent-filter>
<action android:name="android.intent.action.PROCESS_TEXT" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
</activity> </activity>
<activity <activity
android:name=".ui.DisplayTextActivity" android:name=".ui.DisplayTextActivity"
@@ -466,7 +483,7 @@
android:name=".ui.ImportKeysProxyActivity" android:name=".ui.ImportKeysProxyActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard" android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/app_name" android:label="@string/app_name"
android:theme="@android:style/Theme.NoDisplay" android:theme="@style/Theme.Keychain.Transparent"
android:windowSoftInputMode="stateHidden"> android:windowSoftInputMode="stateHidden">
<!-- VIEW with fingerprint scheme: <!-- VIEW with fingerprint scheme:
@@ -499,7 +516,7 @@
<data android:mimeType="application/pgp-keys" /> <data android:mimeType="application/pgp-keys" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity android:name=".ui.QrCodeCaptureActivity" />
<activity <activity
android:name=".ui.ImportKeysActivity" android:name=".ui.ImportKeysActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard" android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
@@ -691,19 +708,19 @@
android:label="@string/title_log_display" /> android:label="@string/title_log_display" />
<activity <activity
android:name=".ui.ConsolidateDialogActivity" android:name=".ui.ConsolidateDialogActivity"
android:theme="@android:style/Theme.NoDisplay" /> android:theme="@style/Theme.Keychain.Transparent" />
<activity <activity
android:name=".ui.PassphraseDialogActivity" android:name=".ui.PassphraseDialogActivity"
android:theme="@android:style/Theme.NoDisplay" /> android:theme="@style/Theme.Keychain.Transparent" />
<activity <activity
android:name=".ui.RetryUploadDialogActivity" android:name=".ui.RetryUploadDialogActivity"
android:theme="@android:style/Theme.NoDisplay" /> android:theme="@style/Theme.Keychain.Transparent" />
<activity <activity
android:name=".ui.DeleteKeyDialogActivity" android:name=".ui.DeleteKeyDialogActivity"
android:theme="@android:style/Theme.NoDisplay" /> android:theme="@style/Theme.Keychain.Transparent" />
<activity <activity
android:name=".ui.OrbotRequiredDialogActivity" android:name=".ui.OrbotRequiredDialogActivity"
android:theme="@android:style/Theme.NoDisplay" /> android:theme="@style/Theme.Keychain.Transparent" />
<!-- <!--
NOTE: singleTop is set to get NFC foreground dispatch to work. NOTE: singleTop is set to get NFC foreground dispatch to work.
Then, all NFC intents will be broadcasted to onNewIntent() of this activity! Then, all NFC intents will be broadcasted to onNewIntent() of this activity!
@@ -713,10 +730,10 @@
--> -->
<activity <activity
android:name=".ui.NfcOperationActivity" android:name=".ui.NfcOperationActivity"
android:theme="@style/Theme.Keychain.Light.Dialog"
android:allowTaskReparenting="true" android:allowTaskReparenting="true"
android:launchMode="singleTop" android:launchMode="singleTop"
android:taskAffinity=":Nfc" /> android:taskAffinity=":Nfc"
android:theme="@style/Theme.Keychain.Light.Dialog" />
<activity <activity
android:name=".ui.HelpActivity" android:name=".ui.HelpActivity"
@@ -741,7 +758,7 @@
android:name=".provider.KeychainProvider" android:name=".provider.KeychainProvider"
android:authorities="${applicationId}.provider" android:authorities="${applicationId}.provider"
android:exported="false" android:exported="false"
android:label="@string/keyserver_sync_settings_title"/> android:label="@string/keyserver_sync_settings_title" />
<!-- Internal classes of the remote APIs (not exported) --> <!-- Internal classes of the remote APIs (not exported) -->
<activity <activity

View File

@@ -0,0 +1,24 @@
package org.spongycastle.openpgp.jcajce;
import java.io.IOException;
import java.io.InputStream;
import org.spongycastle.openpgp.PGPMarker;
public class JcaSkipMarkerPGPObjectFactory extends JcaPGPObjectFactory {
public JcaSkipMarkerPGPObjectFactory(InputStream in) {
super(in);
}
@Override
public Object nextObject() throws IOException {
Object o = super.nextObject();
while (o instanceof PGPMarker) {
o = super.nextObject();
}
return o;
}
}

View File

@@ -28,6 +28,7 @@ public final class Constants {
public static final boolean DEBUG = BuildConfig.DEBUG; public static final boolean DEBUG = BuildConfig.DEBUG;
public static final boolean DEBUG_LOG_DB_QUERIES = false; public static final boolean DEBUG_LOG_DB_QUERIES = false;
public static final boolean DEBUG_EXPLAIN_QUERIES = false;
public static final boolean DEBUG_SYNC_REMOVE_CONTACTS = false; public static final boolean DEBUG_SYNC_REMOVE_CONTACTS = false;
public static final boolean DEBUG_KEYSERVER_SYNC = false; public static final boolean DEBUG_KEYSERVER_SYNC = false;

View File

@@ -24,6 +24,9 @@ import java.net.Proxy;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Vector; import java.util.Vector;
import android.support.annotation.NonNull;
/** /**
* Search two or more types of server for online keys. * Search two or more types of server for online keys.
*/ */
@@ -31,8 +34,8 @@ public class CloudSearch {
private final static long SECONDS = 1000; private final static long SECONDS = 1000;
public static ArrayList<ImportKeysListEntry> search(final String query, Preferences.CloudSearchPrefs cloudPrefs, public static ArrayList<ImportKeysListEntry> search(
final Proxy proxy) @NonNull final String query, Preferences.CloudSearchPrefs cloudPrefs, @NonNull Proxy proxy)
throws Keyserver.CloudSearchFailureException { throws Keyserver.CloudSearchFailureException {
final ArrayList<Keyserver> servers = new ArrayList<>(); final ArrayList<Keyserver> servers = new ArrayList<>();
@@ -40,10 +43,10 @@ public class CloudSearch {
final Vector<Keyserver.CloudSearchFailureException> problems = new Vector<>(); final Vector<Keyserver.CloudSearchFailureException> problems = new Vector<>();
if (cloudPrefs.searchKeyserver) { if (cloudPrefs.searchKeyserver) {
servers.add(new HkpKeyserver(cloudPrefs.keyserver)); servers.add(new HkpKeyserver(cloudPrefs.keyserver, proxy));
} }
if (cloudPrefs.searchKeybase) { if (cloudPrefs.searchKeybase) {
servers.add(new KeybaseKeyserver()); servers.add(new KeybaseKeyserver(proxy));
} }
final ImportKeysList results = new ImportKeysList(servers.size()); final ImportKeysList results = new ImportKeysList(servers.size());
@@ -53,7 +56,7 @@ public class CloudSearch {
@Override @Override
public void run() { public void run() {
try { try {
results.addAll(keyserver.search(query, proxy)); results.addAll(keyserver.search(query));
} catch (Keyserver.CloudSearchFailureException e) { } catch (Keyserver.CloudSearchFailureException e) {
problems.add(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. // wait for either all the searches to come back, or 10 seconds. If using proxy, wait 30 seconds.
synchronized (results) { synchronized (results) {
try { try {
if (proxy != null) { if (proxy == Proxy.NO_PROXY) {
results.wait(30 * SECONDS); results.wait(30 * SECONDS);
} else { } else {
results.wait(10 * SECONDS); results.wait(10 * SECONDS);

View File

@@ -46,6 +46,8 @@ import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import android.support.annotation.NonNull;
import de.measite.minidns.Client; import de.measite.minidns.Client;
import de.measite.minidns.Question; import de.measite.minidns.Question;
import de.measite.minidns.Record; import de.measite.minidns.Record;
@@ -74,6 +76,7 @@ public class HkpKeyserver extends Keyserver {
private String mHost; private String mHost;
private short mPort; private short mPort;
private Proxy mProxy;
private boolean mSecure; private boolean mSecure;
/** /**
@@ -150,17 +153,17 @@ public class HkpKeyserver extends Keyserver {
* connect using {@link #PORT_DEFAULT}. However, port may be specified after colon * connect using {@link #PORT_DEFAULT}. However, port may be specified after colon
* ("<code>hostname:port</code>", eg. "<code>p80.pool.sks-keyservers.net:80</code>"). * ("<code>hostname:port</code>", eg. "<code>p80.pool.sks-keyservers.net:80</code>").
*/ */
public HkpKeyserver(String hostAndPort) { public HkpKeyserver(String hostAndPort, Proxy proxy) {
String host = hostAndPort; String host = hostAndPort;
short port = PORT_DEFAULT; short port = PORT_DEFAULT;
boolean secure = false; boolean secure = false;
String[] parts = hostAndPort.split(":"); String[] parts = hostAndPort.split(":");
if (parts.length > 1) { 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].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; secure = true;
port = PORT_DEFAULT_HKPS; 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"); throw new IllegalArgumentException("Protocol " + parts[0] + " is unknown");
} }
host = parts[1]; host = parts[1];
@@ -177,16 +180,18 @@ public class HkpKeyserver extends Keyserver {
} }
mHost = host; mHost = host;
mPort = port; mPort = port;
mProxy = proxy;
mSecure = secure; mSecure = secure;
} }
public HkpKeyserver(String host, short port) { public HkpKeyserver(String host, short port, Proxy proxy) {
this(host, port, false); 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; mHost = host;
mPort = port; mPort = port;
mProxy = proxy;
mSecure = secure; mSecure = secure;
} }
@@ -226,7 +231,7 @@ public class HkpKeyserver extends Keyserver {
return client; return client;
} }
private String query(String request, Proxy proxy) throws QueryFailedException, HttpError { private String query(String request, @NonNull Proxy proxy) throws QueryFailedException, HttpError {
try { try {
URL url = new URL(getUrlPrefix() + mHost + ":" + mPort + request); URL url = new URL(getUrlPrefix() + mHost + ":" + mPort + request);
Log.d(Constants.TAG, "hkp keyserver query: " + url + " Proxy: " + proxy); Log.d(Constants.TAG, "hkp keyserver query: " + url + " Proxy: " + proxy);
@@ -243,7 +248,7 @@ public class HkpKeyserver extends Keyserver {
} catch (IOException e) { } catch (IOException e) {
Log.e(Constants.TAG, "IOException at HkpKeyserver", e); Log.e(Constants.TAG, "IOException at HkpKeyserver", e);
throw new QueryFailedException("Keyserver '" + mHost + "' is unavailable. Check your Internet connection!" + 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! * Results are sorted by creation date of key!
*/ */
@Override @Override
public ArrayList<ImportKeysListEntry> search(String query, Proxy proxy) throws QueryFailedException, public ArrayList<ImportKeysListEntry> search(String query) throws QueryFailedException,
QueryNeedsRepairException { QueryNeedsRepairException {
ArrayList<ImportKeysListEntry> results = new ArrayList<>(); ArrayList<ImportKeysListEntry> results = new ArrayList<>();
@@ -269,7 +274,7 @@ public class HkpKeyserver extends Keyserver {
String data; String data;
try { try {
data = query(request, proxy); data = query(request, mProxy);
} catch (HttpError e) { } catch (HttpError e) {
if (e.getData() != null) { if (e.getData() != null) {
Log.d(Constants.TAG, "returned error data: " + e.getData().toLowerCase(Locale.ENGLISH)); Log.d(Constants.TAG, "returned error data: " + e.getData().toLowerCase(Locale.ENGLISH));
@@ -373,12 +378,12 @@ public class HkpKeyserver extends Keyserver {
} }
@Override @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; 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; String data;
try { try {
data = query(request, proxy); data = query(request, mProxy);
} catch (HttpError httpError) { } catch (HttpError httpError) {
Log.d(Constants.TAG, "Failed to get key at HkpKeyserver", httpError); Log.d(Constants.TAG, "Failed to get key at HkpKeyserver", httpError);
throw new QueryFailedException("not found"); throw new QueryFailedException("not found");
@@ -394,7 +399,7 @@ public class HkpKeyserver extends Keyserver {
} }
@Override @Override
public void add(String armoredKey, Proxy proxy) throws AddKeyException { public void add(String armoredKey) throws AddKeyException {
try { try {
String path = "/pks/add"; String path = "/pks/add";
String params; String params;
@@ -405,7 +410,7 @@ public class HkpKeyserver extends Keyserver {
} }
URL url = new URL(getUrlPrefix() + mHost + ":" + mPort + path); 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); Log.d(Constants.TAG, "params: " + params);
RequestBody body = RequestBody.create(MediaType.parse("application/x-www-form-urlencoded"), params); RequestBody body = RequestBody.create(MediaType.parse("application/x-www-form-urlencoded"), params);
@@ -417,7 +422,7 @@ public class HkpKeyserver extends Keyserver {
.post(body) .post(body)
.build(); .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, "response code: " + response.code());
Log.d(Constants.TAG, "answer: " + response.body().string()); Log.d(Constants.TAG, "answer: " + response.body().string());
@@ -434,16 +439,15 @@ public class HkpKeyserver extends Keyserver {
@Override @Override
public String toString() { public String toString() {
return mHost + ":" + mPort; return getUrlPrefix() + mHost + ":" + mPort;
} }
/** /**
* Tries to find a server responsible for a given domain * Tries to find a server responsible for a given domain
* *
* @return A responsible Keyserver or null if not found. * @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 { try {
Record[] records = new Client().query(new Question("_hkp._tcp." + domain, Record.TYPE.SRV)).getAnswers(); Record[] records = new Client().query(new Question("_hkp._tcp." + domain, Record.TYPE.SRV)).getAnswers();
if (records.length > 0) { if (records.length > 0) {
@@ -458,7 +462,7 @@ public class HkpKeyserver extends Keyserver {
Record record = records[0]; // This is our best choice Record record = records[0]; // This is our best choice
if (record.getPayload().getType() == Record.TYPE.SRV) { if (record.getPayload().getType() == Record.TYPE.SRV) {
return new HkpKeyserver(((SRV) record.getPayload()).getName(), return new HkpKeyserver(((SRV) record.getPayload()).getName(),
(short) ((SRV) record.getPayload()).getPort()); (short) ((SRV) record.getPayload()).getPort(), proxy);
} }
} }
} catch (Exception ignored) { } catch (Exception ignored) {

View File

@@ -33,10 +33,15 @@ import java.util.List;
public class KeybaseKeyserver extends Keyserver { public class KeybaseKeyserver extends Keyserver {
public static final String ORIGIN = "keybase:keybase.io"; public static final String ORIGIN = "keybase:keybase.io";
private String mQuery;
Proxy mProxy;
public KeybaseKeyserver(Proxy proxy) {
mProxy = proxy;
}
@Override @Override
public ArrayList<ImportKeysListEntry> search(String query, Proxy proxy) throws QueryFailedException, public ArrayList<ImportKeysListEntry> search(String query) throws QueryFailedException,
QueryNeedsRepairException { QueryNeedsRepairException {
ArrayList<ImportKeysListEntry> results = new ArrayList<>(); ArrayList<ImportKeysListEntry> results = new ArrayList<>();
@@ -47,14 +52,13 @@ public class KeybaseKeyserver extends Keyserver {
if (query.isEmpty()) { if (query.isEmpty()) {
throw new QueryTooShortException(); throw new QueryTooShortException();
} }
mQuery = query;
try { try {
KeybaseQuery keybaseQuery = new KeybaseQuery(new OkHttpKeybaseClient()); KeybaseQuery keybaseQuery = new KeybaseQuery(new OkHttpKeybaseClient());
keybaseQuery.setProxy(proxy); keybaseQuery.setProxy(mProxy);
Iterable<Match> matches = keybaseQuery.search(query); Iterable<Match> matches = keybaseQuery.search(query);
for (Match match : matches) { for (Match match : matches) {
results.add(makeEntry(match)); results.add(makeEntry(match, query));
} }
} catch (KeybaseException e) { } catch (KeybaseException e) {
Log.e(Constants.TAG, "keybase result parsing error", e); Log.e(Constants.TAG, "keybase result parsing error", e);
@@ -64,9 +68,9 @@ public class KeybaseKeyserver extends Keyserver {
return results; return results;
} }
private ImportKeysListEntry makeEntry(Match match) throws KeybaseException { private ImportKeysListEntry makeEntry(Match match, String query) throws KeybaseException {
final ImportKeysListEntry entry = new ImportKeysListEntry(); final ImportKeysListEntry entry = new ImportKeysListEntry();
entry.setQuery(mQuery); entry.setQuery(query);
entry.addOrigin(ORIGIN); entry.addOrigin(ORIGIN);
entry.setRevoked(false); // keybase doesnt say anything about revoked keys entry.setRevoked(false); // keybase doesnt say anything about revoked keys
@@ -102,10 +106,10 @@ public class KeybaseKeyserver extends Keyserver {
} }
@Override @Override
public String get(String id, Proxy proxy) throws QueryFailedException { public String get(String id) throws QueryFailedException {
try { try {
KeybaseQuery keybaseQuery = new KeybaseQuery(new OkHttpKeybaseClient()); KeybaseQuery keybaseQuery = new KeybaseQuery(new OkHttpKeybaseClient());
keybaseQuery.setProxy(proxy); keybaseQuery.setProxy(mProxy);
return User.keyForUsername(keybaseQuery, id); return User.keyForUsername(keybaseQuery, id);
} catch (KeybaseException e) { } catch (KeybaseException e) {
throw new QueryFailedException(e.getMessage()); throw new QueryFailedException(e.getMessage());
@@ -113,7 +117,7 @@ public class KeybaseKeyserver extends Keyserver {
} }
@Override @Override
public void add(String armoredKey, Proxy proxy) throws AddKeyException { public void add(String armoredKey) throws AddKeyException {
throw new AddKeyException(); throw new AddKeyException();
} }
} }

View File

@@ -69,12 +69,12 @@ public abstract class Keyserver {
private static final long serialVersionUID = -507574859137295530L; private static final long serialVersionUID = -507574859137295530L;
} }
public abstract List<ImportKeysListEntry> search(String query, Proxy proxy) public abstract List<ImportKeysListEntry> search(String query)
throws QueryFailedException, QueryNeedsRepairException; 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 { public static String readAll(InputStream in, String encoding) throws IOException {
ByteArrayOutputStream raw = new ByteArrayOutputStream(); ByteArrayOutputStream raw = new ByteArrayOutputStream();

View File

@@ -1,5 +1,7 @@
package org.sufficientlysecure.keychain.linked; package org.sufficientlysecure.keychain.linked;
import android.content.Context;
import org.apache.http.HttpEntity; import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient; 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.apache.http.params.BasicHttpParams;
import org.json.JSONException; import org.json.JSONException;
import org.sufficientlysecure.keychain.Constants; 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.GenericHttpsResource;
import org.sufficientlysecure.keychain.linked.resources.GithubResource; import org.sufficientlysecure.keychain.linked.resources.GithubResource;
import org.sufficientlysecure.keychain.linked.resources.TwitterResource; import org.sufficientlysecure.keychain.linked.resources.TwitterResource;
@@ -32,8 +33,6 @@ import java.util.Set;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import android.content.Context;
public abstract class LinkedTokenResource extends LinkedResource { public abstract class LinkedTokenResource extends LinkedResource {

View File

@@ -252,7 +252,7 @@ public class BackupOperation extends BaseOperation<BackupKeyringParcel> {
ring.encode(arOutStream); ring.encode(arOutStream);
} catch (PgpGeneralException e) { } catch (PgpGeneralException e) {
log.add(LogType.MSG_BACKUP_ERROR_KEY, 2); log.add(LogType.MSG_UPLOAD_ERROR_IO, 2);
} finally { } finally {
if (arOutStream != null) { if (arOutStream != null) {
arOutStream.close(); arOutStream.close();
@@ -273,7 +273,7 @@ public class BackupOperation extends BaseOperation<BackupKeyringParcel> {
ring.encode(arOutStream); ring.encode(arOutStream);
} catch (PgpGeneralException e) { } catch (PgpGeneralException e) {
log.add(LogType.MSG_BACKUP_ERROR_KEY, 2); log.add(LogType.MSG_UPLOAD_ERROR_IO, 2);
} finally { } finally {
if (arOutStream != null) { if (arOutStream != null) {
arOutStream.close(); arOutStream.close();

View File

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

View File

@@ -208,7 +208,7 @@ public class CertifyOperation extends BaseOperation<CertifyActionsParcel> {
// these variables are used inside the following loop, but they need to be created only once // these variables are used inside the following loop, but they need to be created only once
UploadOperation uploadOperation = null; UploadOperation uploadOperation = null;
if (parcel.keyServerUri != 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 // Write all certified keys into the database

View File

@@ -26,7 +26,6 @@ import android.support.annotation.NonNull;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.EditKeyResult; 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.LogType;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult; import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult;
@@ -73,7 +72,7 @@ public class EditKeyOperation extends BaseOperation<SaveKeyringParcel> {
* @return the result of the operation * @return the result of the operation
*/ */
@NonNull @NonNull
public InputPendingResult execute(SaveKeyringParcel saveParcel, CryptoInputParcel cryptoInput) { public EditKeyResult execute(SaveKeyringParcel saveParcel, CryptoInputParcel cryptoInput) {
OperationLog log = new OperationLog(); OperationLog log = new OperationLog();
log.add(LogType.MSG_ED, 0); log.add(LogType.MSG_ED, 0);
@@ -100,7 +99,8 @@ public class EditKeyOperation extends BaseOperation<SaveKeyringParcel> {
modifyResult = keyOperations.modifySecretKeyRing(secRing, cryptoInput, saveParcel); modifyResult = keyOperations.modifySecretKeyRing(secRing, cryptoInput, saveParcel);
if (modifyResult.isPending()) { if (modifyResult.isPending()) {
return modifyResult; log.add(modifyResult, 1);
return new EditKeyResult(log, modifyResult);
} }
} catch (NotFoundException e) { } catch (NotFoundException e) {
@@ -148,19 +148,16 @@ public class EditKeyOperation extends BaseOperation<SaveKeyringParcel> {
new UploadKeyringParcel(saveParcel.getUploadKeyserver(), keyringBytes); new UploadKeyringParcel(saveParcel.getUploadKeyserver(), keyringBytes);
UploadResult uploadResult = UploadResult uploadResult =
new UploadOperation(mContext, mProviderHelper, mProgressable) new UploadOperation(mContext, mProviderHelper, mProgressable, mCancelled)
.execute(exportKeyringParcel, cryptoInput); .execute(exportKeyringParcel, cryptoInput);
log.add(uploadResult, 2);
if (uploadResult.isPending()) { if (uploadResult.isPending()) {
return uploadResult; return new EditKeyResult(log, uploadResult);
} else if (!uploadResult.success() && saveParcel.isUploadAtomic()) { } else if (!uploadResult.success() && saveParcel.isUploadAtomic()) {
// if atomic, update fail implies edit operation should also fail and not save // if atomic, update fail implies edit operation should also fail and not save
log.add(uploadResult, 2); return new EditKeyResult(log, RequiredInputParcel.createRetryUploadOperation(), cryptoInput);
return new EditKeyResult(log, RequiredInputParcel.createRetryUploadOperation(),
cryptoInput);
} else {
// upload succeeded or not atomic so we continue
log.add(uploadResult, 2);
} }
} }

View File

@@ -28,7 +28,7 @@ import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService; import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.SynchronousQueue; import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
@@ -82,6 +82,8 @@ import org.sufficientlysecure.keychain.util.orbot.OrbotHelper;
*/ */
public class ImportOperation extends BaseOperation<ImportKeyringParcel> { public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
public static final int MAX_THREADS = 10;
public ImportOperation(Context context, ProviderHelper providerHelper, Progressable public ImportOperation(Context context, ProviderHelper providerHelper, Progressable
progressable) { progressable) {
super(context, providerHelper, progressable); super(context, providerHelper, progressable);
@@ -133,7 +135,7 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
@NonNull @NonNull
private ImportKeyResult serialKeyRingImport(Iterator<ParcelableKeyRing> entries, int num, private ImportKeyResult serialKeyRingImport(Iterator<ParcelableKeyRing> entries, int num,
String keyServerUri, Progressable progressable, String keyServerUri, Progressable progressable,
Proxy proxy) { @NonNull Proxy proxy) {
if (progressable != null) { if (progressable != null) {
progressable.setProgress(R.string.progress_importing, 0, 100); progressable.setProgress(R.string.progress_importing, 0, 100);
} }
@@ -188,7 +190,7 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
// Make sure we have the keyserver instance cached // Make sure we have the keyserver instance cached
if (keyServer == null) { if (keyServer == null) {
log.add(LogType.MSG_IMPORT_KEYSERVER, 1, keyServerUri); log.add(LogType.MSG_IMPORT_KEYSERVER, 1, keyServerUri);
keyServer = new HkpKeyserver(keyServerUri); keyServer = new HkpKeyserver(keyServerUri, proxy);
} }
try { try {
@@ -197,11 +199,10 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
if (entry.mExpectedFingerprint != null) { if (entry.mExpectedFingerprint != null) {
log.add(LogType.MSG_IMPORT_FETCH_KEYSERVER, 2, "0x" + log.add(LogType.MSG_IMPORT_FETCH_KEYSERVER, 2, "0x" +
entry.mExpectedFingerprint.substring(24)); entry.mExpectedFingerprint.substring(24));
data = keyServer.get("0x" + entry.mExpectedFingerprint, proxy) data = keyServer.get("0x" + entry.mExpectedFingerprint).getBytes();
.getBytes();
} else { } else {
log.add(LogType.MSG_IMPORT_FETCH_KEYSERVER, 2, entry.mKeyIdHex); 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); key = UncachedKeyRing.decodeFromData(data);
if (key != null) { if (key != null) {
@@ -219,12 +220,12 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
if (entry.mKeybaseName != null) { if (entry.mKeybaseName != null) {
// Make sure we have this cached // Make sure we have this cached
if (keybaseServer == null) { if (keybaseServer == null) {
keybaseServer = new KeybaseKeyserver(); keybaseServer = new KeybaseKeyserver(proxy);
} }
try { try {
log.add(LogType.MSG_IMPORT_FETCH_KEYBASE, 2, entry.mKeybaseName); 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); UncachedKeyRing keybaseKey = UncachedKeyRing.decodeFromData(data);
// If there already is a key, merge the two // If there already is a key, merge the two
@@ -261,12 +262,6 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
continue; continue;
} }
// Another check if we have been cancelled
if (checkCancelled()) {
cancelled = true;
break;
}
SaveKeyringResult result; SaveKeyringResult result;
// synchronizing prevents https://github.com/open-keychain/open-keychain/issues/1221 // synchronizing prevents https://github.com/open-keychain/open-keychain/issues/1221
// and https://github.com/open-keychain/open-keychain/issues/1480 // and https://github.com/open-keychain/open-keychain/issues/1480
@@ -365,13 +360,15 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
} }
} }
// Final log entry, it's easier to do this individually if (!cancelled) {
if ((newKeys > 0 || updatedKeys > 0) && badKeys > 0) { // Final log entry, it's easier to do this individually
log.add(LogType.MSG_IMPORT_PARTIAL, 1); if ((newKeys > 0 || updatedKeys > 0) && badKeys > 0) {
} else if (newKeys > 0 || updatedKeys > 0) { log.add(LogType.MSG_IMPORT_PARTIAL, 1);
log.add(LogType.MSG_IMPORT_SUCCESS, 1); } else if (newKeys > 0 || updatedKeys > 0) {
} else { log.add(LogType.MSG_IMPORT_SUCCESS, 1);
log.add(LogType.MSG_IMPORT_ERROR, 1); } else {
log.add(LogType.MSG_IMPORT_ERROR, 1);
}
} }
return new ImportKeyResult(resultType, log, newKeys, updatedKeys, badKeys, secret, return new ImportKeyResult(resultType, log, newKeys, updatedKeys, badKeys, secret,
@@ -400,8 +397,7 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
return new ImportKeyResult(null, return new ImportKeyResult(null,
RequiredInputParcel.createOrbotRequiredOperation(), cryptoInput); RequiredInputParcel.createOrbotRequiredOperation(), cryptoInput);
} }
proxy = Preferences.getPreferences(mContext).getProxyPrefs().parcelableProxy proxy = Preferences.getPreferences(mContext).getProxyPrefs().getProxy();
.getProxy();
} else { } else {
proxy = cryptoInput.getParcelableProxy().getProxy(); proxy = cryptoInput.getParcelableProxy().getProxy();
} }
@@ -414,62 +410,61 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
} }
@NonNull @NonNull
private ImportKeyResult multiThreadedKeyImport(Iterator<ParcelableKeyRing> keyListIterator, private ImportKeyResult multiThreadedKeyImport(@NonNull Iterator<ParcelableKeyRing> keyListIterator,
int totKeys, final String keyServer, int totKeys, final String keyServer,
final Proxy proxy) { final Proxy proxy) {
Log.d(Constants.TAG, "Multi-threaded key import starting"); 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, MAX_THREADS, 30L, TimeUnit.SECONDS,
ExecutorService importExecutor = new ThreadPoolExecutor(0, maxThreads, new LinkedBlockingQueue<Runnable>());
30L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
ExecutorCompletionService<ImportKeyResult> importCompletionService = ExecutorCompletionService<ImportKeyResult> importCompletionService =
new ExecutorCompletionService<>(importExecutor); 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<ImportKeyResult> importOperationCallable = new Callable<ImportKeyResult> Callable<ImportKeyResult> importOperationCallable = new Callable<ImportKeyResult>
() { () {
@Override @Override
public ImportKeyResult call() { public ImportKeyResult call() {
ArrayList<ParcelableKeyRing> list = new ArrayList<>(); if (checkCancelled()) {
list.add(pkRing); return null;
return serialKeyRingImport(list.iterator(), 1, keyServer,
ignoreProgressable, proxy);
} }
};
importCompletionService.submit(importOperationCallable); ArrayList<ParcelableKeyRing> list = new ArrayList<>();
} list.add(pkRing);
while (!accumulator.isImportFinished()) { // accumulate the results of each import return serialKeyRingImport(list.iterator(), 1, keyServer, ignoreProgressable, proxy);
try { }
accumulator.accumulateKeyImport(importCompletionService.take().get()); };
} catch (InterruptedException | ExecutionException e) {
Log.e(Constants.TAG, "A key could not be imported during multi-threaded " + importCompletionService.submit(importOperationCallable);
"import", e); }
// do nothing?
if (e instanceof ExecutionException) { while (!accumulator.isImportFinished()) { // accumulate the results of each import
// Since serialKeyRingImport does not throw any exceptions, this is what try {
// would have happened if accumulator.accumulateKeyImport(importCompletionService.take().get());
// we were importing the key on this thread } catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(); 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<ImportKeyringParcel> {
private int mUpdatedKeys = 0; private int mUpdatedKeys = 0;
private int mSecret = 0; private int mSecret = 0;
private int mResultType = 0; private int mResultType = 0;
private boolean mHasCancelledResult;
/** /**
* Accumulates keyring imports and updates the progressable whenever a new key is imported. * Accumulates keyring imports and updates the progressable whenever a new key is imported.
@@ -503,14 +499,25 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
} }
} }
public synchronized void accumulateKeyImport(ImportKeyResult result) { public void accumulateKeyImport(ImportKeyResult result) {
mImportedKeys++; mImportedKeys++;
if (result == null) {
return;
}
if (mProgressable != null) { if (mProgressable != null) {
mProgressable.setProgress(mImportedKeys, mTotalKeys); 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; mBadKeys += result.mBadKeys;
mNewKeys += result.mNewKeys; mNewKeys += result.mNewKeys;
mUpdatedKeys += result.mUpdatedKeys; mUpdatedKeys += result.mUpdatedKeys;
@@ -533,7 +540,9 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
// adding required information to mResultType // adding required information to mResultType
// special case,no keys requested for import // 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; mResultType = ImportKeyResult.RESULT_FAIL_NOTHING;
} else { } else {
if (mNewKeys > 0) { if (mNewKeys > 0) {

View File

@@ -110,10 +110,9 @@ public class InputDataOperation extends BaseOperation<InputDataParcel> {
if (decryptResult.isPending()) { if (decryptResult.isPending()) {
return new InputDataResult(log, decryptResult); return new InputDataResult(log, decryptResult);
} }
log.addByMerge(decryptResult, 2); log.addByMerge(decryptResult, 1);
if (!decryptResult.success()) { if ( ! decryptResult.success()) {
log.add(LogType.MSG_DATA_ERROR_OPENPGP, 1);
return new InputDataResult(InputDataResult.RESULT_ERROR, log); return new InputDataResult(InputDataResult.RESULT_ERROR, log);
} }
@@ -165,6 +164,7 @@ public class InputDataOperation extends BaseOperation<InputDataParcel> {
parser.setContentDecoding(true); parser.setContentDecoding(true);
parser.setRecurse(); parser.setRecurse();
parser.setContentHandler(new AbstractContentHandler() { parser.setContentHandler(new AbstractContentHandler() {
private boolean mFoundHeaderWithFields = false;
private Uri uncheckedSignedDataUri; private Uri uncheckedSignedDataUri;
String mFilename; String mFilename;
@@ -220,12 +220,20 @@ public class InputDataOperation extends BaseOperation<InputDataParcel> {
mFilename = null; mFilename = null;
} }
@Override
public void endHeader() throws MimeException {
if ( ! mFoundHeaderWithFields) {
parser.stop();
}
}
@Override @Override
public void field(Field field) throws MimeException { public void field(Field field) throws MimeException {
field = DefaultFieldParser.getParser().parse(field, DecodeMonitor.SILENT); field = DefaultFieldParser.getParser().parse(field, DecodeMonitor.SILENT);
if (field instanceof ContentDispositionField) { if (field instanceof ContentDispositionField) {
mFilename = ((ContentDispositionField) field).getFilename(); mFilename = ((ContentDispositionField) field).getFilename();
} }
mFoundHeaderWithFields = true;
} }
private void bodySignature(BodyDescriptor bd, InputStream is) throws MimeException, IOException { private void bodySignature(BodyDescriptor bd, InputStream is) throws MimeException, IOException {

View File

@@ -19,12 +19,13 @@
package org.sufficientlysecure.keychain.operations; package org.sufficientlysecure.keychain.operations;
import android.content.Context; import android.content.Context;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import org.sufficientlysecure.keychain.Constants; 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.OperationResult;
import org.sufficientlysecure.keychain.operations.results.RevokeResult; import org.sufficientlysecure.keychain.operations.results.RevokeResult;
import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.pgp.Progressable;
@@ -79,9 +80,8 @@ public class RevokeOperation extends BaseOperation<RevokeKeyringParcel> {
saveKeyringParcel.mRevokeSubKeys.add(masterKeyId); saveKeyringParcel.mRevokeSubKeys.add(masterKeyId);
InputPendingResult revokeAndUploadResult = new EditKeyOperation(mContext, EditKeyResult revokeAndUploadResult = new EditKeyOperation(mContext,
mProviderHelper, mProgressable, mCancelled) mProviderHelper, mProgressable, mCancelled).execute(saveKeyringParcel, cryptoInputParcel);
.execute(saveKeyringParcel, cryptoInputParcel);
if (revokeAndUploadResult.isPending()) { if (revokeAndUploadResult.isPending()) {
return revokeAndUploadResult; return revokeAndUploadResult;

View File

@@ -27,6 +27,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
import android.content.Context; import android.content.Context;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.spongycastle.bcpg.ArmoredOutputStream; import org.spongycastle.bcpg.ArmoredOutputStream;
import org.sufficientlysecure.keychain.Constants; 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.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.ParcelableProxy;
import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.Preferences;
import org.sufficientlysecure.keychain.util.Preferences.ProxyPrefs;
import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; import org.sufficientlysecure.keychain.util.orbot.OrbotHelper;
/** /**
* An operation class which implements high level export operations. * An operation class which implements the upload of a single key to a key server.
* 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.
*/ */
public class UploadOperation extends BaseOperation<UploadKeyringParcel> { public class UploadOperation extends BaseOperation<UploadKeyringParcel> {
public UploadOperation(Context context, ProviderHelper providerHelper, Progressable
progressable) {
super(context, providerHelper, progressable);
}
public UploadOperation(Context context, ProviderHelper providerHelper, public UploadOperation(Context context, ProviderHelper providerHelper,
Progressable progressable, AtomicBoolean cancelled) { Progressable progressable, AtomicBoolean cancelled) {
super(context, providerHelper, progressable, cancelled); super(context, providerHelper, progressable, cancelled);
@@ -74,57 +66,99 @@ public class UploadOperation extends BaseOperation<UploadKeyringParcel> {
@NonNull @NonNull
public UploadResult execute(UploadKeyringParcel uploadInput, CryptoInputParcel cryptoInput) { 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; Proxy proxy;
if (cryptoInput.getParcelableProxy() == null) { {
// explicit proxy not set boolean proxyIsTor = false;
if (!OrbotHelper.isOrbotInRequiredState(mContext)) {
return new UploadResult(null, RequiredInputParcel.createOrbotRequiredOperation(), cryptoInput); // 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 { if (proxyIsTor) {
proxy = cryptoInput.getParcelableProxy().getProxy(); 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); HkpKeyserver hkpKeyserver;
try { {
CanonicalizedPublicKeyRing keyring; hkpKeyserver = new HkpKeyserver(uploadInput.mKeyserver, proxy);
if (uploadInput.mMasterKeyId != null) { log.add(LogType.MSG_UPLOAD_SERVER, 1, hkpKeyserver.toString());
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());
} }
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(); ByteArrayOutputStream bos = new ByteArrayOutputStream();
ArmoredOutputStream aos = null; ArmoredOutputStream aos = null;
OperationLog log = new OperationLog();
log.add(LogType.MSG_BACKUP_UPLOAD_PUBLIC, 0, KeyFormattingUtils.convertKeyIdToHex(
keyring.getPublicKey().getKeyId()
));
try { try {
aos = new ArmoredOutputStream(bos); aos = new ArmoredOutputStream(bos);
@@ -132,22 +166,23 @@ public class UploadOperation extends BaseOperation<UploadKeyringParcel> {
aos.close(); aos.close();
String armoredKey = bos.toString("UTF-8"); 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); return new UploadResult(UploadResult.RESULT_OK, log);
} catch (IOException e) { } catch (IOException e) {
Log.e(Constants.TAG, "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); return new UploadResult(UploadResult.RESULT_ERROR, log);
} catch (AddKeyException e) { } catch (AddKeyException e) {
Log.e(Constants.TAG, "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); return new UploadResult(UploadResult.RESULT_ERROR, log);
} finally { } finally {
mProgressable.setProgress(R.string.progress_uploading, 1, 1);
try { try {
if (aos != null) { if (aos != null) {
aos.close(); aos.close();

View File

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

View File

@@ -39,6 +39,8 @@ public class DecryptVerifyResult extends InputPendingResult {
byte[] mOutputBytes; byte[] mOutputBytes;
public long mOperationTime;
public DecryptVerifyResult(int result, OperationLog log) { public DecryptVerifyResult(int result, OperationLog log) {
super(result, log); super(result, log);
} }

View File

@@ -38,6 +38,11 @@ public class EditKeyResult extends InputPendingResult {
mMasterKeyId = null; mMasterKeyId = null;
} }
public EditKeyResult(OperationLog log, InputPendingResult result) {
super(log, result);
mMasterKeyId = null;
}
public EditKeyResult(Parcel source) { public EditKeyResult(Parcel source) {
super(source); super(source);
mMasterKeyId = source.readInt() != 0 ? source.readLong() : null; mMasterKeyId = source.readInt() != 0 ? source.readLong() : null;

View File

@@ -19,6 +19,7 @@
package org.sufficientlysecure.keychain.operations.results; package org.sufficientlysecure.keychain.operations.results;
import android.os.Parcel; import android.os.Parcel;
import android.support.annotation.NonNull;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; 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 // in case operation needs to add to/changes the cryptoInputParcel sent to it
public final CryptoInputParcel mCryptoInputParcel; public final CryptoInputParcel mCryptoInputParcel;
public InputPendingResult(int result, OperationLog log) { public InputPendingResult(int result, @NonNull OperationLog log) {
super(result, log); super(result, log);
mRequiredInput = null; mRequiredInput = null;
mCryptoInputParcel = null; mCryptoInputParcel = null;
} }
public InputPendingResult(OperationLog log, InputPendingResult result) { public InputPendingResult(@NonNull OperationLog log, @NonNull InputPendingResult result) {
super(RESULT_PENDING, log); super(RESULT_PENDING, log);
if (!result.isPending()) { if (!result.isPending()) {
throw new AssertionError("sub result must be pending!"); throw new AssertionError("sub result must be pending!");
@@ -47,7 +48,7 @@ public class InputPendingResult extends OperationResult {
mCryptoInputParcel = result.mCryptoInputParcel; mCryptoInputParcel = result.mCryptoInputParcel;
} }
public InputPendingResult(OperationLog log, RequiredInputParcel requiredInput, public InputPendingResult(@NonNull OperationLog log, RequiredInputParcel requiredInput,
CryptoInputParcel cryptoInputParcel) { CryptoInputParcel cryptoInputParcel) {
super(RESULT_PENDING, log); super(RESULT_PENDING, log);
mRequiredInput = requiredInput; mRequiredInput = requiredInput;

View File

@@ -635,6 +635,8 @@ public abstract class OperationResult implements Parcelable {
MSG_EK_ERROR_NOT_FOUND (LogLevel.ERROR, R.string.msg_ek_error_not_found), MSG_EK_ERROR_NOT_FOUND (LogLevel.ERROR, R.string.msg_ek_error_not_found),
// decryptverify // 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_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_ASKIP_NOT_ALLOWED (LogLevel.DEBUG, R.string.msg_dc_askip_not_allowed),
MSG_DC_ASYM (LogLevel.DEBUG, R.string.msg_dc_asym), 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_IMPORT_SUCCESS (LogLevel.OK, R.string.msg_import_success),
MSG_BACKUP(LogLevel.START, R.plurals.msg_backup), 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_PUBLIC(LogLevel.DEBUG, R.string.msg_backup_public),
MSG_BACKUP_SECRET(LogLevel.DEBUG, R.string.msg_backup_secret), MSG_BACKUP_SECRET(LogLevel.DEBUG, R.string.msg_backup_secret),
MSG_BACKUP_ALL(LogLevel.START, R.string.msg_backup_all), 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_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_DB(LogLevel.ERROR, R.string.msg_backup_error_db),
MSG_BACKUP_ERROR_IO(LogLevel.ERROR, R.string.msg_backup_error_io), 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_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), 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 (LogLevel.START, R.string.msg_data),
MSG_DATA_OPENPGP (LogLevel.DEBUG, R.string.msg_data_openpgp), 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_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 (LogLevel.INFO, R.string.msg_data_detached),
MSG_DATA_DETACHED_CLEAR (LogLevel.WARN, R.string.msg_data_detached_clear), MSG_DATA_DETACHED_CLEAR (LogLevel.WARN, R.string.msg_data_detached_clear),
MSG_DATA_DETACHED_SIG (LogLevel.DEBUG, R.string.msg_data_detached_sig), 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_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_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_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; public final int mMsgId;

View File

@@ -26,6 +26,7 @@ import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
public class PgpSignEncryptResult extends InputPendingResult { public class PgpSignEncryptResult extends InputPendingResult {
byte[] mDetachedSignature; byte[] mDetachedSignature;
public long mOperationTime;
public void setDetachedSignature(byte[] detachedSignature) { public void setDetachedSignature(byte[] detachedSignature) {
mDetachedSignature = detachedSignature; mDetachedSignature = detachedSignature;

View File

@@ -56,6 +56,10 @@ public class SignEncryptResult extends InputPendingResult {
return mResultBytes; return mResultBytes;
} }
public ArrayList<PgpSignEncryptResult> getResults() {
return mResults;
}
public int describeContents() { public int describeContents() {
return 0; return 0;
} }

View File

@@ -18,6 +18,7 @@
package org.sufficientlysecure.keychain.operations.results; package org.sufficientlysecure.keychain.operations.results;
import android.os.Parcel; import android.os.Parcel;
import android.support.annotation.NonNull;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; 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) { CryptoInputParcel cryptoInputParcel) {
super(log, requiredInputParcel, cryptoInputParcel); super(log, requiredInputParcel, cryptoInputParcel);
// we won't use these values // we won't use these values

View File

@@ -48,7 +48,7 @@ import org.spongycastle.openpgp.PGPPBEEncryptedData;
import org.spongycastle.openpgp.PGPPublicKeyEncryptedData; import org.spongycastle.openpgp.PGPPublicKeyEncryptedData;
import org.spongycastle.openpgp.PGPSignatureList; import org.spongycastle.openpgp.PGPSignatureList;
import org.spongycastle.openpgp.PGPUtil; 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.PBEDataDecryptorFactory;
import org.spongycastle.openpgp.operator.PGPDigestCalculatorProvider; import org.spongycastle.openpgp.operator.PGPDigestCalculatorProvider;
import org.spongycastle.openpgp.operator.jcajce.CachingDataDecryptorFactory; import org.spongycastle.openpgp.operator.jcajce.CachingDataDecryptorFactory;
@@ -87,6 +87,8 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
InputData inputData; InputData inputData;
OutputStream outputStream; OutputStream outputStream;
long startTime = System.currentTimeMillis();
if (input.getInputBytes() != null) { if (input.getInputBytes() != null) {
byte[] inputBytes = input.getInputBytes(); byte[] inputBytes = input.getInputBytes();
inputData = new InputData(new ByteArrayInputStream(inputBytes), inputBytes.length); inputData = new InputData(new ByteArrayInputStream(inputBytes), inputBytes.length);
@@ -122,6 +124,8 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
result.setOutputBytes(outputData); result.setOutputBytes(outputData);
} }
result.mOperationTime = System.currentTimeMillis() - startTime;
Log.d(Constants.TAG, "total time taken: " + String.format("%.2f", result.mOperationTime / 1000.0) + "s");
return result; return result;
} }
@@ -277,11 +281,11 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
OpenPgpDecryptionResultBuilder decryptionResultBuilder = new OpenPgpDecryptionResultBuilder(); OpenPgpDecryptionResultBuilder decryptionResultBuilder = new OpenPgpDecryptionResultBuilder();
JcaPGPObjectFactory plainFact; JcaSkipMarkerPGPObjectFactory plainFact;
Object dataChunk; Object dataChunk;
EncryptStreamResult esResult = null; EncryptStreamResult esResult = null;
{ // resolve encrypted (symmetric and asymmetric) packets { // resolve encrypted (symmetric and asymmetric) packets
JcaPGPObjectFactory pgpF = new JcaPGPObjectFactory(in); JcaSkipMarkerPGPObjectFactory pgpF = new JcaSkipMarkerPGPObjectFactory(in);
Object obj = pgpF.nextObject(); Object obj = pgpF.nextObject();
if (obj instanceof PGPEncryptedDataList) { if (obj instanceof PGPEncryptedDataList) {
@@ -308,7 +312,7 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
decryptionResultBuilder.setInsecure(true); decryptionResultBuilder.setInsecure(true);
} }
plainFact = new JcaPGPObjectFactory(esResult.cleartextStream); plainFact = new JcaSkipMarkerPGPObjectFactory(esResult.cleartextStream);
dataChunk = plainFact.nextObject(); dataChunk = plainFact.nextObject();
} else { } else {
@@ -333,7 +337,7 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
PGPCompressedData compressedData = (PGPCompressedData) dataChunk; PGPCompressedData compressedData = (PGPCompressedData) dataChunk;
JcaPGPObjectFactory fact = new JcaPGPObjectFactory(compressedData.getDataStream()); JcaSkipMarkerPGPObjectFactory fact = new JcaSkipMarkerPGPObjectFactory(compressedData.getDataStream());
dataChunk = fact.nextObject(); dataChunk = fact.nextObject();
plainFact = fact; plainFact = fact;
} }
@@ -425,10 +429,12 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
InputStream dataIn = literalData.getInputStream(); InputStream dataIn = literalData.getInputStream();
long opTime, startTime = System.currentTimeMillis();
long alreadyWritten = 0; long alreadyWritten = 0;
long wholeSize = 0; // TODO inputData.getSize() - inputData.getStreamPosition(); long wholeSize = 0; // TODO inputData.getSize() - inputData.getStreamPosition();
int length; int length;
byte[] buffer = new byte[1 << 16]; byte[] buffer = new byte[8192];
byte[] firstBytes = new byte[48]; byte[] firstBytes = new byte[48];
while ((length = dataIn.read(buffer)) > 0) { while ((length = dataIn.read(buffer)) > 0) {
// Log.d(Constants.TAG, "read bytes: " + length); // Log.d(Constants.TAG, "read bytes: " + length);
@@ -456,6 +462,20 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
} }
} }
if (signatureChecker.isInitialized()) {
Object o = plainFact.nextObject();
boolean signatureCheckOk = signatureChecker.verifySignatureOnePass(o, log, indent + 1);
if (!signatureCheckOk) {
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
}
}
opTime = System.currentTimeMillis()-startTime;
Log.d(Constants.TAG, "decrypt time taken: " + String.format("%.2f", opTime / 1000.0) + "s");
// special treatment to detect pgp mime types // special treatment to detect pgp mime types
if (matchesPrefix(firstBytes, "-----BEGIN PGP PUBLIC KEY BLOCK-----") if (matchesPrefix(firstBytes, "-----BEGIN PGP PUBLIC KEY BLOCK-----")
|| matchesPrefix(firstBytes, "-----BEGIN PGP PRIVATE KEY BLOCK-----")) { || matchesPrefix(firstBytes, "-----BEGIN PGP PRIVATE KEY BLOCK-----")) {
@@ -470,17 +490,6 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
metadata = new OpenPgpMetadata( metadata = new OpenPgpMetadata(
originalFilename, mimeType, literalData.getModificationTime().getTime(), alreadyWritten, charset); originalFilename, mimeType, literalData.getModificationTime().getTime(), alreadyWritten, charset);
if (signatureChecker.isInitialized()) {
Object o = plainFact.nextObject();
boolean signatureCheckOk = signatureChecker.verifySignatureOnePass(o, log, indent + 1);
if (!signatureCheckOk) {
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
}
}
indent -= 1; indent -= 1;
if (esResult != null) { if (esResult != null) {
@@ -513,6 +522,7 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
result.setSignatureResult(signatureChecker.getSignatureResult()); result.setSignatureResult(signatureChecker.getSignatureResult());
result.setDecryptionResult(decryptionResultBuilder.build()); result.setDecryptionResult(decryptionResultBuilder.build());
result.setDecryptionMetadata(metadata); result.setDecryptionMetadata(metadata);
result.mOperationTime = opTime;
return result; return result;
@@ -581,6 +591,18 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
// get subkey which has been used for this encryption packet // get subkey which has been used for this encryption packet
secretEncryptionKey = secretKeyRing.getSecretKey(subKeyId); secretEncryptionKey = secretKeyRing.getSecretKey(subKeyId);
if (!secretEncryptionKey.canEncrypt()) {
secretEncryptionKey = null;
log.add(LogType.MSG_DC_ASKIP_BAD_FLAGS, indent + 1);
continue;
}
if (!secretEncryptionKey.getSecretKeyType().isUsable()) {
secretEncryptionKey = null;
log.add(LogType.MSG_DC_ASKIP_UNAVAILABLE, indent + 1);
continue;
}
/* secret key exists in database and is allowed! */ /* secret key exists in database and is allowed! */
asymmetricPacketFound = true; asymmetricPacketFound = true;
@@ -817,7 +839,7 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
} }
updateProgress(R.string.progress_processing_signature, 60, 100); updateProgress(R.string.progress_processing_signature, 60, 100);
JcaPGPObjectFactory pgpFact = new JcaPGPObjectFactory(aIn); JcaSkipMarkerPGPObjectFactory pgpFact = new JcaSkipMarkerPGPObjectFactory(aIn);
PgpSignatureChecker signatureChecker = new PgpSignatureChecker(mProviderHelper); PgpSignatureChecker signatureChecker = new PgpSignatureChecker(mProviderHelper);
@@ -869,12 +891,12 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
InputStream detachedSigIn = new ByteArrayInputStream(input.getDetachedSignature()); InputStream detachedSigIn = new ByteArrayInputStream(input.getDetachedSignature());
detachedSigIn = PGPUtil.getDecoderStream(detachedSigIn); detachedSigIn = PGPUtil.getDecoderStream(detachedSigIn);
JcaPGPObjectFactory pgpFact = new JcaPGPObjectFactory(detachedSigIn); JcaSkipMarkerPGPObjectFactory pgpFact = new JcaSkipMarkerPGPObjectFactory(detachedSigIn);
Object o = pgpFact.nextObject(); Object o = pgpFact.nextObject();
if (o instanceof PGPCompressedData) { if (o instanceof PGPCompressedData) {
PGPCompressedData c1 = (PGPCompressedData) o; PGPCompressedData c1 = (PGPCompressedData) o;
pgpFact = new JcaPGPObjectFactory(c1.getDataStream()); pgpFact = new JcaSkipMarkerPGPObjectFactory(c1.getDataStream());
o = pgpFact.nextObject(); o = pgpFact.nextObject();
} }

View File

@@ -117,7 +117,7 @@ public class PgpHelper {
} }
} }
public static String getPgpContent(@NonNull CharSequence input) { public static String getPgpMessageContent(@NonNull CharSequence input) {
Log.dEscaped(Constants.TAG, "input: " + input); Log.dEscaped(Constants.TAG, "input: " + input);
Matcher matcher = PgpHelper.PGP_MESSAGE.matcher(input); Matcher matcher = PgpHelper.PGP_MESSAGE.matcher(input);
@@ -141,4 +141,18 @@ public class PgpHelper {
} }
} }
public static String getPgpKeyContent(@NonNull CharSequence input) {
Log.dEscaped(Constants.TAG, "input: " + input);
Matcher matcher = PgpHelper.PGP_PUBLIC_KEY.matcher(input);
if (matcher.matches()) {
String text = matcher.group(1);
text = fixPgpMessage(text);
Log.dEscaped(Constants.TAG, "input fixed: " + text);
return text;
}
return null;
}
} }

View File

@@ -321,6 +321,8 @@ public class PgpSignEncryptOperation extends BaseOperation {
ArmoredOutputStream detachedArmorOut = null; ArmoredOutputStream detachedArmorOut = null;
BCPGOutputStream detachedBcpgOut = null; BCPGOutputStream detachedBcpgOut = null;
long opTime, startTime = System.currentTimeMillis();
try { try {
if (enableEncryption) { if (enableEncryption) {
@@ -516,6 +518,10 @@ public class PgpSignEncryptOperation extends BaseOperation {
} }
} }
opTime = System.currentTimeMillis() -startTime;
Log.d(Constants.TAG, "sign/encrypt time taken: " + String.format("%.2f",
opTime / 1000.0) + "s");
// closing outputs // closing outputs
// NOTE: closing needs to be done in the correct order! // NOTE: closing needs to be done in the correct order!
if (encryptionOut != null) { if (encryptionOut != null) {
@@ -559,6 +565,7 @@ public class PgpSignEncryptOperation extends BaseOperation {
log.add(LogType.MSG_PSE_OK, indent); log.add(LogType.MSG_PSE_OK, indent);
PgpSignEncryptResult result = new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_OK, log); PgpSignEncryptResult result = new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_OK, log);
result.mOperationTime = opTime;
if (detachedByteOut != null) { if (detachedByteOut != null) {
try { try {
detachedByteOut.flush(); detachedByteOut.flush();

View File

@@ -54,7 +54,7 @@ import java.io.IOException;
*/ */
public class KeychainDatabase extends SQLiteOpenHelper { public class KeychainDatabase extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "openkeychain.db"; private static final String DATABASE_NAME = "openkeychain.db";
private static final int DATABASE_VERSION = 13; private static final int DATABASE_VERSION = 14;
static Boolean apgHack = false; static Boolean apgHack = false;
private Context mContext; private Context mContext;
@@ -74,12 +74,14 @@ public class KeychainDatabase extends SQLiteOpenHelper {
"CREATE TABLE IF NOT EXISTS keyrings_public (" "CREATE TABLE IF NOT EXISTS keyrings_public ("
+ KeyRingsColumns.MASTER_KEY_ID + " INTEGER PRIMARY KEY," + KeyRingsColumns.MASTER_KEY_ID + " INTEGER PRIMARY KEY,"
+ KeyRingsColumns.KEY_RING_DATA + " BLOB" + KeyRingsColumns.KEY_RING_DATA + " BLOB"
+ "PRIMARY KEY(" + KeyRingsColumns.MASTER_KEY_ID + "),"
+ ")"; + ")";
private static final String CREATE_KEYRINGS_SECRET = private static final String CREATE_KEYRINGS_SECRET =
"CREATE TABLE IF NOT EXISTS keyrings_secret (" "CREATE TABLE IF NOT EXISTS keyrings_secret ("
+ KeyRingsColumns.MASTER_KEY_ID + " INTEGER PRIMARY KEY," + KeyRingsColumns.MASTER_KEY_ID + " INTEGER PRIMARY KEY,"
+ KeyRingsColumns.KEY_RING_DATA + " BLOB," + KeyRingsColumns.KEY_RING_DATA + " BLOB,"
+ "PRIMARY KEY(" + KeyRingsColumns.MASTER_KEY_ID + "),"
+ "FOREIGN KEY(" + KeyRingsColumns.MASTER_KEY_ID + ") " + "FOREIGN KEY(" + KeyRingsColumns.MASTER_KEY_ID + ") "
+ "REFERENCES keyrings_public(" + KeyRingsColumns.MASTER_KEY_ID + ") ON DELETE CASCADE" + "REFERENCES keyrings_public(" + KeyRingsColumns.MASTER_KEY_ID + ") ON DELETE CASCADE"
+ ")"; + ")";
@@ -220,6 +222,13 @@ public class KeychainDatabase extends SQLiteOpenHelper {
db.execSQL(CREATE_API_APPS); db.execSQL(CREATE_API_APPS);
db.execSQL(CREATE_API_APPS_ACCOUNTS); db.execSQL(CREATE_API_APPS_ACCOUNTS);
db.execSQL(CREATE_API_APPS_ALLOWED_KEYS); db.execSQL(CREATE_API_APPS_ALLOWED_KEYS);
db.execSQL("CREATE INDEX keys_by_rank ON keys (" + KeysColumns.RANK + ");");
db.execSQL("CREATE INDEX uids_by_rank ON user_packets (" + UserPacketsColumns.RANK + ", "
+ UserPacketsColumns.USER_ID + ", " + UserPacketsColumns.MASTER_KEY_ID + ");");
db.execSQL("CREATE INDEX verified_certs ON certs ("
+ CertsColumns.VERIFIED + ", " + CertsColumns.MASTER_KEY_ID + ");");
} }
@Override @Override
@@ -291,13 +300,14 @@ public class KeychainDatabase extends SQLiteOpenHelper {
db.execSQL("DELETE FROM api_accounts WHERE key_id BETWEEN 0 AND 3"); db.execSQL("DELETE FROM api_accounts WHERE key_id BETWEEN 0 AND 3");
case 12: case 12:
db.execSQL(CREATE_UPDATE_KEYS); db.execSQL(CREATE_UPDATE_KEYS);
if (oldVersion == 10) {
// no consolidate if we are updating from 10, we're just here for
// the api_accounts fix and the new update keys table
return;
}
case 13: case 13:
// do nothing here, just consolidate // do nothing here, just consolidate
case 14:
db.execSQL("CREATE INDEX keys_by_rank ON keys (" + KeysColumns.RANK + ");");
db.execSQL("CREATE INDEX uids_by_rank ON user_packets (" + UserPacketsColumns.RANK + ", "
+ UserPacketsColumns.USER_ID + ", " + UserPacketsColumns.MASTER_KEY_ID + ");");
db.execSQL("CREATE INDEX verified_certs ON certs ("
+ CertsColumns.VERIFIED + ", " + CertsColumns.MASTER_KEY_ID + ");");
} }
@@ -310,6 +320,10 @@ public class KeychainDatabase extends SQLiteOpenHelper {
@Override @Override
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// Downgrade is ok for the debug version, makes it easier to work with branches
if (Constants.DEBUG) {
return;
}
// NOTE: downgrading the database is explicitly not allowed to prevent // NOTE: downgrading the database is explicitly not allowed to prevent
// someone from exploiting old bugs to export the database // someone from exploiting old bugs to export the database
throw new RuntimeException("Downgrading the database is not allowed!"); throw new RuntimeException("Downgrading the database is not allowed!");

View File

@@ -303,14 +303,14 @@ public class KeychainProvider extends ContentProvider {
projectionMap.put(KeyRings.FINGERPRINT, Tables.KEYS + "." + Keys.FINGERPRINT); projectionMap.put(KeyRings.FINGERPRINT, Tables.KEYS + "." + Keys.FINGERPRINT);
projectionMap.put(KeyRings.USER_ID, Tables.USER_PACKETS + "." + UserPackets.USER_ID); projectionMap.put(KeyRings.USER_ID, Tables.USER_PACKETS + "." + UserPackets.USER_ID);
projectionMap.put(KeyRings.HAS_DUPLICATE_USER_ID, projectionMap.put(KeyRings.HAS_DUPLICATE_USER_ID,
"(SELECT COUNT (*) FROM " + Tables.USER_PACKETS + " AS dups" "(EXISTS (SELECT * FROM " + Tables.USER_PACKETS + " AS dups"
+ " WHERE dups." + UserPackets.MASTER_KEY_ID + " WHERE dups." + UserPackets.MASTER_KEY_ID
+ " != " + Tables.KEYS + "." + Keys.MASTER_KEY_ID + " != " + Tables.KEYS + "." + Keys.MASTER_KEY_ID
+ " AND dups." + UserPackets.RANK + " = 0" + " AND dups." + UserPackets.RANK + " = 0"
+ " AND dups." + UserPackets.USER_ID + " AND dups." + UserPackets.USER_ID
+ " = "+ Tables.USER_PACKETS + "." + UserPackets.USER_ID + " = "+ Tables.USER_PACKETS + "." + UserPackets.USER_ID
+ ") AS " + KeyRings.HAS_DUPLICATE_USER_ID); + ")) AS " + KeyRings.HAS_DUPLICATE_USER_ID);
projectionMap.put(KeyRings.VERIFIED, KeyRings.VERIFIED); projectionMap.put(KeyRings.VERIFIED, Tables.CERTS + "." + Certs.VERIFIED);
projectionMap.put(KeyRings.PUBKEY_DATA, projectionMap.put(KeyRings.PUBKEY_DATA,
Tables.KEY_RINGS_PUBLIC + "." + KeyRingData.KEY_RING_DATA Tables.KEY_RINGS_PUBLIC + "." + KeyRingData.KEY_RING_DATA
+ " AS " + KeyRings.PUBKEY_DATA); + " AS " + KeyRings.PUBKEY_DATA);
@@ -319,10 +319,8 @@ public class KeychainProvider extends ContentProvider {
+ " AS " + KeyRings.PRIVKEY_DATA); + " AS " + KeyRings.PRIVKEY_DATA);
projectionMap.put(KeyRings.HAS_SECRET, Tables.KEYS + "." + KeyRings.HAS_SECRET); projectionMap.put(KeyRings.HAS_SECRET, Tables.KEYS + "." + KeyRings.HAS_SECRET);
projectionMap.put(KeyRings.HAS_ANY_SECRET, projectionMap.put(KeyRings.HAS_ANY_SECRET,
"(EXISTS (SELECT * FROM " + Tables.KEY_RINGS_SECRET "(" + Tables.KEY_RINGS_SECRET + "." + KeyRings.MASTER_KEY_ID + " IS NOT NULL)" +
+ " WHERE " + Tables.KEY_RINGS_SECRET + "." + KeyRingData.MASTER_KEY_ID " AS " + KeyRings.HAS_ANY_SECRET);
+ " = " + Tables.KEYS + "." + Keys.MASTER_KEY_ID
+ ")) AS " + KeyRings.HAS_ANY_SECRET);
projectionMap.put(KeyRings.HAS_ENCRYPT, projectionMap.put(KeyRings.HAS_ENCRYPT,
"kE." + Keys.KEY_ID + " AS " + KeyRings.HAS_ENCRYPT); "kE." + Keys.KEY_ID + " AS " + KeyRings.HAS_ENCRYPT);
projectionMap.put(KeyRings.HAS_SIGN, projectionMap.put(KeyRings.HAS_SIGN,
@@ -363,7 +361,7 @@ public class KeychainProvider extends ContentProvider {
+ " = " + " = "
+ Tables.KEY_RINGS_PUBLIC + "." + KeyRingData.MASTER_KEY_ID + Tables.KEY_RINGS_PUBLIC + "." + KeyRingData.MASTER_KEY_ID
+ ")" : "") + ")" : "")
+ (plist.contains(KeyRings.PRIVKEY_DATA) ? + (plist.contains(KeyRings.PRIVKEY_DATA) || plist.contains(KeyRings.HAS_ANY_SECRET) ?
" LEFT JOIN " + Tables.KEY_RINGS_SECRET + " ON (" " LEFT JOIN " + Tables.KEY_RINGS_SECRET + " ON ("
+ Tables.KEYS + "." + Keys.MASTER_KEY_ID + Tables.KEYS + "." + Keys.MASTER_KEY_ID
+ " = " + " = "
@@ -712,20 +710,45 @@ public class KeychainProvider extends ContentProvider {
} }
SQLiteDatabase db = getDb().getReadableDatabase(); SQLiteDatabase db = getDb().getReadableDatabase();
Cursor cursor = qb.query(db, projection, selection, selectionArgs, groupBy, having, orderBy); Cursor cursor = qb.query(db, projection, selection, selectionArgs, groupBy, having, orderBy);
if (cursor != null) { if (cursor != null) {
// Tell the cursor what uri to watch, so it knows when its source data changes // Tell the cursor what uri to watch, so it knows when its source data changes
cursor.setNotificationUri(getContext().getContentResolver(), uri); cursor.setNotificationUri(getContext().getContentResolver(), uri);
} }
Log.d(Constants.TAG,
"Query: " + qb.buildQuery(projection, selection, null, null, orderBy, null));
if (Constants.DEBUG && Constants.DEBUG_LOG_DB_QUERIES) { if (Constants.DEBUG && Constants.DEBUG_LOG_DB_QUERIES) {
Log.d(Constants.TAG,
"Query: "
+ qb.buildQuery(projection, selection, selectionArgs, null, null,
orderBy, null));
Log.d(Constants.TAG, "Cursor: " + DatabaseUtils.dumpCursorToString(cursor)); Log.d(Constants.TAG, "Cursor: " + DatabaseUtils.dumpCursorToString(cursor));
} }
if (Constants.DEBUG && Constants.DEBUG_EXPLAIN_QUERIES) {
String rawQuery = qb.buildQuery(projection, selection, groupBy, having, orderBy, null);
Cursor explainCursor = db.rawQuery("EXPLAIN QUERY PLAN " + rawQuery, selectionArgs);
// this is a debugging feature, we can be a little careless
explainCursor.moveToFirst();
StringBuilder line = new StringBuilder();
for (int i = 0; i < explainCursor.getColumnCount(); i++) {
line.append(explainCursor.getColumnName(i)).append(", ");
}
Log.d(Constants.TAG, line.toString());
while (!explainCursor.isAfterLast()) {
line = new StringBuilder();
for (int i = 0; i < explainCursor.getColumnCount(); i++) {
line.append(explainCursor.getString(i)).append(", ");
}
Log.d(Constants.TAG, line.toString());
explainCursor.moveToNext();
}
explainCursor.close();
}
return cursor; return cursor;
} }

View File

@@ -25,6 +25,7 @@ import android.net.Uri;
import android.os.IBinder; import android.os.IBinder;
import android.os.ParcelFileDescriptor; import android.os.ParcelFileDescriptor;
import android.os.Parcelable; import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.text.TextUtils; import android.text.TextUtils;
@@ -34,13 +35,15 @@ import org.openintents.openpgp.OpenPgpError;
import org.openintents.openpgp.OpenPgpMetadata; import org.openintents.openpgp.OpenPgpMetadata;
import org.openintents.openpgp.OpenPgpSignatureResult; import org.openintents.openpgp.OpenPgpSignatureResult;
import org.openintents.openpgp.util.OpenPgpApi; import org.openintents.openpgp.util.OpenPgpApi;
import org.spongycastle.bcpg.ArmoredOutputStream;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogEntryParcel; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogEntryParcel;
import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult; import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult;
import org.sufficientlysecure.keychain.pgp.PgpSecurityConstants; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyOperation;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyOperation;
import org.sufficientlysecure.keychain.pgp.PgpSecurityConstants;
import org.sufficientlysecure.keychain.pgp.PgpSignEncryptInputParcel; import org.sufficientlysecure.keychain.pgp.PgpSignEncryptInputParcel;
import org.sufficientlysecure.keychain.pgp.PgpSignEncryptOperation; import org.sufficientlysecure.keychain.pgp.PgpSignEncryptOperation;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
@@ -238,10 +241,8 @@ public class OpenPgpService extends RemoteService {
PendingIntent.FLAG_CANCEL_CURRENT); PendingIntent.FLAG_CANCEL_CURRENT);
} }
private Intent signImpl(Intent data, ParcelFileDescriptor input, private Intent signImpl(Intent data, InputStream inputStream,
ParcelFileDescriptor output, boolean cleartextSign) { OutputStream outputStream, boolean cleartextSign) {
InputStream is = null;
OutputStream os = null;
try { try {
boolean asciiArmor = cleartextSign || data.getBooleanExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true); boolean asciiArmor = cleartextSign || data.getBooleanExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
@@ -277,14 +278,13 @@ public class OpenPgpService extends RemoteService {
} }
// Get Input- and OutputStream from ParcelFileDescriptor // Get Input- and OutputStream from ParcelFileDescriptor
is = new ParcelFileDescriptor.AutoCloseInputStream(input); if (!cleartextSign) {
if (cleartextSign) {
// output stream only needed for cleartext signatures, // output stream only needed for cleartext signatures,
// detached signatures are returned as extra // detached signatures are returned as extra
os = new ParcelFileDescriptor.AutoCloseOutputStream(output); outputStream = null;
} }
long inputLength = is.available(); long inputLength = inputStream.available();
InputData inputData = new InputData(is, inputLength); InputData inputData = new InputData(inputStream, inputLength);
CryptoInputParcel inputParcel = CryptoInputParcelCacheService.getCryptoInputParcel(this, data); CryptoInputParcel inputParcel = CryptoInputParcelCacheService.getCryptoInputParcel(this, data);
if (inputParcel == null) { if (inputParcel == null) {
@@ -298,7 +298,7 @@ public class OpenPgpService extends RemoteService {
// execute PGP operation! // execute PGP operation!
PgpSignEncryptOperation pse = new PgpSignEncryptOperation(this, new ProviderHelper(getContext()), null); PgpSignEncryptOperation pse = new PgpSignEncryptOperation(this, new ProviderHelper(getContext()), null);
PgpSignEncryptResult pgpResult = pse.execute(pseInput, inputParcel, inputData, os); PgpSignEncryptResult pgpResult = pse.execute(pseInput, inputParcel, inputData, outputStream);
if (pgpResult.isPending()) { if (pgpResult.isPending()) {
@@ -330,28 +330,11 @@ public class OpenPgpService extends RemoteService {
new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage())); new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage()));
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR); result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
return result; return result;
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
Log.e(Constants.TAG, "IOException when closing InputStream", e);
}
}
if (os != null) {
try {
os.close();
} catch (IOException e) {
Log.e(Constants.TAG, "IOException when closing OutputStream", e);
}
}
} }
} }
private Intent encryptAndSignImpl(Intent data, ParcelFileDescriptor input, private Intent encryptAndSignImpl(Intent data, InputStream inputStream,
ParcelFileDescriptor output, boolean sign) { OutputStream outputStream, boolean sign) {
InputStream is = null;
OutputStream os = null;
try { try {
boolean asciiArmor = data.getBooleanExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true); boolean asciiArmor = data.getBooleanExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
String originalFilename = data.getStringExtra(OpenPgpApi.EXTRA_ORIGINAL_FILENAME); String originalFilename = data.getStringExtra(OpenPgpApi.EXTRA_ORIGINAL_FILENAME);
@@ -383,13 +366,9 @@ public class OpenPgpService extends RemoteService {
} }
} }
// build InputData and write into OutputStream // TODO this is not correct!
// Get Input- and OutputStream from ParcelFileDescriptor long inputLength = inputStream.available();
is = new ParcelFileDescriptor.AutoCloseInputStream(input); InputData inputData = new InputData(inputStream, inputLength, originalFilename);
os = new ParcelFileDescriptor.AutoCloseOutputStream(output);
long inputLength = is.available();
InputData inputData = new InputData(is, inputLength, originalFilename);
PgpSignEncryptInputParcel pseInput = new PgpSignEncryptInputParcel(); PgpSignEncryptInputParcel pseInput = new PgpSignEncryptInputParcel();
pseInput.setEnableAsciiArmorOutput(asciiArmor) pseInput.setEnableAsciiArmorOutput(asciiArmor)
@@ -455,7 +434,7 @@ public class OpenPgpService extends RemoteService {
PgpSignEncryptOperation op = new PgpSignEncryptOperation(this, new ProviderHelper(getContext()), null); PgpSignEncryptOperation op = new PgpSignEncryptOperation(this, new ProviderHelper(getContext()), null);
// execute PGP operation! // execute PGP operation!
PgpSignEncryptResult pgpResult = op.execute(pseInput, inputParcel, inputData, os); PgpSignEncryptResult pgpResult = op.execute(pseInput, inputParcel, inputData, outputStream);
if (pgpResult.isPending()) { if (pgpResult.isPending()) {
RequiredInputParcel requiredInput = pgpResult.getRequiredInputParcel(); RequiredInputParcel requiredInput = pgpResult.getRequiredInputParcel();
@@ -482,37 +461,15 @@ public class OpenPgpService extends RemoteService {
new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage())); new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage()));
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR); result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
return result; return result;
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
Log.e(Constants.TAG, "IOException when closing InputStream", e);
}
}
if (os != null) {
try {
os.close();
} catch (IOException e) {
Log.e(Constants.TAG, "IOException when closing OutputStream", e);
}
}
} }
} }
private Intent decryptAndVerifyImpl(Intent data, ParcelFileDescriptor inputDescriptor, private Intent decryptAndVerifyImpl(Intent data, InputStream inputStream,
ParcelFileDescriptor output, boolean decryptMetadataOnly) { OutputStream outputStream, boolean decryptMetadataOnly) {
InputStream inputStream = null;
OutputStream outputStream = null;
try { try {
// Get Input- and OutputStream from ParcelFileDescriptor
inputStream = new ParcelFileDescriptor.AutoCloseInputStream(inputDescriptor);
// output is optional, e.g., for verifying detached signatures // output is optional, e.g., for verifying detached signatures
if (decryptMetadataOnly || output == null) { if (decryptMetadataOnly) {
outputStream = null; outputStream = null;
} else {
outputStream = new ParcelFileDescriptor.AutoCloseOutputStream(output);
} }
String currentPkg = getCurrentCallingPackage(); String currentPkg = getCurrentCallingPackage();
@@ -538,6 +495,7 @@ public class OpenPgpService extends RemoteService {
PgpDecryptVerifyOperation op = new PgpDecryptVerifyOperation(this, mProviderHelper, null); PgpDecryptVerifyOperation op = new PgpDecryptVerifyOperation(this, mProviderHelper, null);
// TODO this is not correct!
long inputLength = inputStream.available(); long inputLength = inputStream.available();
InputData inputData = new InputData(inputStream, inputLength); InputData inputData = new InputData(inputStream, inputLength);
@@ -604,6 +562,7 @@ public class OpenPgpService extends RemoteService {
// case RESULT_NOT_ENCRYPTED, but a signature, fallback to deprecated signatureOnly variable // case RESULT_NOT_ENCRYPTED, but a signature, fallback to deprecated signatureOnly variable
if (decryptionResult.getResult() == OpenPgpDecryptionResult.RESULT_NOT_ENCRYPTED if (decryptionResult.getResult() == OpenPgpDecryptionResult.RESULT_NOT_ENCRYPTED
&& signatureResult.getResult() != OpenPgpSignatureResult.RESULT_NO_SIGNATURE) { && signatureResult.getResult() != OpenPgpSignatureResult.RESULT_NO_SIGNATURE) {
// noinspection deprecation, TODO
signatureResult.setSignatureOnly(true); signatureResult.setSignatureOnly(true);
} }
@@ -665,35 +624,40 @@ public class OpenPgpService extends RemoteService {
result.putExtra(OpenPgpApi.RESULT_ERROR, new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage())); result.putExtra(OpenPgpApi.RESULT_ERROR, new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage()));
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR); result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
return result; return result;
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
Log.e(Constants.TAG, "IOException when closing InputStream", e);
}
}
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
Log.e(Constants.TAG, "IOException when closing OutputStream", e);
}
}
} }
} }
private Intent getKeyImpl(Intent data) { private Intent getKeyImpl(Intent data, OutputStream outputStream) {
try { try {
long masterKeyId = data.getLongExtra(OpenPgpApi.EXTRA_KEY_ID, 0); long masterKeyId = data.getLongExtra(OpenPgpApi.EXTRA_KEY_ID, 0);
try { try {
// try to find key, throws NotFoundException if not in db! // try to find key, throws NotFoundException if not in db!
mProviderHelper.getCanonicalizedPublicKeyRing(masterKeyId); CanonicalizedPublicKeyRing keyRing =
mProviderHelper.getCanonicalizedPublicKeyRing(masterKeyId);
Intent result = new Intent(); Intent result = new Intent();
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS); result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
boolean requestedKeyData = outputStream != null;
if (requestedKeyData) {
boolean requestAsciiArmor = data.getBooleanExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, false);
try {
if (requestAsciiArmor) {
outputStream = new ArmoredOutputStream(outputStream);
}
keyRing.encode(outputStream);
} finally {
try {
outputStream.close();
} catch (IOException e) {
Log.e(Constants.TAG, "IOException when closing OutputStream", e);
}
}
}
// also return PendingIntent that opens the key view activity // also return PendingIntent that opens the key view activity
result.putExtra(OpenPgpApi.RESULT_INTENT, getShowKeyPendingIntent(masterKeyId)); result.putExtra(OpenPgpApi.RESULT_INTENT, getShowKeyPendingIntent(masterKeyId));
@@ -821,7 +785,7 @@ public class OpenPgpService extends RemoteService {
OpenPgpError error = new OpenPgpError OpenPgpError error = new OpenPgpError
(OpenPgpError.INCOMPATIBLE_API_VERSIONS, "Incompatible API versions!\n" (OpenPgpError.INCOMPATIBLE_API_VERSIONS, "Incompatible API versions!\n"
+ "used API version: " + data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) + "\n" + "used API version: " + data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) + "\n"
+ "supported API versions: " + supportedVersions.toString()); + "supported API versions: " + supportedVersions);
result.putExtra(OpenPgpApi.RESULT_ERROR, error); result.putExtra(OpenPgpApi.RESULT_ERROR, error);
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR); result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
return result; return result;
@@ -850,68 +814,88 @@ public class OpenPgpService extends RemoteService {
return mBinder; return mBinder;
} }
@Nullable
protected Intent executeInternal(
@NonNull Intent data,
@Nullable ParcelFileDescriptor input,
@Nullable ParcelFileDescriptor output) {
OutputStream outputStream =
(output != null) ? new ParcelFileDescriptor.AutoCloseOutputStream(output) : null;
InputStream inputStream =
(input != null) ? new ParcelFileDescriptor.AutoCloseInputStream(input) : null;
protected Intent executeInternal(Intent data, ParcelFileDescriptor input, ParcelFileDescriptor output) {
try { try {
Intent errorResult = checkRequirements(data); return executeInternalWithStreams(data, inputStream, outputStream);
if (errorResult != null) {
return errorResult;
}
String action = data.getAction();
switch (action) {
case OpenPgpApi.ACTION_CLEARTEXT_SIGN: {
return signImpl(data, input, output, true);
}
case OpenPgpApi.ACTION_SIGN: {
// DEPRECATED: same as ACTION_CLEARTEXT_SIGN
Log.w(Constants.TAG, "You are using a deprecated API call, please use ACTION_CLEARTEXT_SIGN instead of ACTION_SIGN!");
return signImpl(data, input, output, true);
}
case OpenPgpApi.ACTION_DETACHED_SIGN: {
return signImpl(data, input, output, false);
}
case OpenPgpApi.ACTION_ENCRYPT: {
return encryptAndSignImpl(data, input, output, false);
}
case OpenPgpApi.ACTION_SIGN_AND_ENCRYPT: {
return encryptAndSignImpl(data, input, output, true);
}
case OpenPgpApi.ACTION_DECRYPT_VERIFY: {
return decryptAndVerifyImpl(data, input, output, false);
}
case OpenPgpApi.ACTION_DECRYPT_METADATA: {
return decryptAndVerifyImpl(data, input, output, true);
}
case OpenPgpApi.ACTION_GET_SIGN_KEY_ID: {
return getSignKeyIdImpl(data);
}
case OpenPgpApi.ACTION_GET_KEY_IDS: {
return getKeyIdsImpl(data);
}
case OpenPgpApi.ACTION_GET_KEY: {
return getKeyImpl(data);
}
default: {
return null;
}
}
} finally { } finally {
// always close input and output file descriptors even in error cases // always close input and output file descriptors even in error cases
if (input != null) { if (inputStream != null) {
try { try {
input.close(); inputStream.close();
} catch (IOException e) { } catch (IOException e) {
Log.e(Constants.TAG, "IOException when closing input ParcelFileDescriptor", e); Log.e(Constants.TAG, "IOException when closing input ParcelFileDescriptor", e);
} }
} }
if (output != null) { if (outputStream != null) {
try { try {
output.close(); outputStream.close();
} catch (IOException e) { } catch (IOException e) {
Log.e(Constants.TAG, "IOException when closing output ParcelFileDescriptor", e); Log.e(Constants.TAG, "IOException when closing output ParcelFileDescriptor", e);
} }
} }
} }
} }
@Nullable
protected Intent executeInternalWithStreams(
@NonNull Intent data,
@Nullable InputStream inputStream,
@Nullable OutputStream outputStream) {
Intent errorResult = checkRequirements(data);
if (errorResult != null) {
return errorResult;
}
String action = data.getAction();
switch (action) {
case OpenPgpApi.ACTION_CLEARTEXT_SIGN: {
return signImpl(data, inputStream, outputStream, true);
}
case OpenPgpApi.ACTION_SIGN: {
// DEPRECATED: same as ACTION_CLEARTEXT_SIGN
Log.w(Constants.TAG, "You are using a deprecated API call, please use ACTION_CLEARTEXT_SIGN instead of ACTION_SIGN!");
return signImpl(data, inputStream, outputStream, true);
}
case OpenPgpApi.ACTION_DETACHED_SIGN: {
return signImpl(data, inputStream, outputStream, false);
}
case OpenPgpApi.ACTION_ENCRYPT: {
return encryptAndSignImpl(data, inputStream, outputStream, false);
}
case OpenPgpApi.ACTION_SIGN_AND_ENCRYPT: {
return encryptAndSignImpl(data, inputStream, outputStream, true);
}
case OpenPgpApi.ACTION_DECRYPT_VERIFY: {
return decryptAndVerifyImpl(data, inputStream, outputStream, false);
}
case OpenPgpApi.ACTION_DECRYPT_METADATA: {
return decryptAndVerifyImpl(data, inputStream, outputStream, true);
}
case OpenPgpApi.ACTION_GET_SIGN_KEY_ID: {
return getSignKeyIdImpl(data);
}
case OpenPgpApi.ACTION_GET_KEY_IDS: {
return getKeyIdsImpl(data);
}
case OpenPgpApi.ACTION_GET_KEY: {
return getKeyImpl(data, outputStream);
}
default: {
return null;
}
}
}
} }

View File

@@ -0,0 +1,54 @@
/*
* Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com>
* Copyright (C) 2015 Adithya Abraham Philip <adithyaphilip@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.service;
import android.os.Parcel;
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<BenchmarkInputParcel> CREATOR = new Creator<BenchmarkInputParcel>() {
@Override
public BenchmarkInputParcel createFromParcel(Parcel in) {
return new BenchmarkInputParcel(in);
}
@Override
public BenchmarkInputParcel[] newArray(int size) {
return new BenchmarkInputParcel[size];
}
};
}

View File

@@ -29,6 +29,7 @@ import android.os.RemoteException;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.operations.BaseOperation; import org.sufficientlysecure.keychain.operations.BaseOperation;
import org.sufficientlysecure.keychain.operations.BenchmarkOperation;
import org.sufficientlysecure.keychain.operations.CertifyOperation; import org.sufficientlysecure.keychain.operations.CertifyOperation;
import org.sufficientlysecure.keychain.operations.ConsolidateOperation; import org.sufficientlysecure.keychain.operations.ConsolidateOperation;
import org.sufficientlysecure.keychain.operations.DeleteOperation; 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); op = new KeybaseVerificationOperation(outerThis, new ProviderHelper(outerThis), outerThis);
} else if (inputParcel instanceof InputDataParcel) { } else if (inputParcel instanceof InputDataParcel) {
op = new InputDataOperation(outerThis, new ProviderHelper(outerThis), outerThis); op = new InputDataOperation(outerThis, new ProviderHelper(outerThis), outerThis);
} else if (inputParcel instanceof BenchmarkInputParcel) {
op = new BenchmarkOperation(outerThis, new ProviderHelper(outerThis), outerThis);
} else { } else {
throw new AssertionError("Unrecognized input parcel in KeychainService!"); throw new AssertionError("Unrecognized input parcel in KeychainService!");
} }

View File

@@ -59,11 +59,12 @@ public class KeyserverSyncAdapterService extends Service {
// time since last update after which a key should be updated again, in s // time since last update after which a key should be updated again, in s
public static final long KEY_UPDATE_LIMIT = public static final long KEY_UPDATE_LIMIT =
Constants.DEBUG_KEYSERVER_SYNC ? 1 : TimeUnit.DAYS.toSeconds(7); 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 = public static final long SYNC_POSTPONE_TIME =
Constants.DEBUG_KEYSERVER_SYNC ? 30 * 1000 : TimeUnit.MINUTES.toMillis(5); Constants.DEBUG_KEYSERVER_SYNC ? 30 * 1000 : TimeUnit.MINUTES.toMillis(5);
// Time taken by Orbot before a new circuit is created // 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"; private static final String ACTION_IGNORE_TOR = "ignore_tor";
@@ -77,10 +78,14 @@ public class KeyserverSyncAdapterService extends Service {
@Override @Override
public int onStartCommand(final Intent intent, int flags, final int startId) { 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()) { switch (intent.getAction()) {
case ACTION_CANCEL: { case ACTION_CANCEL: {
mCancelled.set(true); mCancelled.set(true);
break; return START_NOT_STICKY;
} }
// the reason for the separation betweyeen SYNC_NOW and UPDATE_ALL is so that starting // 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 // 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, Constants.PROVIDER_AUTHORITY,
new Bundle() new Bundle()
); );
break; return START_NOT_STICKY;
} }
case ACTION_UPDATE_ALL: { case ACTION_UPDATE_ALL: {
// does not check for screen on/off // does not check for screen on/off
asyncKeyUpdate(this, new CryptoInputParcel()); asyncKeyUpdate(this, new CryptoInputParcel(), startId);
break; // 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: { case ACTION_IGNORE_TOR: {
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
manager.cancel(Constants.Notification.KEYSERVER_SYNC_FAIL_ORBOT); manager.cancel(Constants.Notification.KEYSERVER_SYNC_FAIL_ORBOT);
asyncKeyUpdate(this, new CryptoInputParcel(ParcelableProxy.getForNoProxy())); asyncKeyUpdate(this, new CryptoInputParcel(ParcelableProxy.getForNoProxy()),
break; 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: { case ACTION_START_ORBOT: {
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); NotificationManager manager = (NotificationManager)
getSystemService(NOTIFICATION_SERVICE);
manager.cancel(Constants.Notification.KEYSERVER_SYNC_FAIL_ORBOT); manager.cancel(Constants.Notification.KEYSERVER_SYNC_FAIL_ORBOT);
Intent startOrbot = new Intent(this, OrbotRequiredDialogActivity.class); Intent startOrbot = new Intent(this, OrbotRequiredDialogActivity.class);
startOrbot.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startOrbot.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startOrbot.putExtra(OrbotRequiredDialogActivity.EXTRA_START_ORBOT, true); startOrbot.putExtra(OrbotRequiredDialogActivity.EXTRA_START_ORBOT, true);
Messenger messenger = new Messenger( Messenger messenger = new Messenger(
new Handler() { new Handler() {
@Override @Override
public void handleMessage(Message msg) { public void handleMessage(Message msg) {
switch (msg.what) { switch (msg.what) {
case OrbotRequiredDialogActivity.MESSAGE_ORBOT_STARTED: { case OrbotRequiredDialogActivity.MESSAGE_ORBOT_STARTED: {
asyncKeyUpdate(KeyserverSyncAdapterService.this, startServiceWithUpdateAll();
new CryptoInputParcel());
break;
}
case OrbotRequiredDialogActivity.MESSAGE_ORBOT_IGNORE: {
asyncKeyUpdate(KeyserverSyncAdapterService.this,
new CryptoInputParcel(
ParcelableProxy.getForNoProxy()));
break; break;
} }
case OrbotRequiredDialogActivity.MESSAGE_ORBOT_IGNORE:
case OrbotRequiredDialogActivity.MESSAGE_DIALOG_CANCEL: { case OrbotRequiredDialogActivity.MESSAGE_DIALOG_CANCEL: {
// just stop service // not possible since we proceed to Orbot's Activity
stopSelf(); // directly, by starting OrbotRequiredDialogActivity with
// EXTRA_START_ORBOT set to true
break; break;
} }
} }
@@ -138,13 +146,17 @@ public class KeyserverSyncAdapterService extends Service {
); );
startOrbot.putExtra(OrbotRequiredDialogActivity.EXTRA_MESSENGER, messenger); startOrbot.putExtra(OrbotRequiredDialogActivity.EXTRA_MESSENGER, messenger);
startActivity(startOrbot); 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: { case ACTION_DISMISS_NOTIFICATION: {
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
manager.cancel(Constants.Notification.KEYSERVER_SYNC_FAIL_ORBOT); manager.cancel(Constants.Notification.KEYSERVER_SYNC_FAIL_ORBOT);
stopSelf(startId); return START_NOT_STICKY;
break;
} }
} }
return START_NOT_STICKY; return START_NOT_STICKY;
@@ -167,10 +179,7 @@ public class KeyserverSyncAdapterService extends Service {
boolean isScreenOn = pm.isScreenOn(); boolean isScreenOn = pm.isScreenOn();
if (!isScreenOn) { if (!isScreenOn) {
Intent serviceIntent = new Intent(KeyserverSyncAdapterService.this, startServiceWithUpdateAll();
KeyserverSyncAdapterService.class);
serviceIntent.setAction(ACTION_UPDATE_ALL);
startService(serviceIntent);
} else { } else {
postponeSync(); postponeSync();
} }
@@ -188,16 +197,24 @@ public class KeyserverSyncAdapterService extends Service {
return new KeyserverSyncAdapter().getSyncAdapterBinder(); 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()) { 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 // result is pending due to Orbot not being started
// try to start it silently, if disabled show notifications // try to start it silently, if disabled show notifications
new OrbotHelper.SilentStartManager() { new OrbotHelper.SilentStartManager() {
@Override @Override
protected void onOrbotStarted() { protected void onOrbotStarted() {
// retry the update // retry the update
asyncKeyUpdate(KeyserverSyncAdapterService.this, startServiceWithUpdateAll();
new CryptoInputParcel()); stopSelf(startId); // startServiceWithUpdateAll will deliver a new Intent
} }
@Override @Override
@@ -207,16 +224,24 @@ public class KeyserverSyncAdapterService extends Service {
(NotificationManager) getSystemService(NOTIFICATION_SERVICE); (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
manager.notify(Constants.Notification.KEYSERVER_SYNC_FAIL_ORBOT, manager.notify(Constants.Notification.KEYSERVER_SYNC_FAIL_ORBOT,
getOrbotNoification(KeyserverSyncAdapterService.this)); getOrbotNoification(KeyserverSyncAdapterService.this));
// further action on user interaction with notification, intent should not be
// redelivered, therefore:
stopSelf(startId);
} }
}.startOrbotAndListen(this, false); }.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()) { } else if (isUpdateCancelled()) {
Log.d(Constants.TAG, "Keyserver sync cancelled, postponing by" + SYNC_POSTPONE_TIME Log.d(Constants.TAG, "Keyserver sync cancelled, postponing by" + SYNC_POSTPONE_TIME
+ "ms"); + "ms");
postponeSync(); postponeSync();
// postponeSync creates a new intent, so we don't need this to be redelivered
stopSelf(startId);
} else { } else {
Log.d(Constants.TAG, "Keyserver sync completed: Updated: " + result.mUpdatedKeys Log.d(Constants.TAG, "Keyserver sync completed: Updated: " + result.mUpdatedKeys
+ " Failed: " + result.mBadKeys); + " 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, private void asyncKeyUpdate(final Context context,
final CryptoInputParcel cryptoInputParcel) { final CryptoInputParcel cryptoInputParcel, final int startId) {
new Thread(new Runnable() { new Thread(new Runnable() {
@Override @Override
public void run() { public void run() {
ImportKeyResult result = updateKeysFromKeyserver(context, cryptoInputParcel); ImportKeyResult result = updateKeysFromKeyserver(context, cryptoInputParcel);
handleUpdateResult(result); handleUpdateResult(result, startId);
} }
}).start(); }).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 * 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: * performed by parcimonie. Relevant issue and method at:
@@ -290,17 +314,31 @@ public class KeyserverSyncAdapterService extends Service {
CryptoInputParcel cryptoInputParcel) { CryptoInputParcel cryptoInputParcel) {
Log.d(Constants.TAG, "Starting staggered update"); Log.d(Constants.TAG, "Starting staggered update");
// final int WEEK_IN_SECONDS = (int) TimeUnit.DAYS.toSeconds(7); // 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; final int WEEK_IN_SECONDS = 0;
ImportOperation.KeyImportAccumulator accumulator ImportOperation.KeyImportAccumulator accumulator
= new ImportOperation.KeyImportAccumulator(keyList.size(), null); = 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) { for (ParcelableKeyRing keyRing : keyList) {
int waitTime; int waitTime;
int staggeredTime = new Random().nextInt(1 + 2 * (WEEK_IN_SECONDS / keyList.size())); 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; waitTime = staggeredTime;
} else { } 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 + Log.d(Constants.TAG, "Updating key with fingerprint " + keyRing.mExpectedFingerprint +
" with a wait time of " + waitTime + "s"); " with a wait time of " + waitTime + "s");
try { try {
@@ -362,13 +400,15 @@ public class KeyserverSyncAdapterService extends Service {
); );
ArrayList<Long> ignoreMasterKeyIds = new ArrayList<>(); ArrayList<Long> ignoreMasterKeyIds = new ArrayList<>();
while (updatedKeysCursor.moveToNext()) { while (updatedKeysCursor != null && updatedKeysCursor.moveToNext()) {
long masterKeyId = updatedKeysCursor.getLong(INDEX_UPDATED_KEYS_MASTER_KEY_ID); long masterKeyId = updatedKeysCursor.getLong(INDEX_UPDATED_KEYS_MASTER_KEY_ID);
Log.d(Constants.TAG, "Keyserver sync: Ignoring {" + masterKeyId + "} last updated at {" Log.d(Constants.TAG, "Keyserver sync: Ignoring {" + masterKeyId + "} last updated at {"
+ updatedKeysCursor.getLong(INDEX_LAST_UPDATED) + "}s"); + updatedKeysCursor.getLong(INDEX_LAST_UPDATED) + "}s");
ignoreMasterKeyIds.add(masterKeyId); ignoreMasterKeyIds.add(masterKeyId);
} }
updatedKeysCursor.close(); if (updatedKeysCursor != null) {
updatedKeysCursor.close();
}
// 2. Make a list of public keys which should be updated // 2. Make a list of public keys which should be updated
final int INDEX_MASTER_KEY_ID = 0; 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 * 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. * the default application process where the UI code runs.
* *
* @param context used to send an Intent to the service requesting cancellation. * @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 // from de.azapps.mirakel.helper.Helpers from https://github.com/MirakelX/mirakel-android
private Bitmap getBitmap(int resId, Context context) { private Bitmap getBitmap(int resId, Context context) {
int mLargeIconWidth = (int) context.getResources().getDimension( int mLargeIconWidth = (int) context.getResources().getDimension(

View File

@@ -23,6 +23,7 @@ import android.os.Parcelable;
import org.sufficientlysecure.keychain.util.ParcelableProxy; import org.sufficientlysecure.keychain.util.ParcelableProxy;
import org.sufficientlysecure.keychain.util.Passphrase; import org.sufficientlysecure.keychain.util.Passphrase;
import java.net.Proxy;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;

View File

@@ -200,7 +200,7 @@ public class DecryptActivity extends BaseActivity {
} }
// clean up ascii armored message, fixing newlines and stuff // clean up ascii armored message, fixing newlines and stuff
String cleanedText = PgpHelper.getPgpContent(text); String cleanedText = PgpHelper.getPgpMessageContent(text);
if (cleanedText == null) { if (cleanedText == null) {
return null; return null;
} }

View File

@@ -24,12 +24,14 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import android.Manifest;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.app.Activity; import android.app.Activity;
import android.content.ClipDescription; import android.content.ClipDescription;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.LabeledIntent; import android.content.pm.LabeledIntent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo; import android.content.pm.ResolveInfo;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.Point; import android.graphics.Point;
@@ -37,9 +39,12 @@ import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Build;
import android.os.Build.VERSION_CODES; import android.os.Build.VERSION_CODES;
import android.os.Bundle; import android.os.Bundle;
import android.os.Parcelable; 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.DefaultItemAnimator;
import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
@@ -91,6 +96,22 @@ import org.sufficientlysecure.keychain.util.ParcelableHashMap;
import org.sufficientlysecure.keychain.util.Preferences; 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 public class DecryptListFragment
extends QueueingCryptoOperationFragment<InputDataParcel,InputDataResult> extends QueueingCryptoOperationFragment<InputDataParcel,InputDataResult>
implements OnMenuItemClickListener { implements OnMenuItemClickListener {
@@ -102,7 +123,7 @@ public class DecryptListFragment
public static final String ARG_CAN_DELETE = "can_delete"; public static final String ARG_CAN_DELETE = "can_delete";
private static final int REQUEST_CODE_OUTPUT = 0x00007007; 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<Uri> mInputUris; private ArrayList<Uri> mInputUris;
private HashMap<Uri, InputDataResult> mInputDataResults; private HashMap<Uri, InputDataResult> mInputDataResults;
@@ -118,7 +139,7 @@ public class DecryptListFragment
/** /**
* Creates new instance of this fragment * Creates new instance of this fragment
*/ */
public static DecryptListFragment newInstance(ArrayList<Uri> uris, boolean canDelete) { public static DecryptListFragment newInstance(@NonNull ArrayList<Uri> uris, boolean canDelete) {
DecryptListFragment frag = new DecryptListFragment(); DecryptListFragment frag = new DecryptListFragment();
Bundle args = new Bundle(); Bundle args = new Bundle();
@@ -175,9 +196,12 @@ public class DecryptListFragment
outState.putParcelable(ARG_RESULTS, new ParcelableHashMap<>(results)); outState.putParcelable(ARG_RESULTS, new ParcelableHashMap<>(results));
outState.putParcelable(ARG_OUTPUT_URIS, new ParcelableHashMap<>(mInputDataResults)); outState.putParcelable(ARG_OUTPUT_URIS, new ParcelableHashMap<>(mInputDataResults));
outState.putParcelableArrayList(ARG_CANCELLED_URIS, mCancelledInputUris); outState.putParcelableArrayList(ARG_CANCELLED_URIS, mCancelledInputUris);
outState.putParcelable(ARG_CURRENT_URI, mCurrentInputUri);
outState.putBoolean(ARG_CAN_DELETE, mCanDelete); 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 @Override
@@ -189,20 +213,21 @@ public class DecryptListFragment
ArrayList<Uri> inputUris = getArguments().getParcelableArrayList(ARG_INPUT_URIS); ArrayList<Uri> inputUris = getArguments().getParcelableArrayList(ARG_INPUT_URIS);
ArrayList<Uri> cancelledUris = args.getParcelableArrayList(ARG_CANCELLED_URIS); ArrayList<Uri> cancelledUris = args.getParcelableArrayList(ARG_CANCELLED_URIS);
ParcelableHashMap<Uri,InputDataResult> results = args.getParcelable(ARG_RESULTS); ParcelableHashMap<Uri,InputDataResult> results = args.getParcelable(ARG_RESULTS);
Uri currentInputUri = args.getParcelable(ARG_CURRENT_URI);
mCanDelete = args.getBoolean(ARG_CAN_DELETE, false); mCanDelete = args.getBoolean(ARG_CAN_DELETE, false);
displayInputUris(inputUris, currentInputUri, cancelledUris, displayInputUris(inputUris, cancelledUris,
results != null ? results.getMap() : null results != null ? results.getMap() : null
); );
} }
private void displayInputUris(ArrayList<Uri> inputUris, Uri currentInputUri, private void displayInputUris(
ArrayList<Uri> cancelledUris, HashMap<Uri,InputDataResult> results) { ArrayList<Uri> inputUris,
ArrayList<Uri> cancelledUris,
HashMap<Uri,InputDataResult> results) {
mInputUris = inputUris; mInputUris = inputUris;
mCurrentInputUri = currentInputUri; mCurrentInputUri = null;
mInputDataResults = results != null ? results : new HashMap<Uri,InputDataResult>(inputUris.size()); mInputDataResults = results != null ? results : new HashMap<Uri,InputDataResult>(inputUris.size());
mCancelledInputUris = cancelledUris != null ? cancelledUris : new ArrayList<Uri>(); mCancelledInputUris = cancelledUris != null ? cancelledUris : new ArrayList<Uri>();
@@ -211,30 +236,23 @@ public class DecryptListFragment
for (final Uri uri : inputUris) { for (final Uri uri : inputUris) {
mAdapter.add(uri); mAdapter.add(uri);
if (uri.equals(mCurrentInputUri)) { boolean uriIsCancelled = mCancelledInputUris.contains(uri);
if (uriIsCancelled) {
mAdapter.setCancelled(uri, true);
continue; continue;
} }
if (mCancelledInputUris.contains(uri)) { boolean uriHasResult = results != null && results.containsKey(uri);
mAdapter.setCancelled(uri, new OnClickListener() { if (uriHasResult) {
@Override
public void onClick(View v) {
retryUri(uri);
}
});
continue;
}
if (results != null && results.containsKey(uri)) {
processResult(uri); processResult(uri);
} else { continue;
mPendingInputUris.add(uri);
} }
mPendingInputUris.add(uri);
} }
if (mCurrentInputUri == null) { // check if there are any pending input uris
cryptoOperation(); cryptoOperation();
}
} }
@Override @Override
@@ -364,12 +382,7 @@ public class DecryptListFragment
mCurrentInputUri = null; mCurrentInputUri = null;
mCancelledInputUris.add(uri); mCancelledInputUris.add(uri);
mAdapter.setCancelled(uri, new OnClickListener() { mAdapter.setCancelled(uri, true);
@Override
public void onClick(View v) {
retryUri(uri);
}
});
cryptoOperation(); cryptoOperation();
@@ -463,8 +476,8 @@ public class DecryptListFragment
mPendingInputUris.add(uri); mPendingInputUris.add(uri);
mAdapter.resetItemData(uri); mAdapter.resetItemData(uri);
// check if there are any pending input uris
cryptoOperation(); cryptoOperation();
} }
public void displayBottomSheet(final InputDataResult result, final int index) { public void displayBottomSheet(final InputDataResult result, final int index) {
@@ -585,6 +598,11 @@ public class DecryptListFragment
@Override @Override
public InputDataParcel createOperationInput() { public InputDataParcel createOperationInput() {
Activity activity = getActivity();
if (activity == null) {
return null;
}
if (mCurrentInputUri == null) { if (mCurrentInputUri == null) {
if (mPendingInputUris.isEmpty()) { if (mPendingInputUris.isEmpty()) {
// nothing left to do // nothing left to do
@@ -594,7 +612,11 @@ public class DecryptListFragment
mCurrentInputUri = mPendingInputUris.remove(0); 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() PgpDecryptVerifyInputParcel decryptInput = new PgpDecryptVerifyInputParcel()
.setAllowSymmetricDecryption(true); .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 @Override
public boolean onMenuItemClick(MenuItem menuItem) { public boolean onMenuItemClick(MenuItem menuItem) {
if (mAdapter.mMenuClickedModel == null || !mAdapter.mMenuClickedModel.hasResult()) { if (mAdapter.mMenuClickedModel == null || !mAdapter.mMenuClickedModel.hasResult()) {
@@ -780,8 +883,10 @@ public class DecryptListFragment
return false; return false;
} }
ViewModel viewModel = (ViewModel) o; ViewModel viewModel = (ViewModel) o;
return !(mInputUri != null ? !mInputUri.equals(viewModel.mInputUri) if (mInputUri == null) {
: viewModel.mInputUri != null); return viewModel.mInputUri == null;
}
return mInputUri.equals(viewModel.mInputUri);
} }
// Depends on inputUri only // Depends on inputUri only
@@ -1017,10 +1122,19 @@ public class DecryptListFragment
notifyItemChanged(pos); notifyItemChanged(pos);
} }
public void setCancelled(Uri uri, OnClickListener retryListener) { public void setCancelled(final Uri uri, boolean isCancelled) {
ViewModel newModel = new ViewModel(uri); ViewModel newModel = new ViewModel(uri);
int pos = mDataset.indexOf(newModel); 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); notifyItemChanged(pos);
} }

View File

@@ -28,6 +28,7 @@ import android.os.Bundle;
import android.support.v4.app.FragmentTransaction; import android.support.v4.app.FragmentTransaction;
import android.widget.Toast; import android.widget.Toast;
import org.apache.james.mime4j.util.MimeUtil;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.intents.OpenKeychainIntents; import org.sufficientlysecure.keychain.intents.OpenKeychainIntents;
@@ -59,12 +60,15 @@ public class EncryptTextActivity extends EncryptActivity {
extras = new Bundle(); extras = new Bundle();
} }
String textData = extras.getString(EXTRA_TEXT);
boolean returnProcessText = false;
// When sending to OpenKeychain Encrypt via share menu // When sending to OpenKeychain Encrypt via share menu
if (Intent.ACTION_SEND.equals(action) && type != null) { if (Intent.ACTION_SEND.equals(action) && type != null) {
Log.logDebugBundle(extras, "extras"); Log.logDebugBundle(extras, "extras");
// When sending to OpenKeychain Encrypt via share menu // 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(); Toast.makeText(this, R.string.toast_wrong_mimetype, Toast.LENGTH_LONG).show();
finish(); finish();
return; return;
@@ -94,12 +98,33 @@ public class EncryptTextActivity extends EncryptActivity {
} }
// handle like normal text encryption, override action and extras to later // handle like normal text encryption, override action and extras to later
// executeServiceMethod ACTION_ENCRYPT_TEXT in main actions // 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) { if (textData == null) {
textData = ""; textData = "";
} }
@@ -107,7 +132,7 @@ public class EncryptTextActivity extends EncryptActivity {
if (savedInstanceState == null) { if (savedInstanceState == null) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
EncryptTextFragment encryptFragment = EncryptTextFragment.newInstance(textData); EncryptTextFragment encryptFragment = EncryptTextFragment.newInstance(textData, returnProcessText);
transaction.replace(R.id.encrypt_text_container, encryptFragment); transaction.replace(R.id.encrypt_text_container, encryptFragment);
transaction.commit(); transaction.commit();
} }

View File

@@ -56,8 +56,10 @@ public class EncryptTextFragment
public static final String ARG_TEXT = "text"; public static final String ARG_TEXT = "text";
public static final String ARG_USE_COMPRESSION = "use_compression"; 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 mShareAfterEncrypt;
private boolean mReturnProcessTextAfterEncrypt;
private boolean mUseCompression; private boolean mUseCompression;
private boolean mHiddenRecipients = false; private boolean mHiddenRecipients = false;
@@ -66,11 +68,12 @@ public class EncryptTextFragment
/** /**
* Creates new instance of this fragment * Creates new instance of this fragment
*/ */
public static EncryptTextFragment newInstance(String text) { public static EncryptTextFragment newInstance(String text, boolean returnProcessTextAfterEncrypt) {
EncryptTextFragment frag = new EncryptTextFragment(); EncryptTextFragment frag = new EncryptTextFragment();
Bundle args = new Bundle(); Bundle args = new Bundle();
args.putString(ARG_TEXT, text); args.putString(ARG_TEXT, text);
args.putBoolean(ARG_RETURN_PROCESS_TEXT, returnProcessTextAfterEncrypt);
frag.setArguments(args); frag.setArguments(args);
return frag; return frag;
@@ -128,6 +131,7 @@ public class EncryptTextFragment
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
if (savedInstanceState == null) { if (savedInstanceState == null) {
mMessage = getArguments().getString(ARG_TEXT); mMessage = getArguments().getString(ARG_TEXT);
mReturnProcessTextAfterEncrypt = getArguments().getBoolean(ARG_RETURN_PROCESS_TEXT, false);
} }
Preferences prefs = Preferences.getPreferences(getActivity()); Preferences prefs = Preferences.getPreferences(getActivity());
@@ -151,6 +155,12 @@ public class EncryptTextFragment
inflater.inflate(R.menu.encrypt_text_fragment, menu); inflater.inflate(R.menu.encrypt_text_fragment, menu);
menu.findItem(R.id.check_enable_compression).setChecked(mUseCompression); 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 @Override
@@ -177,6 +187,11 @@ public class EncryptTextFragment
cryptoOperation(new CryptoInputParcel(new Date())); cryptoOperation(new CryptoInputParcel(new Date()));
break; break;
} }
case R.id.encrypt_paste: {
hideKeyboard();
cryptoOperation(new CryptoInputParcel(new Date()));
break;
}
default: { default: {
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }
@@ -328,6 +343,11 @@ public class EncryptTextFragment
// Share encrypted message/file // Share encrypted message/file
startActivity(Intent.createChooser(createSendIntent(result.getResultBytes()), startActivity(Intent.createChooser(createSendIntent(result.getResultBytes()),
getString(R.string.title_share_message))); 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 { } else {
// Copy to clipboard // Copy to clipboard
copyToClipboard(result); copyToClipboard(result);

View File

@@ -29,6 +29,9 @@ import android.view.ViewGroup;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; 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; import org.sufficientlysecure.keychain.util.FileHelper;
public class ImportKeysFileFragment extends Fragment { public class ImportKeysFileFragment extends Fragment {
@@ -78,12 +81,16 @@ public class ImportKeysFileFragment extends Fragment {
String sendText = ""; String sendText = "";
if (clipboardText != null) { if (clipboardText != null) {
sendText = clipboardText.toString(); 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)); mImportActivity.loadCallback(new ImportKeysListFragment.BytesLoaderState(sendText.getBytes(), null));
} }
} }
}); });
return view; return view;
} }

View File

@@ -17,6 +17,11 @@
package org.sufficientlysecure.keychain.ui; package org.sufficientlysecure.keychain.ui;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import android.app.Activity; import android.app.Activity;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; 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.ImportKeysAdapter;
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListCloudLoader; import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListCloudLoader;
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListLoader; 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.Log;
import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize; import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize;
import org.sufficientlysecure.keychain.util.ParcelableProxy; import org.sufficientlysecure.keychain.util.ParcelableProxy;
import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.Preferences;
import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; 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 public class ImportKeysListFragment extends ListFragment implements
LoaderManager.LoaderCallbacks<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> { LoaderManager.LoaderCallbacks<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> {
@@ -180,8 +175,8 @@ public class ImportKeysListFragment extends ListFragment implements
} }
static public class BytesLoaderState extends LoaderState { static public class BytesLoaderState extends LoaderState {
byte[] mKeyBytes; public byte[] mKeyBytes;
Uri mDataUri; public Uri mDataUri;
BytesLoaderState(byte[] keyBytes, Uri dataUri) { BytesLoaderState(byte[] keyBytes, Uri dataUri) {
mKeyBytes = keyBytes; mKeyBytes = keyBytes;
@@ -305,9 +300,7 @@ public class ImportKeysListFragment extends ListFragment implements
onCreateLoader(int id, Bundle args) { onCreateLoader(int id, Bundle args) {
switch (id) { switch (id) {
case LOADER_ID_BYTES: { case LOADER_ID_BYTES: {
BytesLoaderState ls = (BytesLoaderState) mLoaderState; return new ImportKeysListLoader(mActivity, (BytesLoaderState) mLoaderState);
InputData inputData = getInputData(ls.mKeyBytes, ls.mDataUri);
return new ImportKeysListLoader(mActivity, inputData);
} }
case LOADER_ID_CLOUD: { case LOADER_ID_CLOUD: {
CloudLoaderState ls = (CloudLoaderState) mLoaderState; 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;
}
} }

View File

@@ -87,12 +87,7 @@ public class ImportKeysProxyActivity extends FragmentActivity
processScannedContent(dataUri); processScannedContent(dataUri);
} else if (ACTION_SCAN_WITH_RESULT.equals(action) } else if (ACTION_SCAN_WITH_RESULT.equals(action)
|| ACTION_SCAN_IMPORT.equals(action) || ACTION_QR_CODE_API.equals(action)) { || ACTION_SCAN_IMPORT.equals(action) || ACTION_QR_CODE_API.equals(action)) {
IntentIntegrator integrator = new IntentIntegrator(this); new IntentIntegrator(this).setCaptureActivity(QrCodeCaptureActivity.class).initiateScan();
integrator.setDesiredBarcodeFormats(IntentIntegrator.QR_CODE_TYPES)
.setPrompt(getString(R.string.import_qr_code_text))
.setResultDisplayDuration(0);
integrator.setOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
integrator.initiateScan();
} else if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) { } else if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) {
// Check to see if the Activity started due to an Android Beam // Check to see if the Activity started due to an Android Beam
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {

View File

@@ -30,6 +30,7 @@ import android.graphics.Color;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.LoaderManager; import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader; import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader; import android.support.v4.content.Loader;
@@ -46,14 +47,17 @@ import android.view.View.OnClickListener;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.AbsListView.MultiChoiceModeListener; import android.widget.AbsListView.MultiChoiceModeListener;
import android.widget.AdapterView; import android.widget.AdapterView;
import android.widget.Button;
import android.widget.ListView; import android.widget.ListView;
import android.widget.TextView; import android.widget.TextView;
import android.widget.ViewAnimator;
import com.getbase.floatingactionbutton.FloatingActionButton; import com.getbase.floatingactionbutton.FloatingActionButton;
import com.getbase.floatingactionbutton.FloatingActionsMenu; import com.getbase.floatingactionbutton.FloatingActionsMenu;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; 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.ConsolidateResult;
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult; 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.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainDatabase; import org.sufficientlysecure.keychain.provider.KeychainDatabase;
import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.BenchmarkInputParcel;
import org.sufficientlysecure.keychain.service.ConsolidateInputParcel; import org.sufficientlysecure.keychain.service.ConsolidateInputParcel;
import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter; 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 // saves the mode object for multiselect, needed for reset at some point
private ActionMode mActionMode = null; private ActionMode mActionMode = null;
private Button vSearchButton;
private ViewAnimator vSearchContainer;
private String mQuery; private String mQuery;
private FloatingActionsMenu mFab; private FloatingActionsMenu mFab;
@@ -161,7 +168,9 @@ public class KeyListFragment extends LoaderFragment
super.onActivityCreated(savedInstanceState); super.onActivityCreated(savedInstanceState);
// show app name instead of "keys" from nav drawer // 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.setOnItemClickListener(this);
mStickyList.setAreHeadersSticky(true); 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 // Adds an empty footer view so that the Floating Action Button won't block content
// in last few rows. // in last few rows.
View footer = new View(getActivity()); View footer = new View(activity);
int spacing = (int) android.util.TypedValue.applyDimension( int spacing = (int) android.util.TypedValue.applyDimension(
android.util.TypedValue.COMPLEX_UNIT_DIP, 72, getResources().getDisplayMetrics() android.util.TypedValue.COMPLEX_UNIT_DIP, 72, getResources().getDisplayMetrics()
@@ -194,7 +203,7 @@ public class KeyListFragment extends LoaderFragment
@Override @Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) { 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); inflater.inflate(R.menu.key_list_multi, menu);
mActionMode = mode; mActionMode = mode;
return true; return true;
@@ -234,7 +243,7 @@ public class KeyListFragment extends LoaderFragment
@Override @Override
public void onItemCheckedStateChanged(ActionMode mode, int position, long id, public void onItemCheckedStateChanged(ActionMode mode, int position, long id,
boolean checked) { boolean checked) {
if (checked) { if (checked) {
mAdapter.setNewSelection(position, true); mAdapter.setNewSelection(position, true);
} else { } else {
@@ -254,8 +263,21 @@ public class KeyListFragment extends LoaderFragment
// Start out with a progress indicator. // Start out with a progress indicator.
setContentShown(false); 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. // 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); mStickyList.setAdapter(mAdapter);
// Prepare the loader. Either re-connect with an existing one, // Prepare the loader. Either re-connect with an existing one,
@@ -263,8 +285,20 @@ public class KeyListFragment extends LoaderFragment
getLoaderManager().initLoader(0, null, this); 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 = 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 @Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) { public Loader<Cursor> onCreateLoader(int id, Bundle args) {
@@ -318,9 +352,6 @@ public class KeyListFragment extends LoaderFragment
mStickyList.setAdapter(mAdapter); 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 // end action mode, if any
if (mActionMode != null) { if (mActionMode != null) {
mActionMode.finish(); mActionMode.finish();
@@ -386,6 +417,7 @@ public class KeyListFragment extends LoaderFragment
if (Constants.DEBUG) { if (Constants.DEBUG) {
menu.findItem(R.id.menu_key_list_debug_cons).setVisible(true); 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_read).setVisible(true);
menu.findItem(R.id.menu_key_list_debug_write).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); menu.findItem(R.id.menu_key_list_debug_first_time).setVisible(true);
@@ -469,6 +501,10 @@ public class KeyListFragment extends LoaderFragment
consolidate(); consolidate();
return true; return true;
case R.id.menu_key_list_debug_bench:
benchmark();
return true;
default: default:
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }
@@ -482,17 +518,25 @@ public class KeyListFragment extends LoaderFragment
@Override @Override
public boolean onQueryTextChange(String s) { public boolean onQueryTextChange(String s) {
Log.d(Constants.TAG, "onQueryTextChange s:" + s); Log.d(Constants.TAG, "onQueryTextChange s:" + s);
// Called when the action bar search text has changed. Update // Called when the action bar search text has changed. Update the
// the search filter, and restart the loader to do a new query // search filter, and restart the loader to do a new query with this
// with this filter. // filter.
// If the nav drawer is opened, onQueryTextChange("") is executed. // If the nav drawer is opened, onQueryTextChange("") is executed.
// This hack prevents restarting the loader. // This hack prevents restarting the loader.
// TODO: better way to fix this? if (!s.equals(mQuery)) {
String tmp = (mQuery == null) ? "" : mQuery;
if (!s.equals(tmp)) {
mQuery = s; mQuery = s;
getLoaderManager().restartLoader(0, null, this); 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; return true;
} }
@@ -559,8 +603,8 @@ public class KeyListFragment extends LoaderFragment
mKeyserver = cloudPrefs.keyserver; mKeyserver = cloudPrefs.keyserver;
} }
mImportOpHelper = new CryptoOperationHelper<>(1, this, mImportOpHelper = new CryptoOperationHelper<>(1, this, this, R.string.progress_updating);
this, R.string.progress_updating); mImportOpHelper.setProgressCancellable(true);
mImportOpHelper.cryptoOperation(); mImportOpHelper.cryptoOperation();
} }
@@ -601,6 +645,43 @@ public class KeyListFragment extends LoaderFragment
mConsolidateOpHelper.cryptoOperation(); mConsolidateOpHelper.cryptoOperation();
} }
private void benchmark() {
CryptoOperationHelper.Callback<BenchmarkInputParcel, BenchmarkResult> callback
= new CryptoOperationHelper.Callback<BenchmarkInputParcel, BenchmarkResult>() {
@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 @Override
public void onActivityResult(int requestCode, int resultCode, Intent data) { public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (mImportOpHelper != null) { if (mImportOpHelper != null) {

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de> * Copyright (C) 2012-2015 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com> * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com>
* Copyright (C) 2015 Kai Jiang <jiangkai@gmail.com> * Copyright (C) 2015 Kai Jiang <jiangkai@gmail.com>
* *
@@ -27,13 +27,13 @@ import android.support.v4.app.FragmentManager.OnBackStackChangedListener;
import android.support.v4.app.FragmentTransaction; import android.support.v4.app.FragmentTransaction;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
import android.view.View; import android.view.View;
import android.widget.AdapterView;
import com.mikepenz.community_material_typeface_library.CommunityMaterial; 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.google_material_typeface_library.GoogleMaterial;
import com.mikepenz.iconics.typeface.FontAwesome;
import com.mikepenz.materialdrawer.Drawer; import com.mikepenz.materialdrawer.Drawer;
import com.mikepenz.materialdrawer.DrawerBuilder; import com.mikepenz.materialdrawer.DrawerBuilder;
import com.mikepenz.materialdrawer.model.DividerDrawerItem;
import com.mikepenz.materialdrawer.model.PrimaryDrawerItem; import com.mikepenz.materialdrawer.model.PrimaryDrawerItem;
import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem; import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem;
@@ -75,25 +75,23 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac
.withToolbar(mToolbar) .withToolbar(mToolbar)
.addDrawerItems( .addDrawerItems(
new PrimaryDrawerItem().withName(R.string.nav_keys).withIcon(CommunityMaterial.Icon.cmd_key) 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) 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) 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) new PrimaryDrawerItem().withName(R.string.nav_backup).withIcon(CommunityMaterial.Icon.cmd_backup_restore)
.withIdentifier(ID_BACKUP).withCheckable(false) .withIdentifier(ID_BACKUP).withSelectable(false),
) new DividerDrawerItem(),
.addStickyDrawerItems( new PrimaryDrawerItem().withName(R.string.menu_preferences).withIcon(GoogleMaterial.Icon.gmd_settings).withIdentifier(ID_SETTINGS).withSelectable(false),
// display and stick on bottom of drawer new PrimaryDrawerItem().withName(R.string.menu_help).withIcon(CommunityMaterial.Icon.cmd_help_circle).withIdentifier(ID_HELP).withSelectable(false)
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)
) )
.withOnDrawerItemClickListener(new Drawer.OnDrawerItemClickListener() { .withOnDrawerItemClickListener(new Drawer.OnDrawerItemClickListener() {
@Override @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) { if (drawerItem != null) {
Intent intent = null; Intent intent = null;
switch(drawerItem.getIdentifier()) { switch (drawerItem.getIdentifier()) {
case ID_KEYS: case ID_KEYS:
onKeysSelected(); onKeysSelected();
break; break;
@@ -182,28 +180,28 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac
private void onKeysSelected() { private void onKeysSelected() {
mToolbar.setTitle(R.string.app_name); mToolbar.setTitle(R.string.app_name);
mDrawer.setSelectionByIdentifier(ID_KEYS, false); mDrawer.setSelection(ID_KEYS, false);
Fragment frag = new KeyListFragment(); Fragment frag = new KeyListFragment();
setFragment(frag, false); setFragment(frag, false);
} }
private void onEnDecryptSelected() { private void onEnDecryptSelected() {
mToolbar.setTitle(R.string.nav_encrypt_decrypt); mToolbar.setTitle(R.string.nav_encrypt_decrypt);
mDrawer.setSelectionByIdentifier(ID_ENCRYPT_DECRYPT, false); mDrawer.setSelection(ID_ENCRYPT_DECRYPT, false);
Fragment frag = new EncryptDecryptFragment(); Fragment frag = new EncryptDecryptFragment();
setFragment(frag, true); setFragment(frag, true);
} }
private void onAppsSelected() { private void onAppsSelected() {
mToolbar.setTitle(R.string.nav_apps); mToolbar.setTitle(R.string.nav_apps);
mDrawer.setSelectionByIdentifier(ID_APPS, false); mDrawer.setSelection(ID_APPS, false);
Fragment frag = new AppsListFragment(); Fragment frag = new AppsListFragment();
setFragment(frag, true); setFragment(frag, true);
} }
private void onBackupSelected() { private void onBackupSelected() {
mToolbar.setTitle(R.string.nav_backup); mToolbar.setTitle(R.string.nav_backup);
mDrawer.setSelectionByIdentifier(ID_BACKUP, false); mDrawer.setSelection(ID_BACKUP, false);
Fragment frag = new BackupRestoreFragment(); Fragment frag = new BackupRestoreFragment();
setFragment(frag, true); 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 // make sure the selected icon is the one shown at this point
if (frag instanceof KeyListFragment) { if (frag instanceof KeyListFragment) {
mToolbar.setTitle(R.string.app_name); 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) { } else if (frag instanceof EncryptDecryptFragment) {
mToolbar.setTitle(R.string.nav_encrypt_decrypt); 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) { } else if (frag instanceof AppsListFragment) {
mToolbar.setTitle(R.string.nav_apps); 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) { } else if (frag instanceof BackupRestoreFragment) {
mToolbar.setTitle(R.string.nav_backup); mToolbar.setTitle(R.string.nav_backup);
mDrawer.setSelection(mDrawer.getPositionFromIdentifier(ID_BACKUP), false); mDrawer.setSelection(mDrawer.getPosition(ID_BACKUP), false);
} }
} }

View File

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

View File

@@ -54,14 +54,8 @@ import java.util.List;
public class SettingsActivity extends AppCompatPreferenceActivity { 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; public static final int REQUEST_CODE_KEYSERVER_PREF = 0x00007005;
private PreferenceScreen mKeyServerPreference = null;
private static Preferences sPreferences; private static Preferences sPreferences;
private ThemeChanger mThemeChanger; private ThemeChanger mThemeChanger;
@@ -74,49 +68,6 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setupToolbar(); 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 @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 * This fragment shows the keyserver/contacts sync preferences
*/ */
@@ -576,7 +510,6 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
return PassphrasePrefsFragment.class.getName().equals(fragmentName) return PassphrasePrefsFragment.class.getName().equals(fragmentName)
|| CloudSearchPrefsFragment.class.getName().equals(fragmentName) || CloudSearchPrefsFragment.class.getName().equals(fragmentName)
|| ProxyPrefsFragment.class.getName().equals(fragmentName) || ProxyPrefsFragment.class.getName().equals(fragmentName)
|| GuiPrefsFragment.class.getName().equals(fragmentName)
|| SyncPrefsFragment.class.getName().equals(fragmentName) || SyncPrefsFragment.class.getName().equals(fragmentName)
|| ExperimentalPrefsFragment.class.getName().equals(fragmentName) || ExperimentalPrefsFragment.class.getName().equals(fragmentName)
|| super.isValidFragment(fragmentName); || super.isValidFragment(fragmentName);

View File

@@ -38,6 +38,7 @@ import android.os.Handler;
import android.provider.ContactsContract; import android.provider.ContactsContract;
import android.support.design.widget.AppBarLayout; import android.support.design.widget.AppBarLayout;
import android.support.design.widget.CollapsingToolbarLayout; import android.support.design.widget.CollapsingToolbarLayout;
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.FloatingActionButton; import android.support.design.widget.FloatingActionButton;
import android.support.v4.app.ActivityCompat; import android.support.v4.app.ActivityCompat;
import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentManager;
@@ -869,7 +870,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements
mActionEncryptFile.setVisibility(View.INVISIBLE); mActionEncryptFile.setVisibility(View.INVISIBLE);
mActionEncryptText.setVisibility(View.INVISIBLE); mActionEncryptText.setVisibility(View.INVISIBLE);
mActionNfc.setVisibility(View.INVISIBLE); mActionNfc.setVisibility(View.INVISIBLE);
mFab.setVisibility(View.GONE); hideFab();
mQrCodeLayout.setVisibility(View.GONE); mQrCodeLayout.setVisibility(View.GONE);
} else if (mIsExpired) { } else if (mIsExpired) {
if (mIsSecret) { if (mIsSecret) {
@@ -885,7 +886,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements
mActionEncryptFile.setVisibility(View.INVISIBLE); mActionEncryptFile.setVisibility(View.INVISIBLE);
mActionEncryptText.setVisibility(View.INVISIBLE); mActionEncryptText.setVisibility(View.INVISIBLE);
mActionNfc.setVisibility(View.INVISIBLE); mActionNfc.setVisibility(View.INVISIBLE);
mFab.setVisibility(View.GONE); hideFab();
mQrCodeLayout.setVisibility(View.GONE); mQrCodeLayout.setVisibility(View.GONE);
} else if (mIsSecret) { } else if (mIsSecret) {
mStatusText.setText(R.string.view_key_my_key); mStatusText.setText(R.string.view_key_my_key);
@@ -927,7 +928,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements
} else { } else {
mActionNfc.setVisibility(View.GONE); mActionNfc.setVisibility(View.GONE);
} }
mFab.setVisibility(View.VISIBLE); showFab();
// noinspection deprecation (no getDrawable with theme at current minApi level 15!) // noinspection deprecation (no getDrawable with theme at current minApi level 15!)
mFab.setImageDrawable(getResources().getDrawable(R.drawable.ic_repeat_white_24dp)); mFab.setImageDrawable(getResources().getDrawable(R.drawable.ic_repeat_white_24dp));
} else { } else {
@@ -944,7 +945,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements
color = getResources().getColor(R.color.key_flag_green); color = getResources().getColor(R.color.key_flag_green);
photoTask.execute(mMasterKeyId); photoTask.execute(mMasterKeyId);
mFab.setVisibility(View.GONE); hideFab();
} else { } else {
mStatusText.setText(R.string.view_key_unverified); mStatusText.setText(R.string.view_key_unverified);
mStatusImage.setVisibility(View.VISIBLE); mStatusImage.setVisibility(View.VISIBLE);
@@ -952,7 +953,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements
State.UNVERIFIED, R.color.icons, true); State.UNVERIFIED, R.color.icons, true);
color = getResources().getColor(R.color.key_flag_orange); 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 @Override
public void onLoaderReset(Loader<Cursor> loader) { public void onLoaderReset(Loader<Cursor> loader) {

View File

@@ -17,6 +17,14 @@
package org.sufficientlysecure.keychain.ui.adapter; 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.content.Context;
import android.support.v4.content.AsyncTaskLoader; import android.support.v4.content.AsyncTaskLoader;
import android.support.v4.util.LongSparseArray; 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.operations.results.OperationResult;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing.IteratorWithIOThrow; 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.InputData;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.PositionAwareInputStream; import org.sufficientlysecure.keychain.util.PositionAwareInputStream;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.util.ArrayList;
public class ImportKeysListLoader public class ImportKeysListLoader
extends AsyncTaskLoader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> { extends AsyncTaskLoader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> {
final Context mContext; final Context mContext;
final InputData mInputData; final BytesLoaderState mLoaderState;
ArrayList<ImportKeysListEntry> mData = new ArrayList<>(); ArrayList<ImportKeysListEntry> mData = new ArrayList<>();
LongSparseArray<ParcelableKeyRing> mParcelableRings = new LongSparseArray<>(); LongSparseArray<ParcelableKeyRing> mParcelableRings = new LongSparseArray<>();
AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> mEntryListWrapper; AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> mEntryListWrapper;
public ImportKeysListLoader(Context context, InputData inputData) { public ImportKeysListLoader(Context context, BytesLoaderState inputData) {
super(context); super(context);
this.mContext = context; this.mContext = context;
this.mInputData = inputData; this.mLoaderState = inputData;
} }
@Override @Override
@@ -62,12 +68,13 @@ public class ImportKeysListLoader
GetKeyResult getKeyResult = new GetKeyResult(GetKeyResult.RESULT_OK, null); GetKeyResult getKeyResult = new GetKeyResult(GetKeyResult.RESULT_OK, null);
mEntryListWrapper = new AsyncTaskResultWrapper<>(mData, getKeyResult); mEntryListWrapper = new AsyncTaskResultWrapper<>(mData, getKeyResult);
if (mInputData == null) { if (mLoaderState == null) {
Log.e(Constants.TAG, "Input data is null!"); Log.e(Constants.TAG, "Input data is null!");
return mEntryListWrapper; return mEntryListWrapper;
} }
generateListOfKeyrings(mInputData); InputData inputData = getInputData(getContext(), mLoaderState);
generateListOfKeyrings(inputData);
return mEntryListWrapper; return mEntryListWrapper;
} }
@@ -99,12 +106,7 @@ public class ImportKeysListLoader
return mParcelableRings; return mParcelableRings;
} }
/** /** Reads all PGPKeyRing objects from the bytes of an InputData object. */
* Reads all PGPKeyRing objects from input
*
* @param inputData
* @return
*/
private void generateListOfKeyrings(InputData inputData) { private void generateListOfKeyrings(InputData inputData) {
PositionAwareInputStream progressIn = new PositionAwareInputStream( PositionAwareInputStream progressIn = new PositionAwareInputStream(
inputData.getInputStream()); 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;
}
} }

View File

@@ -1,13 +1,8 @@
package org.sufficientlysecure.keychain.ui.adapter; package org.sufficientlysecure.keychain.ui.adapter;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import android.content.Context; import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.support.v7.internal.widget.AdapterViewCompat;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.AdapterView; import android.widget.AdapterView;
@@ -18,6 +13,10 @@ import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
public class KeySelectableAdapter extends KeyAdapter implements OnItemClickListener { public class KeySelectableAdapter extends KeyAdapter implements OnItemClickListener {

View File

@@ -24,6 +24,7 @@ import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.support.v4.content.CursorLoader; import android.support.v4.content.CursorLoader;
import android.util.Log; import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@@ -228,9 +229,11 @@ public class LinkedIdsAdapter extends UserAttributesAdapter {
} }
public void seekAttention() { public void seekAttention() {
ObjectAnimator anim = SubtleAttentionSeeker.tintText(vComment, 1000); if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
anim.setStartDelay(200); ObjectAnimator anim = SubtleAttentionSeeker.tintText(vComment, 1000);
anim.start(); anim.setStartDelay(200);
anim.start();
}
} }
} }

View File

@@ -84,6 +84,7 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
public static final int REQUEST_CODE_RETRY_UPLOAD = 4; public static final int REQUEST_CODE_RETRY_UPLOAD = 4;
private Integer mProgressMessageResource; private Integer mProgressMessageResource;
private boolean mCancellable = false;
private FragmentActivity mActivity; private FragmentActivity mActivity;
private Fragment mFragment; private Fragment mFragment;
@@ -118,6 +119,10 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
mProgressMessageResource = id; mProgressMessageResource = id;
} }
public void setProgressCancellable(boolean cancellable) {
mCancellable = cancellable;
}
private void initiateInputActivity(RequiredInputParcel requiredInput, private void initiateInputActivity(RequiredInputParcel requiredInput,
CryptoInputParcel cryptoInputParcel) { CryptoInputParcel cryptoInputParcel) {
@@ -311,7 +316,7 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
if (mProgressMessageResource != null) { if (mProgressMessageResource != null) {
saveHandler.showProgressDialog( saveHandler.showProgressDialog(
activity.getString(mProgressMessageResource), activity.getString(mProgressMessageResource),
ProgressDialog.STYLE_HORIZONTAL, false); ProgressDialog.STYLE_HORIZONTAL, mCancellable);
} }
activity.startService(intent); activity.startService(intent);

View File

@@ -23,11 +23,13 @@ import android.database.Cursor;
import android.graphics.Rect; import android.graphics.Rect;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentActivity;
import android.support.v4.app.LoaderManager; import android.support.v4.app.LoaderManager;
import android.support.v4.app.LoaderManager.LoaderCallbacks; import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.CursorLoader; import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader; import android.support.v4.content.Loader;
import android.text.TextUtils;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
@@ -46,14 +48,14 @@ import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter.KeyItem;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
public class EncryptKeyCompletionView extends TokenCompleteTextView public class EncryptKeyCompletionView extends TokenCompleteTextView<KeyItem>
implements LoaderCallbacks<Cursor> { implements LoaderCallbacks<Cursor> {
public static final String ARG_QUERY = "query"; public static final String ARG_QUERY = "query";
private KeyAdapter mAdapter; private KeyAdapter mAdapter;
private LoaderManager mLoaderManager; private LoaderManager mLoaderManager;
private String mPrefix; private CharSequence mPrefix;
public EncryptKeyCompletionView(Context context) { public EncryptKeyCompletionView(Context context) {
super(context); super(context);
@@ -79,30 +81,27 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView
} }
@Override @Override
public void setPrefix(String p) { public void setPrefix(CharSequence p) {
// this one is private in the superclass, but we need it here // this one is private in the superclass, but we need it here
mPrefix = p; mPrefix = p;
super.setPrefix(p); super.setPrefix(p);
} }
@Override @Override
protected View getViewForObject(Object object) { protected View getViewForObject(KeyItem keyItem) {
if (object instanceof KeyItem) { LayoutInflater l = LayoutInflater.from(getContext());
LayoutInflater l = LayoutInflater.from(getContext()); View view = l.inflate(R.layout.recipient_box_entry, null);
View view = l.inflate(R.layout.recipient_box_entry, null); ((TextView) view.findViewById(android.R.id.text1)).setText(keyItem.getReadableName());
((TextView) view.findViewById(android.R.id.text1)).setText(((KeyItem) object).getReadableName()); return view;
return view;
}
return null;
} }
@Override @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 // TODO: We could try to automagically download the key if it's unknown but a key id
/*if (completionText.startsWith("0x")) { /*if (completionText.startsWith("0x")) {
}*/ }*/
return ""; return null;
} }
@Override @Override
@@ -128,7 +127,7 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView
// These are the rows that we will retrieve. // These are the rows that we will retrieve.
Uri baseUri = KeyRings.buildUnifiedKeyRingsUri(); Uri baseUri = KeyRings.buildUnifiedKeyRingsUri();
String[] projection = KeyAdapter.getProjectionWith(new String[] { String[] projection = KeyAdapter.getProjectionWith(new String[]{
KeychainContract.KeyRings.HAS_ENCRYPT, KeychainContract.KeyRings.HAS_ENCRYPT,
}); });
@@ -136,18 +135,19 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView
+ KeyRings.IS_EXPIRED + " = 0 AND " + KeyRings.IS_EXPIRED + " = 0 AND "
+ Tables.KEYS + "." + KeyRings.IS_REVOKED + " = 0"; + Tables.KEYS + "." + KeyRings.IS_REVOKED + " = 0";
if (args != null && args.containsKey(ARG_QUERY)) { if (args == null || !args.containsKey(ARG_QUERY)) {
String query = args.getString(ARG_QUERY); // mAdapter.setSearchQuery(null);
mAdapter.setSearchQuery(query); // return new CursorLoader(getContext(), baseUri, projection, where, null, null);
return null;
where += " AND " + KeyRings.USER_ID + " LIKE ?";
return new CursorLoader(getContext(), baseUri, projection, where,
new String[]{"%" + query + "%"}, null);
} }
mAdapter.setSearchQuery(null); String query = args.getString(ARG_QUERY);
return new CursorLoader(getContext(), baseUri, projection, where, null, null); 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(); super.showDropDown();
} }
@Override @Override
public void onFocusChanged(boolean hasFocus, int direction, Rect previous) { public void onFocusChanged(boolean hasFocus, int direction, Rect previous) {
super.onFocusChanged(hasFocus, direction, previous); super.onFocusChanged(hasFocus, direction, previous);
@@ -179,13 +181,18 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView
} }
@Override @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); super.performFiltering(text, start, end, keyCode);
if (start < mPrefix.length()) { if (start < mPrefix.length()) {
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(); Bundle args = new Bundle();
args.putString(ARG_QUERY, text.subSequence(start, end).toString()); args.putString(ARG_QUERY, query);
mLoaderManager.restartLoader(0, args, this); mLoaderManager.restartLoader(0, args, this);
} }

View File

@@ -74,9 +74,9 @@ public class EmailKeyHelper {
// Try _hkp._tcp SRV record first // Try _hkp._tcp SRV record first
String[] mailparts = mail.split("@"); String[] mailparts = mail.split("@");
if (mailparts.length == 2) { if (mailparts.length == 2) {
HkpKeyserver hkp = HkpKeyserver.resolve(mailparts[1]); HkpKeyserver hkp = HkpKeyserver.resolve(mailparts[1], proxy);
if (hkp != null) { 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 // Most users don't have the SRV record, so ask a default server as well
String server = Preferences.getPreferences(context).getPreferredKeyserver(); String server = Preferences.getPreferences(context).getPreferredKeyserver();
if (server != null) { if (server != null) {
HkpKeyserver hkp = new HkpKeyserver(server); HkpKeyserver hkp = new HkpKeyserver(server, proxy);
keys.addAll(getEmailKeys(mail, hkp, proxy)); keys.addAll(getEmailKeys(mail, hkp));
} }
} }
return keys; return keys;
} }
public static List<ImportKeysListEntry> getEmailKeys(String mail, Keyserver keyServer, public static List<ImportKeysListEntry> getEmailKeys(String mail, Keyserver keyServer) {
Proxy proxy) {
Set<ImportKeysListEntry> keys = new HashSet<>(); Set<ImportKeysListEntry> keys = new HashSet<>();
try { try {
for (ImportKeysListEntry key : keyServer.search(mail, proxy)) { for (ImportKeysListEntry key : keyServer.search(mail)) {
if (key.isRevoked() || key.isExpired()) continue; if (key.isRevoked() || key.isExpired()) continue;
for (String userId : key.getUserIds()) { for (String userId : key.getUserIds()) {
if (userId.toLowerCase().contains(mail.toLowerCase(Locale.ENGLISH))) { if (userId.toLowerCase().contains(mail.toLowerCase(Locale.ENGLISH))) {

View File

@@ -272,57 +272,21 @@ public class FileHelper {
return true; return true;
} }
/** /** A replacement for ContentResolver.openInputStream() that does not allow
* Tests whether a file is readable by others * the usage of "file" Uris that point to private files owned by the
*/ * application only, *on Lollipop devices*.
@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.
* *
* This is not allowed: * The check will be performed on devices >= Lollipop only, which have the
* am start -a android.intent.action.SEND -t text/plain -n * necessary API to stat filedescriptors.
* "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 * @see FileHelperLollipop
*/ */
@TargetApi(VERSION_CODES.LOLLIPOP)
public static InputStream openInputStreamSafe(ContentResolver resolver, Uri uri) public static InputStream openInputStreamSafe(ContentResolver resolver, Uri uri)
throws FileNotFoundException { throws FileNotFoundException {
// Not supported on Android < 5 // Not supported on Android < 5
if (Build.VERSION.SDK_INT < VERSION_CODES.LOLLIPOP) { if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
return resolver.openInputStream(uri); return FileHelperLollipop.openInputStreamSafe(resolver, 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");
}
} else { } else {
return resolver.openInputStream(uri); return resolver.openInputStream(uri);
} }

View File

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

View File

@@ -17,12 +17,14 @@
package org.sufficientlysecure.keychain.util; package org.sufficientlysecure.keychain.util;
import android.os.Parcel;
import android.os.Parcelable;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.Proxy; 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 * 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); return new ParcelableProxy(null, -1, null);
} }
@NonNull
public Proxy getProxy() { public Proxy getProxy() {
if (mProxyHost == null) { if (mProxyHost == null) {
return null; return Proxy.NO_PROXY;
} }
/* /*
* InetSocketAddress.createUnresolved so we can use this method even in the main thread * InetSocketAddress.createUnresolved so we can use this method even in the main thread

View File

@@ -23,6 +23,9 @@ import android.content.SharedPreferences;
import android.content.res.Resources; import android.content.res.Resources;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Constants.Pref; import org.sufficientlysecure.keychain.Constants.Pref;
import org.sufficientlysecure.keychain.service.KeyserverSyncAdapterService; import org.sufficientlysecure.keychain.service.KeyserverSyncAdapterService;
@@ -322,6 +325,12 @@ public class Preferences {
if (!torEnabled && !normalPorxyEnabled) this.parcelableProxy = new ParcelableProxy(null, -1, null); if (!torEnabled && !normalPorxyEnabled) this.parcelableProxy = new ParcelableProxy(null, -1, null);
else this.parcelableProxy = new ParcelableProxy(hostName, port, type); else this.parcelableProxy = new ParcelableProxy(hostName, port, type);
} }
@NonNull
public Proxy getProxy() {
return parcelableProxy.getProxy();
}
} }
// cloud prefs // cloud prefs

Binary file not shown.

After

Width:  |  Height:  |  Size: 590 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 433 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 684 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 0 B

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 986 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -3,6 +3,8 @@
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_width="match_parent" android:layout_width="match_parent"
xmlns:tools="http://schemas.android.com/tools"
xmlns:custom="http://schemas.android.com/apk/res-auto"
> >
<!--rebuild functionality of ListFragment --> <!--rebuild functionality of ListFragment -->
@@ -23,10 +25,11 @@
<LinearLayout <LinearLayout
android:id="@+id/key_list_empty" android:id="@+id/key_list_empty"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="240dp"
android:gravity="center" android:gravity="center"
android:orientation="vertical" android:orientation="vertical"
android:visibility="visible"> android:animateLayoutChanges="true"
>
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
@@ -35,6 +38,30 @@
android:text="@string/key_list_empty_text1" android:text="@string/key_list_empty_text1"
android:textAppearance="?android:attr/textAppearanceLarge" /> android:textAppearance="?android:attr/textAppearanceLarge" />
<org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/search_container"
android:inAnimation="@anim/fade_in_delayed"
android:outAnimation="@anim/fade_out"
android:measureAllChildren="true"
custom:initialView="1">
<Space
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:id="@+id/search_button"
android:gravity="center"
tools:text="@string/btn_search_for_query"
/>
</org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator>
</LinearLayout> </LinearLayout>
</FrameLayout> </FrameLayout>

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.journeyapps.barcodescanner.CompoundBarcodeView
android:id="@+id/zxing_barcode_scanner"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentBottom="true" />
</RelativeLayout>

View File

@@ -18,7 +18,7 @@
android:layout_height="?attr/actionBarSize" android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary" android:background="?attr/colorPrimary"
android:textColor="?attr/colorTabText" android:textColor="?attr/colorTabText"
app:pstsTextColorSelected="?attr/colorTabTextSelected" app:pstsTabTextColor="?attr/colorTabTextSelected"
app:pstsIndicatorColor="?attr/colorTabIndicator" /> app:pstsIndicatorColor="?attr/colorTabIndicator" />
</RelativeLayout> </RelativeLayout>

View File

@@ -2,6 +2,14 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android" <menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/encrypt_paste"
android:title="@string/btn_paste_encrypted_signed"
android:icon="@drawable/ic_action_encrypt_paste_24dp"
android:orderInCategory="1"
android:visible="false"
app:showAsAction="ifRoom" />
<item <item
android:id="@+id/encrypt_copy" android:id="@+id/encrypt_copy"
android:title="@string/btn_copy_encrypted_signed" android:title="@string/btn_copy_encrypted_signed"

View File

@@ -25,6 +25,12 @@
android:visible="false" android:visible="false"
app:showAsAction="never" /> app:showAsAction="never" />
<item
android:id="@+id/menu_key_list_debug_bench"
android:title="Debug / Benchmark"
android:visible="false"
app:showAsAction="never" />
<item <item
android:id="@+id/menu_key_list_debug_read" android:id="@+id/menu_key_list_debug_read"
android:title="Debug / DB restore" android:title="Debug / DB restore"

View File

@@ -1,8 +1,8 @@
[//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!) [//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!)
[http://www.openkeychain.org](http://www.openkeychain.org) [https://www.openkeychain.org](https://www.openkeychain.org)
[OpenKeychain](http://www.openkeychain.org) is an OpenPGP implementation for Android. [OpenKeychain](https://www.openkeychain.org) is an OpenPGP implementation for Android.
License: GPLv3+ License: GPLv3+
@@ -61,11 +61,11 @@ License: GPLv3+
* [Markdown4J](https://github.com/jdcasey/markdown4j) (Apache License v2) * [Markdown4J](https://github.com/jdcasey/markdown4j) (Apache License v2)
* [MaterialDrawer](https://github.com/mikepenz/MaterialDrawer) (Apache License v2) * [MaterialDrawer](https://github.com/mikepenz/MaterialDrawer) (Apache License v2)
* [MiniDNS](https://github.com/rtreffer/minidns) (Apache License v2) * [MiniDNS](https://github.com/rtreffer/minidns) (Apache License v2)
* [OkHttp](http://square.github.io/okhttp/) (Apache License v2) * [OkHttp](https://square.github.io/okhttp/) (Apache License v2)
* [PagerSlidingTabStrip](https://github.com/jpardogo/PagerSlidingTabStrip) (Material Design) (Apache License v2) * [PagerSlidingTabStrip](https://github.com/jpardogo/PagerSlidingTabStrip) (Material Design) (Apache License v2)
* [SafeSlinger Exchange library](https://github.com/SafeSlingerProject/exchange-android) (MIT License) * [SafeSlinger Exchange library](https://github.com/SafeSlingerProject/exchange-android) (MIT License)
* [Snackbar](https://github.com/nispok/snackbar) (MIT License) * [Snackbar](https://github.com/nispok/snackbar) (MIT License)
* [SpongyCastle](http://rtyley.github.com/spongycastle/) (MIT X11 License) * [SpongyCastle](https://rtyley.github.io/spongycastle/) (MIT X11 License)
* [StickyListHeaders](https://github.com/emilsjolander/StickyListHeaders) (Apache License v2) * [StickyListHeaders](https://github.com/emilsjolander/StickyListHeaders) (Apache License v2)
* [TokenAutoComplete](https://github.com/splitwise/TokenAutoComplete) (Apache License v2) * [TokenAutoComplete](https://github.com/splitwise/TokenAutoComplete) (Apache License v2)
* [ZXing](https://github.com/zxing/zxing) (Apache License v2) * [ZXing](https://github.com/zxing/zxing) (Apache License v2)

View File

@@ -1,8 +1,8 @@
[//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!) [//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!)
[http://www.openkeychain.org](http://www.openkeychain.org) [https://www.openkeychain.org](https://www.openkeychain.org)
[OpenKeychain](http://www.openkeychain.org) ist eine OpenPGP-Implementierung für Android. [OpenKeychain](https://www.openkeychain.org) is an OpenPGP implementation for Android.
Lizenz: GPLv3+ Lizenz: GPLv3+
@@ -61,11 +61,11 @@ Lizenz: GPLv3+
* [Markdown4J](https://github.com/jdcasey/markdown4j) (Apache-Lizenz v2) * [Markdown4J](https://github.com/jdcasey/markdown4j) (Apache-Lizenz v2)
* [MaterialDrawer](https://github.com/mikepenz/MaterialDrawer) (Apache-Lizenz v2) * [MaterialDrawer](https://github.com/mikepenz/MaterialDrawer) (Apache-Lizenz v2)
* [MiniDNS](https://github.com/rtreffer/minidns) (Apache-Lizenz v2) * [MiniDNS](https://github.com/rtreffer/minidns) (Apache-Lizenz v2)
* [OkHttp](http://square.github.io/okhttp/) (Apache-Lizenz v2) * [OkHttp](https://square.github.io/okhttp/) (Apache License v2)
* [PagerSlidingTabStrip](https://github.com/jpardogo/PagerSlidingTabStrip) (Material Design) (Apache-Lizenz v2) * [PagerSlidingTabStrip](https://github.com/jpardogo/PagerSlidingTabStrip) (Material Design) (Apache-Lizenz v2)
* [SafeSlinger Exchange library](https://github.com/SafeSlingerProject/exchange-android) (MIT-Lizenz) * [SafeSlinger Exchange library](https://github.com/SafeSlingerProject/exchange-android) (MIT-Lizenz)
* [Snackbar](https://github.com/nispok/snackbar) (MIT-Lizenz) * [Snackbar](https://github.com/nispok/snackbar) (MIT-Lizenz)
* [SpongyCastle](http://rtyley.github.com/spongycastle/) (MIT X11-Lizenz) * [SpongyCastle](https://rtyley.github.io/spongycastle/) (MIT X11 License)
* [StickyListHeaders](https://github.com/emilsjolander/StickyListHeaders) (Apache-Lizenz v2) * [StickyListHeaders](https://github.com/emilsjolander/StickyListHeaders) (Apache-Lizenz v2)
* [TokenAutoComplete](https://github.com/splitwise/TokenAutoComplete) (Apache-Lizenz v2) * [TokenAutoComplete](https://github.com/splitwise/TokenAutoComplete) (Apache-Lizenz v2)
* [ZXing](https://github.com/zxing/zxing) (Apache-Lizenz v2) * [ZXing](https://github.com/zxing/zxing) (Apache-Lizenz v2)

View File

@@ -1,8 +1,8 @@
[//]: # (NOTA: ¡Ponga cada frase en su propia línea, Transifex pone cada línea en su propio campo de traducción!) [//]: # (NOTA: ¡Ponga cada frase en su propia línea, Transifex pone cada línea en su propio campo de traducción!)
[http://www.openkeychain.org](http://www.openkeychain.org) [https://www.openkeychain.org](https://www.openkeychain.org)
[OpenKeychain](http://www.openkeychain.org) es una implementación de OpenPGP para Android. [OpenKeychain](https://www.openkeychain.org) is an OpenPGP implementation for Android.
Licencia: GPLv3+ Licencia: GPLv3+
@@ -61,11 +61,11 @@ Licencia: GPLv3+
* [Markdown4J](https://github.com/jdcasey/markdown4j) (Licencia Apache v2) * [Markdown4J](https://github.com/jdcasey/markdown4j) (Licencia Apache v2)
* [MaterialDrawer](https://github.com/mikepenz/MaterialDrawer) (Licencia Apache v2) * [MaterialDrawer](https://github.com/mikepenz/MaterialDrawer) (Licencia Apache v2)
* [MiniDNS](https://github.com/rtreffer/minidns) (Licencia Apache v2) * [MiniDNS](https://github.com/rtreffer/minidns) (Licencia Apache v2)
* [OkHttp](http://square.github.io/okhttp/) (Licencia Apache v2) * [OkHttp](https://square.github.io/okhttp/) (Apache License v2)
* [PagerSlidingTabStrip](https://github.com/jpardogo/PagerSlidingTabStrip) (Material Design) (Licencia Apache v2) * [PagerSlidingTabStrip](https://github.com/jpardogo/PagerSlidingTabStrip) (Material Design) (Licencia Apache v2)
* [Librería SafeSlinger Exchange](https://github.com/SafeSlingerProject/exchange-android) (Licencia MIT) * [Librería SafeSlinger Exchange](https://github.com/SafeSlingerProject/exchange-android) (Licencia MIT)
* [Snackbar](https://github.com/nispok/snackbar) (Licencia MIT) * [Snackbar](https://github.com/nispok/snackbar) (Licencia MIT)
* [SpongyCastle](http://rtyley.github.com/spongycastle/) (Licencia MIT X11) * [SpongyCastle](https://rtyley.github.io/spongycastle/) (MIT X11 License)
* [StickyListHeaders](https://github.com/emilsjolander/StickyListHeaders) (Licencia Apache v2) * [StickyListHeaders](https://github.com/emilsjolander/StickyListHeaders) (Licencia Apache v2)
* [TokenAutoComplete](https://github.com/splitwise/TokenAutoComplete) (Licencia Apache v2) * [TokenAutoComplete](https://github.com/splitwise/TokenAutoComplete) (Licencia Apache v2)
* [ZXing](https://github.com/zxing/zxing) (Licencia Apache v2) * [ZXing](https://github.com/zxing/zxing) (Licencia Apache v2)

View File

@@ -1,8 +1,8 @@
[//]: # (OHARRA: Meseez jarri esaldi bakoitza bere lerroan, Transifex-ek lerroak bere itzulpen eremuan jartzen ditu!) [//]: # (OHARRA: Meseez jarri esaldi bakoitza bere lerroan, Transifex-ek lerroak bere itzulpen eremuan jartzen ditu!)
[http://www.openkeychain.org](http://www.openkeychain.org) [https://www.openkeychain.org](https://www.openkeychain.org)
[OpenKeychain](http://www.openkeychain.org) Android-rako OpenPGP egokitzapen bat da. [OpenKeychain](https://www.openkeychain.org) is an OpenPGP implementation for Android.
Baimena: GPLv3+ Baimena: GPLv3+
@@ -61,11 +61,11 @@ Baimena: GPLv3+
* [Markdown4J](https://github.com/jdcasey/markdown4j) (Apache Baimena v2) * [Markdown4J](https://github.com/jdcasey/markdown4j) (Apache Baimena v2)
* [MaterialDrawer](https://github.com/mikepenz/MaterialDrawer) (Apache Baimena v2) * [MaterialDrawer](https://github.com/mikepenz/MaterialDrawer) (Apache Baimena v2)
* [MiniDNS](https://github.com/rtreffer/minidns) (Apache Baimena v2) * [MiniDNS](https://github.com/rtreffer/minidns) (Apache Baimena v2)
* [OkHttp](http://square.github.io/okhttp/) (Apache Baimena v2) * [OkHttp](https://square.github.io/okhttp/) (Apache License v2)
* [PagerSlidingTabStrip](https://github.com/jpardogo/PagerSlidingTabStrip) (Material Diseinua) (Apache Baimena 2 bertsioa) * [PagerSlidingTabStrip](https://github.com/jpardogo/PagerSlidingTabStrip) (Material Diseinua) (Apache Baimena 2 bertsioa)
* [SafeSlinger Exchange library](https://github.com/SafeSlingerProject/exchange-android) (MIT Baimena) * [SafeSlinger Exchange library](https://github.com/SafeSlingerProject/exchange-android) (MIT Baimena)
* [Snackbar](https://github.com/nispok/snackbar) (MIT Baimena) * [Snackbar](https://github.com/nispok/snackbar) (MIT Baimena)
* [SpongyCastle](http://rtyley.github.com/spongycastle/) (MIT X11 Baimena) * [SpongyCastle](https://rtyley.github.io/spongycastle/) (MIT X11 License)
* [StickyListHeaders](https://github.com/emilsjolander/StickyListHeaders) (Apache Baimena v2) * [StickyListHeaders](https://github.com/emilsjolander/StickyListHeaders) (Apache Baimena v2)
* [TokenAutoComplete](https://github.com/splitwise/TokenAutoComplete) (Apache Baimena v2) * [TokenAutoComplete](https://github.com/splitwise/TokenAutoComplete) (Apache Baimena v2)
* [ZXing](https://github.com/zxing/zxing) (Apache Baimena v2) * [ZXing](https://github.com/zxing/zxing) (Apache Baimena v2)

View File

@@ -1,8 +1,8 @@
[//]: # (تذکر: هر جمله در همان خط!) [//]: # (تذکر: هر جمله در همان خط!)
[http://www.openkeychain.org](http://www.openkeychain.org) [https://www.openkeychain.org](https://www.openkeychain.org)
[OpenKeychain](http://www.openkeychain.org) یک نسخه از OpenPGP برای اندروید است. [OpenKeychain](https://www.openkeychain.org) is an OpenPGP implementation for Android.
مجوز: GPLv3+ مجوز: GPLv3+
@@ -61,11 +61,11 @@
* [Markdown4J](https://github.com/jdcasey/markdown4j) (Apache License v2) * [Markdown4J](https://github.com/jdcasey/markdown4j) (Apache License v2)
* [MaterialDrawer](https://github.com/mikepenz/MaterialDrawer) (Apache License v2) * [MaterialDrawer](https://github.com/mikepenz/MaterialDrawer) (Apache License v2)
* [MiniDNS](https://github.com/rtreffer/minidns) (Apache License v2) * [MiniDNS](https://github.com/rtreffer/minidns) (Apache License v2)
* [OkHttp](http://square.github.io/okhttp/) (Apache License v2) * [OkHttp](https://square.github.io/okhttp/) (Apache License v2)
* [PagerSlidingTabStrip](https://github.com/jpardogo/PagerSlidingTabStrip) (Material Design) (Apache License v2) * [PagerSlidingTabStrip](https://github.com/jpardogo/PagerSlidingTabStrip) (Material Design) (Apache License v2)
* [SafeSlinger Exchange library](https://github.com/SafeSlingerProject/exchange-android) (MIT License) * [SafeSlinger Exchange library](https://github.com/SafeSlingerProject/exchange-android) (MIT License)
* [Snackbar](https://github.com/nispok/snackbar) (MIT License) * [Snackbar](https://github.com/nispok/snackbar) (MIT License)
* [SpongyCastle](http://rtyley.github.com/spongycastle/) (MIT X11 License) * [SpongyCastle](https://rtyley.github.io/spongycastle/) (MIT X11 License)
* [StickyListHeaders](https://github.com/emilsjolander/StickyListHeaders) (Apache License v2) * [StickyListHeaders](https://github.com/emilsjolander/StickyListHeaders) (Apache License v2)
* [TokenAutoComplete](https://github.com/splitwise/TokenAutoComplete) (Apache License v2) * [TokenAutoComplete](https://github.com/splitwise/TokenAutoComplete) (Apache License v2)
* [ZXing](https://github.com/zxing/zxing) (Apache License v2) * [ZXing](https://github.com/zxing/zxing) (Apache License v2)

View File

@@ -1,8 +1,8 @@
[//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!) [//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!)
[http://www.openkeychain.org](http://www.openkeychain.org) [https://www.openkeychain.org](https://www.openkeychain.org)
[OpenKeychain](http://www.openkeychain.org) is an OpenPGP implementation for Android. [OpenKeychain](https://www.openkeychain.org) is an OpenPGP implementation for Android.
License: GPLv3+ License: GPLv3+
@@ -61,11 +61,11 @@ License: GPLv3+
* [Markdown4J](https://github.com/jdcasey/markdown4j) (Apache License v2) * [Markdown4J](https://github.com/jdcasey/markdown4j) (Apache License v2)
* [MaterialDrawer](https://github.com/mikepenz/MaterialDrawer) (Apache License v2) * [MaterialDrawer](https://github.com/mikepenz/MaterialDrawer) (Apache License v2)
* [MiniDNS](https://github.com/rtreffer/minidns) (Apache License v2) * [MiniDNS](https://github.com/rtreffer/minidns) (Apache License v2)
* [OkHttp](http://square.github.io/okhttp/) (Apache License v2) * [OkHttp](https://square.github.io/okhttp/) (Apache License v2)
* [PagerSlidingTabStrip](https://github.com/jpardogo/PagerSlidingTabStrip) (Material Design) (Apache License v2) * [PagerSlidingTabStrip](https://github.com/jpardogo/PagerSlidingTabStrip) (Material Design) (Apache License v2)
* [SafeSlinger Exchange library](https://github.com/SafeSlingerProject/exchange-android) (MIT License) * [SafeSlinger Exchange library](https://github.com/SafeSlingerProject/exchange-android) (MIT License)
* [Snackbar](https://github.com/nispok/snackbar) (MIT License) * [Snackbar](https://github.com/nispok/snackbar) (MIT License)
* [SpongyCastle](http://rtyley.github.com/spongycastle/) (MIT X11 License) * [SpongyCastle](https://rtyley.github.io/spongycastle/) (MIT X11 License)
* [StickyListHeaders](https://github.com/emilsjolander/StickyListHeaders) (Apache License v2) * [StickyListHeaders](https://github.com/emilsjolander/StickyListHeaders) (Apache License v2)
* [TokenAutoComplete](https://github.com/splitwise/TokenAutoComplete) (Apache License v2) * [TokenAutoComplete](https://github.com/splitwise/TokenAutoComplete) (Apache License v2)
* [ZXing](https://github.com/zxing/zxing) (Apache License v2) * [ZXing](https://github.com/zxing/zxing) (Apache License v2)

View File

@@ -1,8 +1,8 @@
[//] : # (NOTE : veuillez mettre chaque phrase dans sa propre ligne. Transifex met chaque ligne dans son propre champ de traduction !) [//] : # (NOTE : veuillez mettre chaque phrase dans sa propre ligne. Transifex met chaque ligne dans son propre champ de traduction !)
[http://www.openkeychain.org](http://www.openkeychain.org) [https://www.openkeychain.org](https://www.openkeychain.org)
[OpenKeychain](http://www.openkeychain.org) est une mise en œuvre d'OpenPGP pour Android. [OpenKeychain](https://www.openkeychain.org) is an OpenPGP implementation for Android.
Licence : GPLv3+ Licence : GPLv3+
@@ -61,11 +61,11 @@ Licence : GPLv3+
* [Markdown4J](https://github.com/jdcasey/markdown4j) (Licence Apache v2) * [Markdown4J](https://github.com/jdcasey/markdown4j) (Licence Apache v2)
* [MaterialDrawer](https://github.com/mikepenz/MaterialDrawer) (Licence Apache v2) * [MaterialDrawer](https://github.com/mikepenz/MaterialDrawer) (Licence Apache v2)
* [MiniDNS](https://github.com/rtreffer/minidns) (Licence Apache v2) * [MiniDNS](https://github.com/rtreffer/minidns) (Licence Apache v2)
* [OkHttp](http://square.github.io/okhttp/) (licence Apache v2) * [OkHttp](https://square.github.io/okhttp/) (Apache License v2)
* [PagerSlidingTabStrip](https://github.com/jpardogo/PagerSlidingTabStrip) (Conception matérielle)</a> (Licence Apache v2) * [PagerSlidingTabStrip](https://github.com/jpardogo/PagerSlidingTabStrip) (Conception matérielle)</a> (Licence Apache v2)
* [Bibliothèque d'échange SafeSlinger](https://github.com/SafeSlingerProject/exchange-android) (Licence MIT) * [Bibliothèque d'échange SafeSlinger](https://github.com/SafeSlingerProject/exchange-android) (Licence MIT)
* [Snackbar](https://github.com/nispok/snackbar) (Licence MIT) * [Snackbar](https://github.com/nispok/snackbar) (Licence MIT)
* [SpongyCastle](http://rtyley.github.com/spongycastle/) (Licence MIT X11) * [SpongyCastle](https://rtyley.github.io/spongycastle/) (MIT X11 License)
* [StickyListHeaders](https://github.com/emilsjolander/StickyListHeaders) (Licence Apache v2) * [StickyListHeaders](https://github.com/emilsjolander/StickyListHeaders) (Licence Apache v2)
* [TokenAutoComplete](https://github.com/splitwise/TokenAutoComplete) (Licence Apache v2) * [TokenAutoComplete](https://github.com/splitwise/TokenAutoComplete) (Licence Apache v2)
* [ZXing](https://github.com/zxing/zxing) (Licence Apache v2) * [ZXing](https://github.com/zxing/zxing) (Licence Apache v2)

View File

@@ -1,8 +1,8 @@
[//]: # (NOTA: Si prega di mettere ogni frase in una propria linea, Transifex mette ogni riga nel proprio campo di traduzione!) [//]: # (NOTA: Si prega di mettere ogni frase in una propria linea, Transifex mette ogni riga nel proprio campo di traduzione!)
[http://www.openkeychain.org](http://www.openkeychain.org) [https://www.openkeychain.org](https://www.openkeychain.org)
[OpenKeychain](http://www.openkeychain.org) e un impelementazione OpenPGP per Android. [OpenKeychain](https://www.openkeychain.org) is an OpenPGP implementation for Android.
Licenza: GPLv3+ Licenza: GPLv3+
@@ -61,11 +61,11 @@ Licenza: GPLv3+
* [Markdown4J](https://github.com/jdcasey/markdown4j) (Apache License v2) * [Markdown4J](https://github.com/jdcasey/markdown4j) (Apache License v2)
* [MaterialDrawer](https://github.com/mikepenz/MaterialDrawer) (Apache License v2) * [MaterialDrawer](https://github.com/mikepenz/MaterialDrawer) (Apache License v2)
* [MiniDNS](https://github.com/rtreffer/minidns) (Apache License v2) * [MiniDNS](https://github.com/rtreffer/minidns) (Apache License v2)
* [OkHttp](http://square.github.io/okhttp/) (Apache License v2) * [OkHttp](https://square.github.io/okhttp/) (Apache License v2)
* [PagerSlidingTabStrip](https://github.com/jpardogo/PagerSlidingTabStrip) (Design materiale) (Apache License v2) * [PagerSlidingTabStrip](https://github.com/jpardogo/PagerSlidingTabStrip) (Design materiale) (Apache License v2)
* [SafeSlinger Exchange library](https://github.com/SafeSlingerProject/exchange-android) (MIT License) * [SafeSlinger Exchange library](https://github.com/SafeSlingerProject/exchange-android) (MIT License)
* [Snackbar](https://github.com/nispok/snackbar) (MIT License) * [Snackbar](https://github.com/nispok/snackbar) (MIT License)
* [SpongyCastle](http://rtyley.github.com/spongycastle/) (MIT X11 License) * [SpongyCastle](https://rtyley.github.io/spongycastle/) (MIT X11 License)
* [StickyListHeaders](https://github.com/emilsjolander/StickyListHeaders) (Apache License v2) * [StickyListHeaders](https://github.com/emilsjolander/StickyListHeaders) (Apache License v2)
* [TokenAutoComplete](https://github.com/splitwise/TokenAutoComplete) (Apache License v2) * [TokenAutoComplete](https://github.com/splitwise/TokenAutoComplete) (Apache License v2)
* [ZXing](https://github.com/zxing/zxing) (Apache License v2) * [ZXing](https://github.com/zxing/zxing) (Apache License v2)

View File

@@ -1,8 +1,8 @@
[//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!) [//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!)
[http://www.openkeychain.org](http://www.openkeychain.org) [https://www.openkeychain.org](https://www.openkeychain.org)
[OpenKeychain](http://www.openkeychain.org) は Android における OpenPGP 実装です。 [OpenKeychain](https://www.openkeychain.org) is an OpenPGP implementation for Android.
ライセンス: GPLv3以降 ライセンス: GPLv3以降
@@ -61,11 +61,11 @@
* [Markdown4J](https://github.com/jdcasey/markdown4j) (Apache License v2) * [Markdown4J](https://github.com/jdcasey/markdown4j) (Apache License v2)
* [MaterialDrawer](https://github.com/mikepenz/MaterialDrawer) (Apache License v2) * [MaterialDrawer](https://github.com/mikepenz/MaterialDrawer) (Apache License v2)
* [MiniDNS](https://github.com/rtreffer/minidns) (Apache License v2) * [MiniDNS](https://github.com/rtreffer/minidns) (Apache License v2)
* [OkHttp](http://square.github.io/okhttp/) (Apache License v2) * [OkHttp](https://square.github.io/okhttp/) (Apache License v2)
* [PagerSlidingTabStrip](https://github.com/jpardogo/PagerSlidingTabStrip) (Material Design) (Apache License v2) * [PagerSlidingTabStrip](https://github.com/jpardogo/PagerSlidingTabStrip) (Material Design) (Apache License v2)
* [SafeSlinger Exchange library](https://github.com/SafeSlingerProject/exchange-android) (MIT License) * [SafeSlinger Exchange library](https://github.com/SafeSlingerProject/exchange-android) (MIT License)
* [Snackbar](https://github.com/nispok/snackbar) (MIT License) * [Snackbar](https://github.com/nispok/snackbar) (MIT License)
* [SpongyCastle](http://rtyley.github.com/spongycastle/) (MIT X11 License) * [SpongyCastle](https://rtyley.github.io/spongycastle/) (MIT X11 License)
* [StickyListHeaders](https://github.com/emilsjolander/StickyListHeaders) (Apache License v2) * [StickyListHeaders](https://github.com/emilsjolander/StickyListHeaders) (Apache License v2)
* [TokenAutoComplete](https://github.com/splitwise/TokenAutoComplete) (Apache License v2) * [TokenAutoComplete](https://github.com/splitwise/TokenAutoComplete) (Apache License v2)
* [ZXing](https://github.com/zxing/zxing) (Apache License v2) * [ZXing](https://github.com/zxing/zxing) (Apache License v2)

View File

@@ -2,14 +2,14 @@
## 3.6 ## 3.6
* Encrypted backups * 暗号化したバックアップ
* Security fixes based on external security audit * 外部セキュリティ監査によるセキュリティ修正
* YubiKey NEO key creation wizard * YubiKey NEO 鍵生成ウィザード
* Basic internal MIME support * 基本的な内部 MIME のサポート
* Automatic key synchronization * 自動鍵同期
* Experimental feature: link keys to Github, Twitter accounts * 試験的機能: Github, Twitter アカウントと鍵のリンク
* Experimental feature: key confirmation via phrases * 試験的機能: 語句による鍵検証
* Experimental feature: dark theme * 試験的機能: ダークテーマ
* API: Version 9 * API: Version 9
## 3.5 ## 3.5

View File

@@ -1,8 +1,8 @@
[//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!) [//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!)
[http://www.openkeychain.org](http://www.openkeychain.org) [https://www.openkeychain.org](https://www.openkeychain.org)
[OpenKeychain](http://www.openkeychain.org) is een OpenPGP implementatie voor Android. [OpenKeychain](https://www.openkeychain.org) is an OpenPGP implementation for Android.
Licentie: GPLv3+ Licentie: GPLv3+
@@ -61,11 +61,11 @@ Licentie: GPLv3+
* [Markdown4J](https://github.com/jdcasey/markdown4j) (Apache licentie v2) * [Markdown4J](https://github.com/jdcasey/markdown4j) (Apache licentie v2)
* [MaterialDrawer](https://github.com/mikepenz/MaterialDrawer) (Apache licentie v2) * [MaterialDrawer](https://github.com/mikepenz/MaterialDrawer) (Apache licentie v2)
* [MiniDNS](https://github.com/rtreffer/minidns) (Apache licentie v2) * [MiniDNS](https://github.com/rtreffer/minidns) (Apache licentie v2)
* [OkHttp](http://square.github.io/okhttp/) (Apache licentie v2) * [OkHttp](https://square.github.io/okhttp/) (Apache License v2)
* [PagerSlidingTabStrip](https://github.com/jpardogo/PagerSlidingTabStrip) (Material Design) (Apache licentie v2) * [PagerSlidingTabStrip](https://github.com/jpardogo/PagerSlidingTabStrip) (Material Design) (Apache licentie v2)
* [SafeSlinger Exchange library](https://github.com/SafeSlingerProject/exchange-android) (MIT licentie) * [SafeSlinger Exchange library](https://github.com/SafeSlingerProject/exchange-android) (MIT licentie)
* [Snackbar](https://github.com/nispok/snackbar) (MIT licentie) * [Snackbar](https://github.com/nispok/snackbar) (MIT licentie)
* [SpongyCastle](http://rtyley.github.com/spongycastle/) (MIT X11 licentie) * [SpongyCastle](https://rtyley.github.io/spongycastle/) (MIT X11 License)
* [StickyListHeaders](https://github.com/emilsjolander/StickyListHeaders) (Apache licentie v2) * [StickyListHeaders](https://github.com/emilsjolander/StickyListHeaders) (Apache licentie v2)
* [TokenAutoComplete](https://github.com/splitwise/TokenAutoComplete) (Apache licentie v2) * [TokenAutoComplete](https://github.com/splitwise/TokenAutoComplete) (Apache licentie v2)
* [ZXing](https://github.com/zxing/zxing) (Apache licentie v2) * [ZXing](https://github.com/zxing/zxing) (Apache licentie v2)

View File

@@ -1,8 +1,8 @@
[//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!) [//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!)
[http://www.openkeychain.org](http://www.openkeychain.org) [https://www.openkeychain.org](https://www.openkeychain.org)
[OpenKeychain](http://www.openkeychain.org) jest implementacją OpenPGP dla Androida. [OpenKeychain](https://www.openkeychain.org) is an OpenPGP implementation for Android.
Licencja: GPLv3+ Licencja: GPLv3+
@@ -61,11 +61,11 @@ Licencja: GPLv3+
* [Markdown4J](https://github.com/jdcasey/markdown4j) (Apache License v2) * [Markdown4J](https://github.com/jdcasey/markdown4j) (Apache License v2)
* [MaterialDrawer](https://github.com/mikepenz/MaterialDrawer) (Apache License v2) * [MaterialDrawer](https://github.com/mikepenz/MaterialDrawer) (Apache License v2)
* [MiniDNS](https://github.com/rtreffer/minidns) (Apache License v2) * [MiniDNS](https://github.com/rtreffer/minidns) (Apache License v2)
* [OkHttp](http://square.github.io/okhttp/) (Apache License v2) * [OkHttp](https://square.github.io/okhttp/) (Apache License v2)
* [PagerSlidingTabStrip](https://github.com/jpardogo/PagerSlidingTabStrip) (Material Design) (Apache License v2) * [PagerSlidingTabStrip](https://github.com/jpardogo/PagerSlidingTabStrip) (Material Design) (Apache License v2)
* [SafeSlinger Exchange library](https://github.com/SafeSlingerProject/exchange-android) (MIT License) * [SafeSlinger Exchange library](https://github.com/SafeSlingerProject/exchange-android) (MIT License)
* [Snackbar](https://github.com/nispok/snackbar) (MIT License) * [Snackbar](https://github.com/nispok/snackbar) (MIT License)
* [SpongyCastle](http://rtyley.github.com/spongycastle/) (MIT X11 License) * [SpongyCastle](https://rtyley.github.io/spongycastle/) (MIT X11 License)
* [StickyListHeaders](https://github.com/emilsjolander/StickyListHeaders) (Apache License v2) * [StickyListHeaders](https://github.com/emilsjolander/StickyListHeaders) (Apache License v2)
* [TokenAutoComplete](https://github.com/splitwise/TokenAutoComplete) (Apache License v2) * [TokenAutoComplete](https://github.com/splitwise/TokenAutoComplete) (Apache License v2)
* [ZXing](https://github.com/zxing/zxing) (Apache License v2) * [ZXing](https://github.com/zxing/zxing) (Apache License v2)

View File

@@ -1,8 +1,8 @@
[//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!) [//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!)
[http://www.openkeychain.org](http://www.openkeychain.org) [https://www.openkeychain.org](https://www.openkeychain.org)
[OpenKeychain](http://www.openkeychain.org) это OpenPGP имплементация для ОС Android. [OpenKeychain](https://www.openkeychain.org) is an OpenPGP implementation for Android.
Лицензия: GPLv3+ Лицензия: GPLv3+
@@ -61,11 +61,11 @@
* [Markdown4J](https://github.com/jdcasey/markdown4j) (Apache License v2) * [Markdown4J](https://github.com/jdcasey/markdown4j) (Apache License v2)
* [MaterialDrawer](https://github.com/mikepenz/MaterialDrawer) (Apache License v2) * [MaterialDrawer](https://github.com/mikepenz/MaterialDrawer) (Apache License v2)
* [MiniDNS](https://github.com/rtreffer/minidns) (Apache License v2) * [MiniDNS](https://github.com/rtreffer/minidns) (Apache License v2)
* [OkHttp](http://square.github.io/okhttp/) (Apache License v2) * [OkHttp](https://square.github.io/okhttp/) (Apache License v2)
* [PagerSlidingTabStrip](https://github.com/jpardogo/PagerSlidingTabStrip) (Material Design) (Apache License v2) * [PagerSlidingTabStrip](https://github.com/jpardogo/PagerSlidingTabStrip) (Material Design) (Apache License v2)
* [SafeSlinger Exchange library](https://github.com/SafeSlingerProject/exchange-android) (MIT License) * [SafeSlinger Exchange library](https://github.com/SafeSlingerProject/exchange-android) (MIT License)
* [Snackbar](https://github.com/nispok/snackbar) (MIT License) * [Snackbar](https://github.com/nispok/snackbar) (MIT License)
* [SpongyCastle](http://rtyley.github.com/spongycastle/) (MIT X11 License) * [SpongyCastle](https://rtyley.github.io/spongycastle/) (MIT X11 License)
* [StickyListHeaders](https://github.com/emilsjolander/StickyListHeaders) (Apache License v2) * [StickyListHeaders](https://github.com/emilsjolander/StickyListHeaders) (Apache License v2)
* [TokenAutoComplete](https://github.com/splitwise/TokenAutoComplete) (Apache License v2) * [TokenAutoComplete](https://github.com/splitwise/TokenAutoComplete) (Apache License v2)
* [ZXing](https://github.com/zxing/zxing) (Apache License v2) * [ZXing](https://github.com/zxing/zxing) (Apache License v2)

View File

@@ -1,8 +1,8 @@
[//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!) [//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!)
[http://www.openkeychain.org](http://www.openkeychain.org) [https://www.openkeychain.org](https://www.openkeychain.org)
[OpenKeychain](http://www.openkeychain.org) je implementacija OpenPGP za Android. [OpenKeychain](https://www.openkeychain.org) is an OpenPGP implementation for Android.
Licenca: GPLv3+ Licenca: GPLv3+
@@ -61,11 +61,11 @@ Licenca: GPLv3+
* [Markdown4J](https://github.com/jdcasey/markdown4j) (Licenca Apache v2) * [Markdown4J](https://github.com/jdcasey/markdown4j) (Licenca Apache v2)
* [MaterialDrawer](https://github.com/mikepenz/MaterialDrawer) (Apache License v2) * [MaterialDrawer](https://github.com/mikepenz/MaterialDrawer) (Apache License v2)
* [MiniDNS](https://github.com/rtreffer/minidns) (Licenca Apache v2) * [MiniDNS](https://github.com/rtreffer/minidns) (Licenca Apache v2)
* [OkHttp](http://square.github.io/okhttp/) (Apache License v2) * [OkHttp](https://square.github.io/okhttp/) (Apache License v2)
* [PagerSlidingTabStrip](https://github.com/jpardogo/PagerSlidingTabStrip) (Material Design) (Apache License v2) * [PagerSlidingTabStrip](https://github.com/jpardogo/PagerSlidingTabStrip) (Material Design) (Apache License v2)
* [SafeSlinger Exchange library](https://github.com/SafeSlingerProject/exchange-android) (Licenca MIT) * [SafeSlinger Exchange library](https://github.com/SafeSlingerProject/exchange-android) (Licenca MIT)
* [Snackbar](https://github.com/nispok/snackbar) (Licenca MIT) * [Snackbar](https://github.com/nispok/snackbar) (Licenca MIT)
* [SpongyCastle](http://rtyley.github.com/spongycastle/) (Licenca MIT X11) * [SpongyCastle](https://rtyley.github.io/spongycastle/) (MIT X11 License)
* [StickyListHeaders](https://github.com/emilsjolander/StickyListHeaders) (Licenca Apache v2) * [StickyListHeaders](https://github.com/emilsjolander/StickyListHeaders) (Licenca Apache v2)
* [TokenAutoComplete](https://github.com/splitwise/TokenAutoComplete) (Licenca Apache v2) * [TokenAutoComplete](https://github.com/splitwise/TokenAutoComplete) (Licenca Apache v2)
* [ZXing](https://github.com/zxing/zxing) (Licenca Apache v2) * [ZXing](https://github.com/zxing/zxing) (Licenca Apache v2)

View File

@@ -1,8 +1,8 @@
[//]: # [//]: #
[http://www.openkeychain.org](http://www.openkeychain.org) [https://www.openkeychain.org](https://www.openkeychain.org)
[Отворени кључарник (OpenKeychain)](http://www.openkeychain.org) је ОпенПГП имплементација за Андроид. [OpenKeychain](https://www.openkeychain.org) is an OpenPGP implementation for Android.
Лиценца: ГПЛв3+ Лиценца: ГПЛв3+
@@ -61,11 +61,11 @@
* [Markdown4J](https://github.com/jdcasey/markdown4j) (Апачи лиценца в2) * [Markdown4J](https://github.com/jdcasey/markdown4j) (Апачи лиценца в2)
* [MaterialDrawer](https://github.com/mikepenz/MaterialDrawer) (Апачи лиценца в2) * [MaterialDrawer](https://github.com/mikepenz/MaterialDrawer) (Апачи лиценца в2)
* [MiniDNS](https://github.com/rtreffer/minidns) (Апачи лиценца в2) * [MiniDNS](https://github.com/rtreffer/minidns) (Апачи лиценца в2)
* [OkHttp](http://square.github.io/okhttp/) (Apache License v2) * [OkHttp](https://square.github.io/okhttp/) (Apache License v2)
* [PagerSlidingTabStrip](https://github.com/jpardogo/PagerSlidingTabStrip) (Материјал дизајн) (Апачи лиценца в2) * [PagerSlidingTabStrip](https://github.com/jpardogo/PagerSlidingTabStrip) (Материјал дизајн) (Апачи лиценца в2)
* [SafeSlinger Exchange library](https://github.com/SafeSlingerProject/exchange-android) (МИТ лиценца) * [SafeSlinger Exchange library](https://github.com/SafeSlingerProject/exchange-android) (МИТ лиценца)
* [Snackbar](https://github.com/nispok/snackbar) (МИТ лиценца) * [Snackbar](https://github.com/nispok/snackbar) (МИТ лиценца)
* [SpongyCastle](http://rtyley.github.com/spongycastle/) (МИТ Икс11 лиценца) * [SpongyCastle](https://rtyley.github.io/spongycastle/) (MIT X11 License)
* [StickyListHeaders](https://github.com/emilsjolander/StickyListHeaders) (Апачи лиценца в2) * [StickyListHeaders](https://github.com/emilsjolander/StickyListHeaders) (Апачи лиценца в2)
* [TokenAutoComplete](https://github.com/splitwise/TokenAutoComplete) (Апачи лиценца в2) * [TokenAutoComplete](https://github.com/splitwise/TokenAutoComplete) (Апачи лиценца в2)
* [ZXing](https://github.com/zxing/zxing) (Апачи лиценца в2) * [ZXing](https://github.com/zxing/zxing) (Апачи лиценца в2)

View File

@@ -1,8 +1,8 @@
[//]: # (NOTERING: Var vänlig och sätt varje mening på sin egen rad, Transifex sätter varje rad i sitt eget fält för översättningar!) [//]: # (NOTERING: Var vänlig och sätt varje mening på sin egen rad, Transifex sätter varje rad i sitt eget fält för översättningar!)
[http://www.openkeychain.org](http://www.openkeychain.org) [https://www.openkeychain.org](https://www.openkeychain.org)
[OpenKeychain](http://www.openkeychain.org) är en OpenPGP-implementation till Android. [OpenKeychain](https://www.openkeychain.org) is an OpenPGP implementation for Android.
Licens: GPLv3+ Licens: GPLv3+
@@ -61,11 +61,11 @@ Licens: GPLv3+
* [Markdown4J](https://github.com/jdcasey/markdown4j) (Apache License v2) * [Markdown4J](https://github.com/jdcasey/markdown4j) (Apache License v2)
* [MaterialDrawer](https://github.com/mikepenz/MaterialDrawer) (Apache License v2) * [MaterialDrawer](https://github.com/mikepenz/MaterialDrawer) (Apache License v2)
* [MiniDNS](https://github.com/rtreffer/minidns) (Apache License v2) * [MiniDNS](https://github.com/rtreffer/minidns) (Apache License v2)
* [OkHttp](http://square.github.io/okhttp/) (Apache License v2) * [OkHttp](https://square.github.io/okhttp/) (Apache License v2)
* [PagerSlidingTabStrip](https://github.com/jpardogo/PagerSlidingTabStrip) (Material Design) (Apache License v2) * [PagerSlidingTabStrip](https://github.com/jpardogo/PagerSlidingTabStrip) (Material Design) (Apache License v2)
* [SafeSlinger's bibliotek för utbyte](https://github.com/SafeSlingerProject/exchange-android) (MIT-licens) * [SafeSlinger's bibliotek för utbyte](https://github.com/SafeSlingerProject/exchange-android) (MIT-licens)
* [Snackbar](https://github.com/nispok/snackbar) (MIT License) * [Snackbar](https://github.com/nispok/snackbar) (MIT License)
* [SpongyCastle](http://rtyley.github.com/spongycastle/) (MIT X11-licens) * [SpongyCastle](https://rtyley.github.io/spongycastle/) (MIT X11 License)
* [StickyListHeaders](https://github.com/emilsjolander/StickyListHeaders) (Apache License v2) * [StickyListHeaders](https://github.com/emilsjolander/StickyListHeaders) (Apache License v2)
* [TokenAutoComplete](https://github.com/splitwise/TokenAutoComplete) (Apache License v2) * [TokenAutoComplete](https://github.com/splitwise/TokenAutoComplete) (Apache License v2)
* [ZXing](https://github.com/zxing/zxing) (Apache License v2) * [ZXing](https://github.com/zxing/zxing) (Apache License v2)

View File

@@ -1,8 +1,8 @@
[//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!) [//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!)
[http://www.openkeychain.org](http://www.openkeychain.org) [https://www.openkeychain.org](https://www.openkeychain.org)
[OpenKeychain](http://www.openkeychain.org) is an OpenPGP implementation for Android. [OpenKeychain](https://www.openkeychain.org) is an OpenPGP implementation for Android.
License: GPLv3+ License: GPLv3+
@@ -61,11 +61,11 @@ License: GPLv3+
* [Markdown4J](https://github.com/jdcasey/markdown4j) (Apache License v2) * [Markdown4J](https://github.com/jdcasey/markdown4j) (Apache License v2)
* [MaterialDrawer](https://github.com/mikepenz/MaterialDrawer) (Apache License v2) * [MaterialDrawer](https://github.com/mikepenz/MaterialDrawer) (Apache License v2)
* [MiniDNS](https://github.com/rtreffer/minidns) (Apache License v2) * [MiniDNS](https://github.com/rtreffer/minidns) (Apache License v2)
* [OkHttp](http://square.github.io/okhttp/) (Apache License v2) * [OkHttp](https://square.github.io/okhttp/) (Apache License v2)
* [PagerSlidingTabStrip](https://github.com/jpardogo/PagerSlidingTabStrip) (Material Design) (Apache License v2) * [PagerSlidingTabStrip](https://github.com/jpardogo/PagerSlidingTabStrip) (Material Design) (Apache License v2)
* [SafeSlinger Exchange library](https://github.com/SafeSlingerProject/exchange-android) (MIT License) * [SafeSlinger Exchange library](https://github.com/SafeSlingerProject/exchange-android) (MIT License)
* [Snackbar](https://github.com/nispok/snackbar) (MIT License) * [Snackbar](https://github.com/nispok/snackbar) (MIT License)
* [SpongyCastle](http://rtyley.github.com/spongycastle/) (MIT X11 License) * [SpongyCastle](https://rtyley.github.io/spongycastle/) (MIT X11 License)
* [StickyListHeaders](https://github.com/emilsjolander/StickyListHeaders) (Apache License v2) * [StickyListHeaders](https://github.com/emilsjolander/StickyListHeaders) (Apache License v2)
* [TokenAutoComplete](https://github.com/splitwise/TokenAutoComplete) (Apache License v2) * [TokenAutoComplete](https://github.com/splitwise/TokenAutoComplete) (Apache License v2)
* [ZXing](https://github.com/zxing/zxing) (Apache License v2) * [ZXing](https://github.com/zxing/zxing) (Apache License v2)

View File

@@ -1,8 +1,8 @@
[//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!) [//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!)
[http://www.openkeychain.org](http://www.openkeychain.org) [https://www.openkeychain.org](https://www.openkeychain.org)
[OpenKeychain](http://www.openkeychain.org) is an OpenPGP implementation for Android. [OpenKeychain](https://www.openkeychain.org) is an OpenPGP implementation for Android.
License: GPLv3+ License: GPLv3+
@@ -61,11 +61,11 @@ License: GPLv3+
* [Markdown4J](https://github.com/jdcasey/markdown4j) (Apache License v2) * [Markdown4J](https://github.com/jdcasey/markdown4j) (Apache License v2)
* [MaterialDrawer](https://github.com/mikepenz/MaterialDrawer) (Apache License v2) * [MaterialDrawer](https://github.com/mikepenz/MaterialDrawer) (Apache License v2)
* [MiniDNS](https://github.com/rtreffer/minidns) (Apache License v2) * [MiniDNS](https://github.com/rtreffer/minidns) (Apache License v2)
* [OkHttp](http://square.github.io/okhttp/) (Apache License v2) * [OkHttp](https://square.github.io/okhttp/) (Apache License v2)
* [PagerSlidingTabStrip](https://github.com/jpardogo/PagerSlidingTabStrip) (Material Design) (Apache License v2) * [PagerSlidingTabStrip](https://github.com/jpardogo/PagerSlidingTabStrip) (Material Design) (Apache License v2)
* [SafeSlinger Exchange library](https://github.com/SafeSlingerProject/exchange-android) (MIT License) * [SafeSlinger Exchange library](https://github.com/SafeSlingerProject/exchange-android) (MIT License)
* [Snackbar](https://github.com/nispok/snackbar) (MIT License) * [Snackbar](https://github.com/nispok/snackbar) (MIT License)
* [SpongyCastle](http://rtyley.github.com/spongycastle/) (MIT X11 License) * [SpongyCastle](https://rtyley.github.io/spongycastle/) (MIT X11 License)
* [StickyListHeaders](https://github.com/emilsjolander/StickyListHeaders) (Apache License v2) * [StickyListHeaders](https://github.com/emilsjolander/StickyListHeaders) (Apache License v2)
* [TokenAutoComplete](https://github.com/splitwise/TokenAutoComplete) (Apache License v2) * [TokenAutoComplete](https://github.com/splitwise/TokenAutoComplete) (Apache License v2)
* [ZXing](https://github.com/zxing/zxing) (Apache License v2) * [ZXing](https://github.com/zxing/zxing) (Apache License v2)

View File

@@ -1,8 +1,8 @@
[//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!) [//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!)
[http://www.openkeychain.org](http://www.openkeychain.org) [https://www.openkeychain.org](https://www.openkeychain.org)
[OpenKeychain](http://www.openkeychain.org)是一個Android的OpenPGP應用。 [OpenKeychain](https://www.openkeychain.org) is an OpenPGP implementation for Android.
授權GPLv3+ 授權GPLv3+
@@ -61,11 +61,11 @@
* [Markdown4J](https://github.com/jdcasey/markdown4j) (Apache License v2) * [Markdown4J](https://github.com/jdcasey/markdown4j) (Apache License v2)
* [MaterialDrawer](https://github.com/mikepenz/MaterialDrawer) (Apache License v2) * [MaterialDrawer](https://github.com/mikepenz/MaterialDrawer) (Apache License v2)
* [MiniDNS](https://github.com/rtreffer/minidns) (Apache License v2) * [MiniDNS](https://github.com/rtreffer/minidns) (Apache License v2)
* [OkHttp](http://square.github.io/okhttp/) (Apache License v2) * [OkHttp](https://square.github.io/okhttp/) (Apache License v2)
* [PagerSlidingTabStrip](https://github.com/jpardogo/PagerSlidingTabStrip) (Material Design) (Apache License v2) * [PagerSlidingTabStrip](https://github.com/jpardogo/PagerSlidingTabStrip) (Material Design) (Apache License v2)
* [SafeSlinger Exchange library](https://github.com/SafeSlingerProject/exchange-android) (MIT License) * [SafeSlinger Exchange library](https://github.com/SafeSlingerProject/exchange-android) (MIT License)
* [Snackbar](https://github.com/nispok/snackbar) (MIT License) * [Snackbar](https://github.com/nispok/snackbar) (MIT License)
* [SpongyCastle](http://rtyley.github.com/spongycastle/) (MIT X11 License) * [SpongyCastle](https://rtyley.github.io/spongycastle/) (MIT X11 License)
* [StickyListHeaders](https://github.com/emilsjolander/StickyListHeaders) (Apache License v2) * [StickyListHeaders](https://github.com/emilsjolander/StickyListHeaders) (Apache License v2)
* [TokenAutoComplete](https://github.com/splitwise/TokenAutoComplete) (Apache License v2) * [TokenAutoComplete](https://github.com/splitwise/TokenAutoComplete) (Apache License v2)
* [ZXing](https://github.com/zxing/zxing) (Apache License v2) * [ZXing](https://github.com/zxing/zxing) (Apache License v2)

View File

@@ -1,8 +1,8 @@
[//]: # (注意: 请把每个句子放在其本行中, Transifex把每一行放在它自己的位置) [//]: # (注意: 请把每个句子放在其本行中, Transifex把每一行放在它自己的位置)
[http://www.openkeychain.org](http://www.openkeychain.org) [https://www.openkeychain.org](https://www.openkeychain.org)
[OpenKeychain](http://www.openkeychain.org) 是安卓上的一个 OpenPGP 协议实现。 [OpenKeychain](https://www.openkeychain.org) is an OpenPGP implementation for Android.
许可协议GPLv3+ 许可协议GPLv3+
@@ -61,11 +61,11 @@
* [Markdown4J](https://github.com/jdcasey/markdown4j) (Apache 许可证 v2) * [Markdown4J](https://github.com/jdcasey/markdown4j) (Apache 许可证 v2)
* [MaterialDrawer](https://github.com/mikepenz/MaterialDrawer) (Apache 许可证 v2) * [MaterialDrawer](https://github.com/mikepenz/MaterialDrawer) (Apache 许可证 v2)
* [MiniDNS](https://github.com/rtreffer/minidns) (Apache 许可证 v2) * [MiniDNS](https://github.com/rtreffer/minidns) (Apache 许可证 v2)
* [OkHttp](http://square.github.io/okhttp/) (Apache License v2) * [OkHttp](https://square.github.io/okhttp/) (Apache License v2)
* [PagerSlidingTabStrip](https://github.com/jpardogo/PagerSlidingTabStrip) (Material Design)</a> (Apache 许可证 v2) * [PagerSlidingTabStrip](https://github.com/jpardogo/PagerSlidingTabStrip) (Material Design)</a> (Apache 许可证 v2)
* [SafeSlinger Exchange library](https://github.com/SafeSlingerProject/exchange-android) (MIT 许可证) * [SafeSlinger Exchange library](https://github.com/SafeSlingerProject/exchange-android) (MIT 许可证)
* [Snackbar](https://github.com/nispok/snackbar) (MIT 许可证) * [Snackbar](https://github.com/nispok/snackbar) (MIT 许可证)
* [SpongyCastle](http://rtyley.github.com/spongycastle/) (MIT X11 许可证) * [SpongyCastle](https://rtyley.github.io/spongycastle/) (MIT X11 License)
* [StickyListHeaders](https://github.com/emilsjolander/StickyListHeaders) (Apache 许可证 v2) * [StickyListHeaders](https://github.com/emilsjolander/StickyListHeaders) (Apache 许可证 v2)
* [TokenAutoComplete](https://github.com/splitwise/TokenAutoComplete) (Apache 许可证 v2) * [TokenAutoComplete](https://github.com/splitwise/TokenAutoComplete) (Apache 许可证 v2)
* [ZXing](https://github.com/zxing/zxing) (Apache 许可证 v2) * [ZXing](https://github.com/zxing/zxing) (Apache 许可证 v2)

View File

@@ -1,8 +1,8 @@
[//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!) [//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!)
[http://www.openkeychain.org](http://www.openkeychain.org) [https://www.openkeychain.org](https://www.openkeychain.org)
[OpenKeychain](http://www.openkeychain.org) is an OpenPGP implementation for Android. [OpenKeychain](https://www.openkeychain.org) is an OpenPGP implementation for Android.
License: GPLv3+ License: GPLv3+
@@ -61,11 +61,11 @@ License: GPLv3+
* [Markdown4J](https://github.com/jdcasey/markdown4j) (Apache License v2) * [Markdown4J](https://github.com/jdcasey/markdown4j) (Apache License v2)
* [MaterialDrawer](https://github.com/mikepenz/MaterialDrawer) (Apache License v2) * [MaterialDrawer](https://github.com/mikepenz/MaterialDrawer) (Apache License v2)
* [MiniDNS](https://github.com/rtreffer/minidns) (Apache License v2) * [MiniDNS](https://github.com/rtreffer/minidns) (Apache License v2)
* [OkHttp](http://square.github.io/okhttp/) (Apache License v2) * [OkHttp](https://square.github.io/okhttp/) (Apache License v2)
* [PagerSlidingTabStrip](https://github.com/jpardogo/PagerSlidingTabStrip) (Material Design) (Apache License v2) * [PagerSlidingTabStrip](https://github.com/jpardogo/PagerSlidingTabStrip) (Material Design) (Apache License v2)
* [SafeSlinger Exchange library](https://github.com/SafeSlingerProject/exchange-android) (MIT License) * [SafeSlinger Exchange library](https://github.com/SafeSlingerProject/exchange-android) (MIT License)
* [Snackbar](https://github.com/nispok/snackbar) (MIT License) * [Snackbar](https://github.com/nispok/snackbar) (MIT License)
* [SpongyCastle](http://rtyley.github.com/spongycastle/) (MIT X11 License) * [SpongyCastle](https://rtyley.github.io/spongycastle/) (MIT X11 License)
* [StickyListHeaders](https://github.com/emilsjolander/StickyListHeaders) (Apache License v2) * [StickyListHeaders](https://github.com/emilsjolander/StickyListHeaders) (Apache License v2)
* [TokenAutoComplete](https://github.com/splitwise/TokenAutoComplete) (Apache License v2) * [TokenAutoComplete](https://github.com/splitwise/TokenAutoComplete) (Apache License v2)
* [ZXing](https://github.com/zxing/zxing) (Apache License v2) * [ZXing](https://github.com/zxing/zxing) (Apache License v2)

View File

@@ -404,7 +404,6 @@
<!--Key trust--> <!--Key trust-->
<string name="key_trust_start_cloud_search">Vyhledat</string> <string name="key_trust_start_cloud_search">Vyhledat</string>
<string name="key_trust_results_prefix">Keybase.io nabízí “důkazy” které tvrdí, že vlastníkem tohoto klíče je: </string> <string name="key_trust_results_prefix">Keybase.io nabízí “důkazy” které tvrdí, že vlastníkem tohoto klíče je: </string>
<string name="key_trust_header_text">Poznámka: Keybase.io důkazy jsou experimentální fíčura OpenKeychainu. Doporučujeme vám navíc potvrdit je pomocí naskenování QR kódu nebo si vyměnit klíče pomocí NFC.</string>
<!--keybase proof stuff--> <!--keybase proof stuff-->
<string name="keybase_a_post">Příspěvek</string> <string name="keybase_a_post">Příspěvek</string>
<string name="keybase_twitter_proof">tweet</string> <string name="keybase_twitter_proof">tweet</string>

View File

@@ -189,20 +189,16 @@
<string name="pref_proxy_type_choice_http">HTTP</string> <string name="pref_proxy_type_choice_http">HTTP</string>
<string name="pref_proxy_type_choice_socks">SOCKS</string> <string name="pref_proxy_type_choice_socks">SOCKS</string>
<!--OrbotHelper strings--> <!--OrbotHelper strings-->
<string name="orbot_ignore_tor">Tor wird nicht verwendet</string>
<!--InstallDialogFragment strings--> <!--InstallDialogFragment strings-->
<string name="orbot_install_dialog_title">Orbot installieren, um Tor zu nutzen?</string> <string name="orbot_install_dialog_title">Orbot installieren, um Tor zu nutzen?</string>
<string name="orbot_install_dialog_install">Installieren</string> <string name="orbot_install_dialog_install">Installieren</string>
<string name="orbot_install_dialog_content">Du musst Orbot installiert und aktiviert haben, um Netzwerverkehr hindurchleiten zu können. Möchtest du es installieren?</string> <string name="orbot_install_dialog_content">Du musst Orbot installiert und aktiviert haben, um Netzwerverkehr hindurchleiten zu können. Möchtest du es installieren?</string>
<string name="orbot_install_dialog_cancel">Abbrechen</string> <string name="orbot_install_dialog_cancel">Abbrechen</string>
<string name="orbot_install_dialog_ignore_tor">Tor nicht verwendet</string>
<!--StartOrbotDialogFragment strings--> <!--StartOrbotDialogFragment strings-->
<string name="orbot_start_dialog_title">Orbot starten?</string> <string name="orbot_start_dialog_title">Orbot starten?</string>
<string name="orbot_start_dialog_content">Orbot scheint nicht zu laufen. Möchtest du es starten und mit Tor verbinden?</string>
<string name="orbot_start_btn">Orbot starten</string> <string name="orbot_start_btn">Orbot starten</string>
<string name="orbot_start_dialog_start">Orbot starten</string> <string name="orbot_start_dialog_start">Orbot starten</string>
<string name="orbot_start_dialog_cancel">Abbrechen</string> <string name="orbot_start_dialog_cancel">Abbrechen</string>
<string name="orbot_start_dialog_ignore_tor">Tor nicht verwendet</string>
<string name="user_id_no_name"><![CDATA[<kein Name>]]></string> <string name="user_id_no_name"><![CDATA[<kein Name>]]></string>
<string name="none"><![CDATA[<keine>]]></string> <string name="none"><![CDATA[<keine>]]></string>
<plurals name="n_keys"> <plurals name="n_keys">
@@ -593,7 +589,6 @@
<string name="key_trust_no_cloud_evidence">Es gibt keinen Nachweis aus dem Internet zur Vertrauenswürdigkeit dieses Schlüssels.</string> <string name="key_trust_no_cloud_evidence">Es gibt keinen Nachweis aus dem Internet zur Vertrauenswürdigkeit dieses Schlüssels.</string>
<string name="key_trust_start_cloud_search">Suche beginnen</string> <string name="key_trust_start_cloud_search">Suche beginnen</string>
<string name="key_trust_results_prefix">Keybase.io bietet \"Nachweise\" die bestätigen, dass der Schlüsselinhaber:</string> <string name="key_trust_results_prefix">Keybase.io bietet \"Nachweise\" die bestätigen, dass der Schlüsselinhaber:</string>
<string name="key_trust_header_text">Hinweis: Keybase.io-Nachweise sind ein experimentelles Feature von OpenKeychain. Wir rufen dazu auf, zusätzlich zur Bestätigung, QR-Codes zu nutzen oder Schlüssel via NFC auszutauschen.</string>
<!--keybase proof stuff--> <!--keybase proof stuff-->
<string name="keybase_narrative_twitter">Auf Twitter schreibt, als %s</string> <string name="keybase_narrative_twitter">Auf Twitter schreibt, als %s</string>
<string name="keybase_narrative_github">Auf GitHub bekannt ist, als %s</string> <string name="keybase_narrative_github">Auf GitHub bekannt ist, als %s</string>

View File

@@ -189,20 +189,16 @@
<string name="pref_proxy_type_choice_http">HTTP</string> <string name="pref_proxy_type_choice_http">HTTP</string>
<string name="pref_proxy_type_choice_socks">SOCKS</string> <string name="pref_proxy_type_choice_socks">SOCKS</string>
<!--OrbotHelper strings--> <!--OrbotHelper strings-->
<string name="orbot_ignore_tor">No usar Tor</string>
<!--InstallDialogFragment strings--> <!--InstallDialogFragment strings-->
<string name="orbot_install_dialog_title">¿Instalar Orbot para usar Tor?</string> <string name="orbot_install_dialog_title">¿Instalar Orbot para usar Tor?</string>
<string name="orbot_install_dialog_install">Instalar</string> <string name="orbot_install_dialog_install">Instalar</string>
<string name="orbot_install_dialog_content">Tiene que tener Orbot instalado y activado para proxyficar el tráfico a través de él. ¿Desea instalarlo?</string> <string name="orbot_install_dialog_content">Tiene que tener Orbot instalado y activado para proxyficar el tráfico a través de él. ¿Desea instalarlo?</string>
<string name="orbot_install_dialog_cancel">Cancelar</string> <string name="orbot_install_dialog_cancel">Cancelar</string>
<string name="orbot_install_dialog_ignore_tor">No usar Tor</string>
<!--StartOrbotDialogFragment strings--> <!--StartOrbotDialogFragment strings-->
<string name="orbot_start_dialog_title">¿Iniciar Orbot?</string> <string name="orbot_start_dialog_title">¿Iniciar Orbot?</string>
<string name="orbot_start_dialog_content">Orbot no parece estar corriendo. ¿Desea iniciarlo y conectar a Tor?</string>
<string name="orbot_start_btn">Iniciar Orbot</string> <string name="orbot_start_btn">Iniciar Orbot</string>
<string name="orbot_start_dialog_start">Iniciar Orbot</string> <string name="orbot_start_dialog_start">Iniciar Orbot</string>
<string name="orbot_start_dialog_cancel">Cancelar</string> <string name="orbot_start_dialog_cancel">Cancelar</string>
<string name="orbot_start_dialog_ignore_tor">No usar Tor</string>
<string name="user_id_no_name"><![CDATA[<no name>]]></string> <string name="user_id_no_name"><![CDATA[<no name>]]></string>
<string name="none"><![CDATA[<none>]]></string> <string name="none"><![CDATA[<none>]]></string>
<plurals name="n_keys"> <plurals name="n_keys">
@@ -593,7 +589,6 @@
<string name="key_trust_no_cloud_evidence">No hay comprobante desde Internet de la confiabilidad de esta clave.</string> <string name="key_trust_no_cloud_evidence">No hay comprobante desde Internet de la confiabilidad de esta clave.</string>
<string name="key_trust_start_cloud_search">Iniciar búsqueda</string> <string name="key_trust_start_cloud_search">Iniciar búsqueda</string>
<string name="key_trust_results_prefix">Keybase.io ofrece \"comprobantes\" que sostienen que el propietario de esta clave:</string> <string name="key_trust_results_prefix">Keybase.io ofrece \"comprobantes\" que sostienen que el propietario de esta clave:</string>
<string name="key_trust_header_text">Nota: Los comprobantes de Keybase.io son una característica experimental de OpenKeychain. Le animamos a que escanee los códigos QR o intercambie claves vía NFC además de confirmarlas.</string>
<!--keybase proof stuff--> <!--keybase proof stuff-->
<string name="keybase_narrative_twitter">Publica en Twitter como %s</string> <string name="keybase_narrative_twitter">Publica en Twitter como %s</string>
<string name="keybase_narrative_github">Es conocido en GitHub como %s</string> <string name="keybase_narrative_github">Es conocido en GitHub como %s</string>

View File

@@ -186,20 +186,16 @@
<string name="pref_proxy_type_choice_http">HTTP</string> <string name="pref_proxy_type_choice_http">HTTP</string>
<string name="pref_proxy_type_choice_socks">SOCKS</string> <string name="pref_proxy_type_choice_socks">SOCKS</string>
<!--OrbotHelper strings--> <!--OrbotHelper strings-->
<string name="orbot_ignore_tor">Ez erabili Tor</string>
<!--InstallDialogFragment strings--> <!--InstallDialogFragment strings-->
<string name="orbot_install_dialog_title">Ezarri Orbot edo erabili Tor?</string> <string name="orbot_install_dialog_title">Ezarri Orbot edo erabili Tor?</string>
<string name="orbot_install_dialog_install">Ezarri</string> <string name="orbot_install_dialog_install">Ezarri</string>
<string name="orbot_install_dialog_content">Orbot ezarrita eta proxy trafiko igaropena gaituta eduki behar duzu. Nahi duzu ezartzea?</string> <string name="orbot_install_dialog_content">Orbot ezarrita eta proxy trafiko igaropena gaituta eduki behar duzu. Nahi duzu ezartzea?</string>
<string name="orbot_install_dialog_cancel">Ezeztatu</string> <string name="orbot_install_dialog_cancel">Ezeztatu</string>
<string name="orbot_install_dialog_ignore_tor">Ez erabili Tor</string>
<!--StartOrbotDialogFragment strings--> <!--StartOrbotDialogFragment strings-->
<string name="orbot_start_dialog_title">Abiarazi Orbot?</string> <string name="orbot_start_dialog_title">Abiarazi Orbot?</string>
<string name="orbot_start_dialog_content">Ez dirudi Orbot ekinean dagoenik. Nahi duzu abiaraztea eta Tor-era elkartzea?</string>
<string name="orbot_start_btn">Abiarazi Orbot</string> <string name="orbot_start_btn">Abiarazi Orbot</string>
<string name="orbot_start_dialog_start">Abiarazi Orbot</string> <string name="orbot_start_dialog_start">Abiarazi Orbot</string>
<string name="orbot_start_dialog_cancel">Ezeztatu</string> <string name="orbot_start_dialog_cancel">Ezeztatu</string>
<string name="orbot_start_dialog_ignore_tor">Ez erabili Tor</string>
<string name="user_id_no_name"><![CDATA[<izengabe>]]></string> <string name="user_id_no_name"><![CDATA[<izengabe>]]></string>
<string name="none"><![CDATA[<ezer ez>]]></string> <string name="none"><![CDATA[<ezer ez>]]></string>
<plurals name="n_keys"> <plurals name="n_keys">
@@ -587,7 +583,6 @@
<!--Key trust--> <!--Key trust-->
<string name="key_trust_start_cloud_search">Hasi bilaketa</string> <string name="key_trust_start_cloud_search">Hasi bilaketa</string>
<string name="key_trust_results_prefix">Keybase.io \"probak\" eskaintzen ditu giltza honen jabea baieztatzeko:</string> <string name="key_trust_results_prefix">Keybase.io \"probak\" eskaintzen ditu giltza honen jabea baieztatzeko:</string>
<string name="key_trust_header_text">Oharra: Keybase.io egiaztapenak OpenKeychain-en ezaugarri esperimental bat da. QR kodeak eskaneatzea edo giltzak NFC bidez aldatzea gomendatzen dizugu hauek baieztatu ahal izateko.</string>
<!--keybase proof stuff--> <!--keybase proof stuff-->
<string name="keybase_narrative_twitter">Argitaratu Twitter-en honela %s</string> <string name="keybase_narrative_twitter">Argitaratu Twitter-en honela %s</string>
<string name="keybase_narrative_github">GitHub-en %s bezala ezagutzen da</string> <string name="keybase_narrative_github">GitHub-en %s bezala ezagutzen da</string>

View File

@@ -189,20 +189,16 @@
<string name="pref_proxy_type_choice_http">HTTP</string> <string name="pref_proxy_type_choice_http">HTTP</string>
<string name="pref_proxy_type_choice_socks">SOCKS</string> <string name="pref_proxy_type_choice_socks">SOCKS</string>
<!--OrbotHelper strings--> <!--OrbotHelper strings-->
<string name="orbot_ignore_tor">Ne pas utiliser Tor</string>
<!--InstallDialogFragment strings--> <!--InstallDialogFragment strings-->
<string name="orbot_install_dialog_title">Installer Orbot pour utiliser Tor ?</string> <string name="orbot_install_dialog_title">Installer Orbot pour utiliser Tor ?</string>
<string name="orbot_install_dialog_install">Installer</string> <string name="orbot_install_dialog_install">Installer</string>
<string name="orbot_install_dialog_content">Orbot doit être installé et doit relayer le trafic. Voulez-vous l\'installer maintenant ?</string> <string name="orbot_install_dialog_content">Orbot doit être installé et doit relayer le trafic. Voulez-vous l\'installer maintenant ?</string>
<string name="orbot_install_dialog_cancel">Annuler</string> <string name="orbot_install_dialog_cancel">Annuler</string>
<string name="orbot_install_dialog_ignore_tor">Ne pas utiliser Tor</string>
<!--StartOrbotDialogFragment strings--> <!--StartOrbotDialogFragment strings-->
<string name="orbot_start_dialog_title">Démarrer Orbot ?</string> <string name="orbot_start_dialog_title">Démarrer Orbot ?</string>
<string name="orbot_start_dialog_content">Orbot ne semble pas tourner. Voulez-vous le démarrer et vous connecter à Tor ?</string>
<string name="orbot_start_btn">Démarrer Orbot</string> <string name="orbot_start_btn">Démarrer Orbot</string>
<string name="orbot_start_dialog_start">Démarrer Orbot</string> <string name="orbot_start_dialog_start">Démarrer Orbot</string>
<string name="orbot_start_dialog_cancel">Annuler</string> <string name="orbot_start_dialog_cancel">Annuler</string>
<string name="orbot_start_dialog_ignore_tor">Ne pas utiliser Tor</string>
<string name="user_id_no_name"><![CDATA[<no name>]]></string> <string name="user_id_no_name"><![CDATA[<no name>]]></string>
<string name="none"><![CDATA[<none>]]></string> <string name="none"><![CDATA[<none>]]></string>
<plurals name="n_keys"> <plurals name="n_keys">
@@ -593,7 +589,6 @@
<string name="key_trust_no_cloud_evidence">Aucune preuve en provenance de l\'Internet sur la fiabilité de cette clef.</string> <string name="key_trust_no_cloud_evidence">Aucune preuve en provenance de l\'Internet sur la fiabilité de cette clef.</string>
<string name="key_trust_start_cloud_search">Lancer la recherche</string> <string name="key_trust_start_cloud_search">Lancer la recherche</string>
<string name="key_trust_results_prefix">Keybase.io offre des « preuves » affirmant que le propriétaire de cette clef : </string> <string name="key_trust_results_prefix">Keybase.io offre des « preuves » affirmant que le propriétaire de cette clef : </string>
<string name="key_trust_header_text">Note : les preuves de keybase.io sont une fonction expérimentales d\'OpenKeychain. Nous vous encourageons à lire des codes QR ou à échanger des clefs via NFC en plus de les confirmer.</string>
<!--keybase proof stuff--> <!--keybase proof stuff-->
<string name="keybase_narrative_twitter">Publie sur Twitter en tant que %s</string> <string name="keybase_narrative_twitter">Publie sur Twitter en tant que %s</string>
<string name="keybase_narrative_github">Est connu sur GitHub en tant que %s</string> <string name="keybase_narrative_github">Est connu sur GitHub en tant que %s</string>

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