Merge branch 'master' into v/decrypt-key-lookup
@@ -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
|
||||||
|
|||||||
59
Graphics/drawables/ic_action_encrypt_paste.svg
Normal 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 |
@@ -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 |
1
Graphics/drawables/originals/content-paste.svg
Normal 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
|
After Width: | Height: | Size: 162 KiB |
@@ -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"
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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 doesn’t say anything about revoked keys
|
entry.setRevoked(false); // keybase doesn’t 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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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!");
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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!");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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))) {
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
|
After Width: | Height: | Size: 590 B |
|
After Width: | Height: | Size: 433 B |
|
After Width: | Height: | Size: 684 B |
|
Before Width: | Height: | Size: 0 B After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 986 B |
|
After Width: | Height: | Size: 1.3 KiB |
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||