diff options
326 files changed, 20503 insertions, 7202 deletions
diff --git a/.travis.yml b/.travis.yml index a2959e0a..2930c250 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,8 +3,11 @@ android: components: - platform-tools - tools + - build-tools-23.0.2 + - build-tools-23.0.1 + - build-tools-23.0.0 - build-tools-22.0.1 - build-tools-21.1.2 - build-tools-19.1.0 - - android-22 + - android-23 - extra-android-m2repository diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fbbaf83..0407f2d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,112 @@ ###Changelog +####Version 1.9.3 +* expert setting that enables host and port configuration +* expert setting opt-out of bookmark autojoin handling +* offer to rejoin a conference after server sent unavailable +* internal rewrites + +####Version 1.9.2 +* prevent startup crash on Sailfish OS +* minor bug fixes + +####Version 1.9.1 +* minor bug fixes incl. a workaround for nimbuzz.com + +####Version 1.9.0 +* Per conference notification settings +* Let user decide whether to compress pictures +* Support for XEP-0368 +* Ask user to exclude Conversations from battery optimizations + +####Version 1.8.4 +* prompt to trust own OMEMO devices +* fixed rotation issues in avatar publication +* invite non-contact JIDs to conferences + +####Version 1.8.3 +* brought text selection back + +####Version 1.8.2 +* fixed stuck at 'connecting...' bug +* make message box behave correctly with multiple links + +####Version 1.8.1 +* enabled direct share on Android 6.0 +* ask for permissions on Android 6.0 +* notify on MAM catchup messages +* bug fixes + +####Version 1.8.0 +* TOR/ORBOT support in advanced settings +* show vcard avatars of participants in a conference + +####Version 1.7.3 +* fixed PGP encrypted file transfer +* fixed repeating messages in slack conferences + +####Version 1.7.2 +* decode PGP messages in background + + +####Versrion 1.7.1 +* performance improvements when opening a conversation + +####Version 1.7.0 +* CAPTCHA support +* SASL EXTERNAL (client certifiates) +* fetching MUC history via MAM +* redownload deleted files from HTTP hosts +* Expert setting to automatically set presence +* bug fixes + +####Version 1.6.11 +* tab completion for MUC nicks +* history export +* bug fixes + +####Version 1.6.10 +* fixed facebook login +* fixed bug with ejabberd mam +* use official HTTP File Upload namespace + +####Version 1.6.9 +* basic keyboard support + +####Version 1.6.8 +* reworked 'enter is send' setting +* reworked DNS server discovery on lolipop devices +* various bug fixes + +####Version 1.6.7 +* bug fixes + +####Version 1.6.6 +* best 1.6 release yet + +####Version 1.6.5 +* more OMEMO fixes + +####Version 1.6.4 +* setting to enable white chat bubbles +* limit OMEMO key publish attempts to work around broken PEP +* various bug fixes + +####Version 1.6.3 +* bug fixes + +####Version 1.6.2 +* fixed issues with connection time out when server does not support ping + +####Version 1.6.1 +* fixed crashes + +####Version 1.6.0 +* new multi-end-to-multi-end encryption method +* redesigned chat bubbles +* show unexpected encryption changes as red chat bubbles +* always notify in private/non-anonymous conferences + ####Version 1.5.1 * fixed rare crashes * improved otr support diff --git a/art/conversations_baloon.svg b/art/conversations_baloon.svg index 140a84c7..96b17d2b 100644 --- a/art/conversations_baloon.svg +++ b/art/conversations_baloon.svg @@ -2,6 +2,7 @@ <!-- Created with Inkscape (http://www.inkscape.org/) --> <svg + xmlns:osb="http://www.openswatchbook.org/uri/2009/osb" 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#" @@ -10,298 +11,256 @@ xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="512" - height="512" - id="svg2" + width="57mm" + height="57mm" + viewBox="0 0 201.96849 201.96849" + id="svg4211" version="1.1" - inkscape:version="0.48.5 r10040" - sodipodi:docname="conversations_baloon.svg" - inkscape:export-filename="/home/diesys/diesys/grafica/conversation/conversation_bubble.png" - inkscape:export-xdpi="100" - inkscape:export-ydpi="100"> + inkscape:version="0.91 r13725" + sodipodi:docname="conversations_baloon.svg"> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + showgrid="false" + fit-margin-top="0" + fit-margin-left="0" + fit-margin-right="0" + fit-margin-bottom="0" + showguides="false" + inkscape:zoom="2.2196812" + inkscape:cx="39.109276" + inkscape:cy="132.27753" + inkscape:window-width="1600" + inkscape:window-height="836" + inkscape:window-x="0" + inkscape:window-y="27" + inkscape:window-maximized="1" + inkscape:current-layer="layer8" /> <defs - id="defs4"> + id="defs4213"> <linearGradient - inkscape:collect="always" - id="linearGradient3874"> + osb:paint="solid" + id="linearGradient5393"> <stop - style="stop-color:#00a000;stop-opacity:1;" + id="stop5395" offset="0" - id="stop3876" /> - <stop - style="stop-color:#00a000;stop-opacity:0;" - offset="1" - id="stop3878" /> + style="stop-color:#ffffff;stop-opacity:1;" /> </linearGradient> - <linearGradient - inkscape:collect="always" - id="linearGradient3913"> - <stop - style="stop-color:#ffffff;stop-opacity:1;" - offset="0" - id="stop3915" /> - <stop - style="stop-color:#ffffff;stop-opacity:0;" - offset="1" - id="stop3917" /> - </linearGradient> - <linearGradient - inkscape:collect="always" - id="linearGradient3818"> - <stop - style="stop-color:#669900;stop-opacity:1" - offset="0" - id="stop3820" /> - <stop - style="stop-color:#99cc00;stop-opacity:1" - offset="1" - id="stop3822" /> - </linearGradient> - <radialGradient - inkscape:collect="always" - xlink:href="#linearGradient3818" - id="radialGradient3824" - cx="212.07048" - cy="1045.9178" - fx="212.07048" - fy="1045.9178" - r="238.57143" - gradientTransform="matrix(1.9491621,-0.90817722,0.65829208,1.4128498,-879.63121,-248.98648)" - gradientUnits="userSpaceOnUse" /> + <clipPath + id="clipPath4831" + clipPathUnits="userSpaceOnUse"> + <circle + style="display:inline;opacity:1;fill:#a00e00;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="circle4833" + cx="883.16943" + cy="677.19611" + r="229.80969" /> + </clipPath> + <clipPath + id="clipPath4859" + clipPathUnits="userSpaceOnUse"> + <circle + style="display:inline;opacity:1;fill:#a00e00;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="circle4861" + cx="883.16943" + cy="677.19611" + r="229.80969" /> + </clipPath> + <clipPath + id="clipPath5624" + clipPathUnits="userSpaceOnUse"> + <g + style="display:inline" + id="g5626" + transform="matrix(0.3835576,0,0,0.3835576,-250.60108,-156.11014)"> + <path + sodipodi:nodetypes="ccsssc" + inkscape:connector-curvature="0" + id="path5628" + d="m 1120.8042,772.36056 -118.0025,103.66316 118.5792,46.01918 c 8.4859,3.29325 19.6524,7.94481 27.2622,0.71376 7.3868,-7.01907 5.6502,-14.13839 3.0935,-24.54095 z" + style="display:inline;fill:#4caf50;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <circle + transform="matrix(1.0878566,0,0,1.0878566,-57.401992,-79.686482)" + clip-path="url(#clipPath4859)" + r="229.80969" + cy="677.19611" + cx="883.16943" + id="circle5630" + style="display:inline;opacity:1;fill:#4caf50;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </g> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10653"> + <g + style="display:inline" + id="g10655" + transform="matrix(0.3835576,0,0,0.3835576,-250.60108,-156.11015)" + inkscape:export-xdpi="100" + inkscape:export-ydpi="100"> + <path + sodipodi:nodetypes="ccsssc" + inkscape:connector-curvature="0" + id="path10657" + d="m 1120.8042,772.36056 -118.0025,103.66316 118.5792,46.01918 c 8.4859,3.29325 19.6524,7.94481 27.2622,0.71376 7.3868,-7.01907 5.6502,-14.13839 3.0935,-24.54095 z" + style="display:inline;fill:#4caf50;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <circle + transform="matrix(1.0878566,0,0,1.0878566,-57.401992,-79.686482)" + clip-path="url(#clipPath4859)" + r="229.80969" + cy="677.19611" + cx="883.16943" + id="circle10659" + style="display:inline;opacity:1;fill:#4caf50;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </g> + </clipPath> <radialGradient inkscape:collect="always" xlink:href="#linearGradient3913" - id="radialGradient3919" - cx="362.98563" - cy="379.77524" - fx="362.98563" - fy="379.77524" - r="139.95312" - gradientTransform="matrix(1.3800477,1.0445431,-1.3325077,1.7605059,339.09383,-577.83938)" - gradientUnits="userSpaceOnUse" /> - <linearGradient + id="radialGradient3883" gradientUnits="userSpaceOnUse" - y2="-155.75885" - x2="114.59022" - y1="35.545681" - x1="114.55434" - id="linearGradient3794" - xlink:href="#linearGradient3788" - inkscape:collect="always" /> - <linearGradient - id="linearGradient3788"> - <stop - id="stop3790" - offset="0" - style="stop-color:#1eed00;stop-opacity:1;" /> - <stop - id="stop3792" - offset="1" - style="stop-color:#abff28;stop-opacity:1;" /> - </linearGradient> - <linearGradient - id="linearGradient3821"> - <stop - style="stop-color:#ff283d;stop-opacity:1;" - offset="0" - id="stop3823" /> - <stop - style="stop-color:#ff28ae;stop-opacity:1;" - offset="1" - id="stop3825" /> - </linearGradient> - <linearGradient - id="linearGradient4543"> - <stop - style="stop-color:#2e45bf;stop-opacity:1;" - offset="0" - id="stop4545" /> - <stop - style="stop-color:#28a7ff;stop-opacity:1;" - offset="1" - id="stop4547" /> - </linearGradient> + gradientTransform="matrix(0.68662089,-0.30388739,0.24146012,0.54605188,-300.74233,-264.46964)" + cx="262.33273" + cy="945.23846" + fx="262.33273" + fy="945.23846" + r="185.49754" /> <linearGradient inkscape:collect="always" - id="linearGradient4098"> + id="linearGradient3913"> <stop style="stop-color:#ffffff;stop-opacity:1;" offset="0" - id="stop4100" /> + id="stop3915" /> <stop - style="stop-color:#e6e6e6;stop-opacity:1" + style="stop-color:#ffffff;stop-opacity:0;" offset="1" - id="stop4102" /> + id="stop3917" /> </linearGradient> - <linearGradient - inkscape:collect="always" - xlink:href="#linearGradient4098" - id="linearGradient3833" - x1="273.81851" - y1="764.74677" - x2="304.14023" - y2="936.47272" - gradientUnits="userSpaceOnUse" /> - <linearGradient - inkscape:collect="always" - xlink:href="#linearGradient4098" - id="linearGradient3853" - gradientUnits="userSpaceOnUse" - x1="273.81851" - y1="764.74677" - x2="304.14023" - y2="936.47272" /> - <radialGradient - inkscape:collect="always" - xlink:href="#linearGradient3818" - id="radialGradient3863" - cx="262.33273" - cy="945.23846" - fx="262.33273" - fy="945.23846" - r="185.49754" - gradientTransform="matrix(1.2253203,-0.54206726,0.43090148,0.97403458,-466.4135,170.11831)" - gradientUnits="userSpaceOnUse" /> - <radialGradient - inkscape:collect="always" - xlink:href="#linearGradient3818" - id="radialGradient3866" - gradientUnits="userSpaceOnUse" - gradientTransform="matrix(1.2253203,-0.54206726,0.43090148,0.97403458,-466.4135,170.11831)" - cx="262.33273" - cy="945.23846" - fx="262.33273" - fy="945.23846" - r="185.49754" /> - <radialGradient - inkscape:collect="always" - xlink:href="#linearGradient3913" - id="radialGradient3873" - gradientUnits="userSpaceOnUse" - gradientTransform="matrix(1.3800477,1.0445431,-1.3325077,1.7605059,339.09383,-577.83938)" - cx="321.75275" - cy="386.38751" - fx="321.75275" - fy="386.38751" - r="139.95312" /> - <radialGradient - inkscape:collect="always" - xlink:href="#linearGradient3818" - id="radialGradient3880" - gradientUnits="userSpaceOnUse" - gradientTransform="matrix(1.2253203,-0.54206726,0.43090148,0.97403458,-466.4135,-370.24387)" - cx="262.33273" - cy="945.23846" - fx="262.33273" - fy="945.23846" - r="185.49754" /> - <radialGradient - inkscape:collect="always" - xlink:href="#linearGradient3913" - id="radialGradient3883" - gradientUnits="userSpaceOnUse" - gradientTransform="matrix(1.4430075,-0.63865195,0.50745433,1.1475866,-594.40824,44.803037)" - cx="262.33273" - cy="945.23846" - fx="262.33273" - fy="945.23846" - r="185.49754" /> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath5315"> + <g + inkscape:export-ydpi="100" + inkscape:export-xdpi="100" + transform="matrix(0.3835576,0,0,0.3835576,-246.60108,-156.11013)" + id="g5317" + style="display:inline;fill:#00a000;fill-opacity:1"> + <path + style="display:inline;fill:#00a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 1120.8042,772.36056 -118.0025,103.66316 118.5792,46.01918 c 8.4859,3.29325 19.6524,7.94481 27.2622,0.71376 7.3868,-7.01907 5.6502,-14.13839 3.0935,-24.54095 z" + id="path5319" + inkscape:connector-curvature="0" + sodipodi:nodetypes="ccsssc" /> + <circle + style="display:inline;opacity:1;fill:#00a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="circle5321" + cx="883.16943" + cy="677.19611" + r="229.80969" + clip-path="url(#clipPath4859)" + transform="matrix(1.0878566,0,0,1.0878566,-57.401992,-79.686482)" /> + </g> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath6882"> + <path + inkscape:connector-curvature="0" + id="path6884" + d="M 99.88867,-2.3837657e-4 A 95.889392,95.889392 0 0 0 4,95.888436 95.889392,95.889392 0 0 0 99.88867,191.77906 95.889392,95.889392 0 0 0 142.59375,181.70093 l 0.12695,0.0137 40.79297,15.83204 c 3.25479,1.26313 7.53628,3.04697 10.45508,0.27343 2.83326,-2.69222 2.16811,-5.42213 1.1875,-9.41211 l -11.34766,-46.16797 a 95.889392,95.889392 0 0 1 -0.002,0.002 l 0,-0.008 0.002,0.006 A 95.889392,95.889392 0 0 0 195.7793,95.888466 95.889392,95.889392 0 0 0 99.88867,-2.0837657e-4 Z" + style="display:inline;opacity:1;fill:#00a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath6886"> + <path + inkscape:connector-curvature="0" + id="path6888" + d="M 99.88867,-2.3837657e-4 A 95.889392,95.889392 0 0 0 4,95.888436 95.889392,95.889392 0 0 0 99.88867,191.77906 95.889392,95.889392 0 0 0 142.59375,181.70093 l 0.12695,0.0137 40.79297,15.83204 c 3.25479,1.26313 7.53628,3.04697 10.45508,0.27343 2.83326,-2.69222 2.16811,-5.42213 1.1875,-9.41211 l -11.34766,-46.16797 a 95.889392,95.889392 0 0 1 -0.002,0.002 l 0,-0.008 0.002,0.006 A 95.889392,95.889392 0 0 0 195.7793,95.888466 95.889392,95.889392 0 0 0 99.88867,-2.0837657e-4 Z" + style="display:inline;opacity:1;fill:#00a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath6890"> + <path + inkscape:connector-curvature="0" + id="path6892" + d="M 99.88867,-2.3837657e-4 A 95.889392,95.889392 0 0 0 4,95.888436 95.889392,95.889392 0 0 0 99.88867,191.77906 95.889392,95.889392 0 0 0 142.59375,181.70093 l 0.12695,0.0137 40.79297,15.83204 c 3.25479,1.26313 7.53628,3.04697 10.45508,0.27343 2.83326,-2.69222 2.16811,-5.42213 1.1875,-9.41211 l -11.34766,-46.16797 a 95.889392,95.889392 0 0 1 -0.002,0.002 l 0,-0.008 0.002,0.006 A 95.889392,95.889392 0 0 0 195.7793,95.888466 95.889392,95.889392 0 0 0 99.88867,-2.0837657e-4 Z" + style="display:inline;opacity:1;fill:#00a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath6894"> + <path + inkscape:connector-curvature="0" + id="path6896" + d="M 99.88867,-2.3837657e-4 A 95.889392,95.889392 0 0 0 4,95.888436 95.889392,95.889392 0 0 0 99.88867,191.77906 95.889392,95.889392 0 0 0 142.59375,181.70093 l 0.12695,0.0137 40.79297,15.83204 c 3.25479,1.26313 7.53628,3.04697 10.45508,0.27343 2.83326,-2.69222 2.16811,-5.42213 1.1875,-9.41211 l -11.34766,-46.16797 a 95.889392,95.889392 0 0 1 -0.002,0.002 l 0,-0.008 0.002,0.006 A 95.889392,95.889392 0 0 0 195.7793,95.888466 95.889392,95.889392 0 0 0 99.88867,-2.0837657e-4 Z" + style="display:inline;opacity:1;fill:#00a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath6898"> + <path + inkscape:connector-curvature="0" + id="path6900" + d="M 99.88867,-2.3837657e-4 A 95.889392,95.889392 0 0 0 4,95.888436 95.889392,95.889392 0 0 0 99.88867,191.77906 95.889392,95.889392 0 0 0 142.59375,181.70093 l 0.12695,0.0137 40.79297,15.83204 c 3.25479,1.26313 7.53628,3.04697 10.45508,0.27343 2.83326,-2.69222 2.16811,-5.42213 1.1875,-9.41211 l -11.34766,-46.16797 a 95.889392,95.889392 0 0 1 -0.002,0.002 l 0,-0.008 0.002,0.006 A 95.889392,95.889392 0 0 0 195.7793,95.888466 95.889392,95.889392 0 0 0 99.88867,-2.0837657e-4 Z" + style="display:inline;opacity:1;fill:#00a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath6902"> + <path + inkscape:connector-curvature="0" + id="path6904" + d="M 99.88867,-2.3837657e-4 A 95.889392,95.889392 0 0 0 4,95.888436 95.889392,95.889392 0 0 0 99.88867,191.77906 95.889392,95.889392 0 0 0 142.59375,181.70093 l 0.12695,0.0137 40.79297,15.83204 c 3.25479,1.26313 7.53628,3.04697 10.45508,0.27343 2.83326,-2.69222 2.16811,-5.42213 1.1875,-9.41211 l -11.34766,-46.16797 a 95.889392,95.889392 0 0 1 -0.002,0.002 l 0,-0.008 0.002,0.006 A 95.889392,95.889392 0 0 0 195.7793,95.888466 95.889392,95.889392 0 0 0 99.88867,-2.0837657e-4 Z" + style="display:inline;opacity:1;fill:#00a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath6906"> + <path + inkscape:connector-curvature="0" + id="path6908" + d="M 99.88867,-2.3837657e-4 A 95.889392,95.889392 0 0 0 4,95.888436 95.889392,95.889392 0 0 0 99.88867,191.77906 95.889392,95.889392 0 0 0 142.59375,181.70093 l 0.12695,0.0137 40.79297,15.83204 c 3.25479,1.26313 7.53628,3.04697 10.45508,0.27343 2.83326,-2.69222 2.16811,-5.42213 1.1875,-9.41211 l -11.34766,-46.16797 a 95.889392,95.889392 0 0 1 -0.002,0.002 l 0,-0.008 0.002,0.006 A 95.889392,95.889392 0 0 0 195.7793,95.888466 95.889392,95.889392 0 0 0 99.88867,-2.0837657e-4 Z" + style="display:inline;opacity:1;fill:#00a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath6910"> + <path + inkscape:connector-curvature="0" + id="path6912" + d="M 99.88867,-2.3837657e-4 A 95.889392,95.889392 0 0 0 4,95.888436 95.889392,95.889392 0 0 0 99.88867,191.77906 95.889392,95.889392 0 0 0 142.59375,181.70093 l 0.12695,0.0137 40.79297,15.83204 c 3.25479,1.26313 7.53628,3.04697 10.45508,0.27343 2.83326,-2.69222 2.16811,-5.42213 1.1875,-9.41211 l -11.34766,-46.16797 a 95.889392,95.889392 0 0 1 -0.002,0.002 l 0,-0.008 0.002,0.006 A 95.889392,95.889392 0 0 0 195.7793,95.888466 95.889392,95.889392 0 0 0 99.88867,-2.0837657e-4 Z" + style="display:inline;opacity:1;fill:#00a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> <filter inkscape:collect="always" - id="filter3895"> + style="color-interpolation-filters:sRGB" + id="filter5640" + x="-0.012227737" + width="1.0244555" + y="-0.011780591" + height="1.0235612"> <feGaussianBlur inkscape:collect="always" - stdDeviation="2.0013623" - id="feGaussianBlur3897" /> + stdDeviation="0.9782166" + id="feGaussianBlur5642" /> </filter> - <radialGradient - inkscape:collect="always" - xlink:href="#linearGradient3874" - id="radialGradient3881" - cx="150.35715" - cy="236.28571" - fx="150.35715" - fy="236.28571" - r="26.887305" - gradientTransform="matrix(1,0,0,0.98671703,0,3.1385771)" - gradientUnits="userSpaceOnUse" /> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath5745"> + <path + inkscape:connector-curvature="0" + id="path5747" + d="M 99.908581,-2.3831968e-4 A 95.889392,95.889392 0 0 0 4.0199102,95.888436 95.889392,95.889392 0 0 0 99.908581,191.77906 95.889392,95.889392 0 0 0 142.61366,181.70093 l 0.12695,0.0137 40.79297,15.83204 c 3.25479,1.26313 7.53628,3.04697 10.45508,0.27343 2.83326,-2.69222 2.16811,-5.42213 1.1875,-9.41211 L 183.8285,142.24002 a 95.889392,95.889392 0 0 1 -0.002,0.002 l 0,-0.008 0.002,0.006 A 95.889392,95.889392 0 0 0 195.79921,95.888466 95.889392,95.889392 0 0 0 99.908581,-2.0831968e-4 Z" + style="display:inline;opacity:1;fill:#00a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> </defs> - <sodipodi:namedview - id="base" - pagecolor="#ffffff" - bordercolor="#666666" - borderopacity="1.0" - inkscape:pageopacity="0.0" - inkscape:pageshadow="2" - inkscape:zoom="1.4142136" - inkscape:cx="385.13513" - inkscape:cy="237.84331" - inkscape:document-units="px" - inkscape:current-layer="layer4" - showgrid="false" - inkscape:window-width="2560" - inkscape:window-height="1020" - inkscape:window-x="0" - inkscape:window-y="27" - inkscape:window-maximized="1" - showguides="true" - inkscape:guide-bbox="true" - inkscape:snap-to-guides="true" - inkscape:snap-grids="false" - inkscape:object-paths="true" - inkscape:object-nodes="false" - inkscape:snap-nodes="false"> - <sodipodi:guide - orientation="1,0" - position="0,534.28571" - id="guide3004" /> - <sodipodi:guide - orientation="0,1" - position="394.28571,511.42857" - id="guide3006" /> - <sodipodi:guide - orientation="1,0" - position="511.42857,320" - id="guide3008" /> - <sodipodi:guide - orientation="0,1" - position="401.42857,0" - id="guide3010" /> - <sodipodi:guide - orientation="1,0" - position="17.142857,258.57143" - id="guide3012" /> - <sodipodi:guide - orientation="0,1" - position="327.14286,494.28571" - id="guide3014" /> - <sodipodi:guide - orientation="0,1" - position="324.28571,17.142857" - id="guide3016" /> - <sodipodi:guide - orientation="1,0" - position="494.28571,237.14286" - id="guide3018" /> - <sodipodi:guide - orientation="1,0" - position="255.71429,302.85714" - id="guide3022" /> - <sodipodi:guide - orientation="1,0" - position="660,-315" - id="guide3904" /> - <sodipodi:guide - orientation="0,1" - position="554.28571,475.71429" - id="guide3931" /> - <sodipodi:guide - orientation="0,1" - position="581.42857,244.28571" - id="guide3933" /> - </sodipodi:namedview> <metadata - id="metadata7"> + id="metadata4216"> <rdf:RDF> <cc:Work rdf:about=""> @@ -313,78 +272,156 @@ </rdf:RDF> </metadata> <g - inkscape:label="Layer 1" inkscape:groupmode="layer" - id="layer1" - transform="translate(0,-540.36218)" + id="layer9" + inkscape:label="shaddow" + transform="translate(-4,2.6816164)" style="display:inline"> <path - d="m 253.34375,605.78125 c -107.90463,0 -195.9375,85.86121 -195.9375,191.84375 0,105.98253 88.02779,191.90625 195.9375,191.90625 33.55862,0 59.4324,-6.89467 88.96875,-17.625 l 93.8125,37.81255 A 12.359798,12.359798 0 0 0 452.75,995.28125 L 427.34375,892.59375 C 443.67389,863.93074 449.25,831.2919 449.25,797.625 449.25,691.64506 361.24842,605.78125 253.34375,605.78125 z" - id="path3885" - style="opacity:0.6;fill:#000000;fill-opacity:1;stroke:none;filter:url(#filter3895)" - inkscape:original="M 253.34375 618.125 C 151.96941 618.125 69.75 698.4746 69.75 797.625 C 69.75 896.77539 151.96941 977.1875 253.34375 977.1875 C 287.00054 977.1875 311.5728 970.27778 342.65625 958.71875 L 440.75 998.25 L 414.1875 890.8125 C 431.0772 863.65332 436.90625 831.73711 436.90625 797.625 C 436.90625 698.4746 354.71813 618.125 253.34375 618.125 z " - inkscape:radius="12.358562" - sodipodi:type="inkscape:offset" - transform="matrix(1.1776575,0,0,1.1781783,-45.132882,-150.91395)" /> - <path - sodipodi:type="inkscape:offset" - inkscape:radius="12.358562" - inkscape:original="M 253.34375 618.125 C 151.96941 618.125 69.75 698.4746 69.75 797.625 C 69.75 896.77539 151.96941 977.1875 253.34375 977.1875 C 287.00054 977.1875 311.5728 970.27778 342.65625 958.71875 L 440.75 998.25 L 414.1875 890.8125 C 431.0772 863.65332 436.90625 831.73711 436.90625 797.625 C 436.90625 698.4746 354.71813 618.125 253.34375 618.125 z " - style="fill:#00a000;fill-opacity:1;stroke:none" - id="path3868" - d="m 253.34375,605.78125 c -107.90463,0 -195.9375,85.86121 -195.9375,191.84375 0,105.98253 88.02779,191.90625 195.9375,191.90625 33.55862,0 59.4324,-6.89467 88.96875,-17.625 l 93.8125,37.81255 A 12.359798,12.359798 0 0 0 452.75,995.28125 L 427.34375,892.59375 C 443.67389,863.93074 449.25,831.2919 449.25,797.625 449.25,691.64506 361.24842,605.78125 253.34375,605.78125 z" - transform="matrix(1.1776575,0,0,1.1781783,-45.132882,-155.6267)" /> + inkscape:connector-curvature="0" + id="path6914" + d="M 104.88867,0.06226191 A 95.889392,95.889392 0 0 0 8.9999996,95.950936 95.889392,95.889392 0 0 0 104.88867,191.84156 95.889392,95.889392 0 0 0 147.59375,181.76343 l 0.12695,0.0137 40.79297,15.83204 c 3.25479,1.26313 7.53628,3.04697 10.45508,0.27343 2.83326,-2.69222 2.16811,-5.42213 1.1875,-9.41211 l -11.34766,-46.16797 a 95.889392,95.889392 0 0 1 -0.002,0.002 l 0,-0.008 0.002,0.006 A 95.889392,95.889392 0 0 0 200.7793,95.950966 95.889392,95.889392 0 0 0 104.88867,0.06229191 Z" + style="display:inline;opacity:0.4;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter5640)" /> + </g> + <g + style="display:inline" + inkscape:label="bubble" + id="layer4" + inkscape:groupmode="layer" + transform="translate(-4,2.6816348)"> <path - style="opacity:0.19211821;fill:url(#radialGradient3883);fill-opacity:1;stroke:none" - d="m 442.08605,700.89397 c -129.66422,0 -234.75863,103.19621 -234.75863,230.48113 0,26.84957 4.6841,52.62718 13.28548,76.5811 10.65333,1.4828 21.54531,2.2461 32.60637,2.2461 39.52053,0 69.99101,-8.1231 104.7747,-20.7651 l 110.479,44.5494 a 14.555607,14.562048 0 0 0 19.57853,-17.0097 L 458.13167,895.99293 c 19.23127,-33.77016 25.79804,-72.22452 25.79804,-111.89014 0,-28.84573 -5.53074,-56.41202 -15.60395,-81.77294 -8.61503,-0.94041 -17.37147,-1.43588 -26.23971,-1.43588 z" - id="path3878" + style="display:inline;opacity:1;fill:#00a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + d="M 104.88867,-1.9377566 A 95.889392,95.889392 0 0 0 8.9999996,93.950918 95.889392,95.889392 0 0 0 104.88867,189.84154 95.889392,95.889392 0 0 0 147.59375,179.76341 l 0.12695,0.0137 40.79297,15.83204 c 3.25479,1.26313 7.53628,3.04697 10.45508,0.27343 2.83326,-2.69222 2.16811,-5.42213 1.1875,-9.41211 L 188.80859,140.3025 a 95.889392,95.889392 0 0 1 -0.002,0.002 l 0,-0.008 0.002,0.006 A 95.889392,95.889392 0 0 0 200.7793,93.950948 95.889392,95.889392 0 0 0 104.88867,-1.9377266 Z" + id="circle6661" inkscape:connector-curvature="0" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:125px;line-height:1000%;font-family:Sans;letter-spacing:-10.89000034px;word-spacing:5px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + x="85.862968" + y="-55.271603" + id="text6634" + sodipodi:linespacing="1000%"><tspan + sodipodi:role="line" + id="tspan6636" + x="85.862968" + y="-55.271603" /></text> + </g> + <g + inkscape:groupmode="layer" + id="layer8" + inkscape:label="dotted line" + style="display:inline" + transform="translate(-4,2.6816164)"> + <path + style="opacity:1;fill:#80d080;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath6910)" + d="m 145.16406,11.183594 -5.13232,9.649402 c -0.77924,1.465076 -0.65974,2.41396 0.66876,3.18097 9.66686,5.488467 18.12303,12.874168 24.86104,21.711122 1.05534,1.616079 2.08054,1.713076 3.67763,0.571565 L 178.04883,40 C 169.45271,27.990203 158.19857,18.128379 145.16406,11.183594 Z" + id="path7364" + inkscape:connector-curvature="0" + sodipodi:nodetypes="csccscc" + transform="translate(4.9999996,-1.9374999)" /> + <path + style="opacity:1;fill:#80d080;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath6906)" + d="m 193.80469,75.615234 -9.62713,2.062751 c -2.66266,0.570512 -3.40763,1.172953 -2.90593,3.917433 0.85823,4.714633 1.30424,9.497137 1.33189,14.293254 -0.028,5.578758 -0.62194,11.137108 -1.77093,16.589918 -0.86591,3.23162 0.13682,3.77092 3.16149,4.58138 l 8.98639,2.30136 c 1.98177,-7.66828 3.00584,-15.55255 3.04883,-23.472658 -0.0187,-6.817681 -0.76446,-13.613926 -2.22461,-20.273438 z" + id="path7366" + inkscape:connector-curvature="0" + sodipodi:nodetypes="csccccccc" + transform="translate(4.9999996,-1.9374999)" /> + <path + style="opacity:1;fill:#80d080;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath6902)" + d="m 14.264281,102.76512 -10.2076406,0.87943 c 1.2093798,14.83154 5.8540346,29.17808 13.5664056,41.90429 l 8.544301,-5.23239 c 2.394983,-1.46665 1.895406,-3.37834 0.986202,-5.04513 -5.118253,-9.40257 -8.359018,-19.71635 -9.536202,-30.36553 0,-2.09418 -1.881577,-2.26744 -3.353066,-2.14067 z" + id="path7372" + inkscape:connector-curvature="0" + sodipodi:nodetypes="sccsccs" + transform="translate(4.9999996,-1.9374999)" /> + <path + style="opacity:1;fill:#80d080;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath6898)" + d="m 51.504371,166.60235 -5.82273,8.50898 c 12.710503,8.71282 27.333669,14.23394 42.630859,16.0957 l 1.220329,-9.90843 c 0.355066,-2.88295 -1.085712,-3.52946 -3.332252,-3.90256 -10.402329,-1.73697 -20.373956,-5.45322 -29.373754,-10.94516 -1.647505,-1.06744 -3.639993,-2.30718 -5.322452,0.15147 z" + id="path7370" + inkscape:connector-curvature="0" + sodipodi:nodetypes="sccsccs" + transform="translate(4.9999996,-1.9374999)" /> + <path + style="opacity:1;fill:#80d080;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath6894)" + d="M 32.208984,27.683594 C 21.779177,38.079001 13.883707,50.736882 9.1347656,64.675781 L 19.33617,68.090365 c 1.658147,0.55501 2.832564,-0.120955 3.374272,-1.591979 3.777598,-10.021698 9.470788,-19.210103 16.759132,-27.052307 1.561136,-1.561136 1.567283,-2.960058 0.447507,-4.076606 z" + id="path7374" + inkscape:connector-curvature="0" + sodipodi:nodetypes="ccsccsc" + transform="translate(4.9999996,-1.9374999)" /> + <path + style="opacity:1;fill:#80d080;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath6890)" + d="M 99.888672,-0.25 C 87.701045,-0.2239408 75.630114,2.1252837 64.322266,6.671875 l 3.530435,8.74898 c 1.063314,2.635062 1.616754,3.526314 4.973913,2.352259 8.692057,-3.031338 17.839027,-4.588849 27.062058,-4.599286 5.555828,0 6.486278,0.350026 6.780788,-3.4460223 l 0.74851,-9.64772758 C 104.9135,-0.12857239 102.40179,-0.23868346 99.888672,-0.25 Z" + id="path7376" + inkscape:connector-curvature="0" + sodipodi:nodetypes="ccsccscc" + transform="translate(4.9999996,-1.9374999)" /> <path - sodipodi:nodetypes="ccsssscc" + style="display:inline;fill:#80d080;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath6886)" + d="m 138.72416,168.48439 c -4.17634,2.25458 -8.55959,4.09055 -13.0504,5.63418 -1.00363,0.34498 -1.20742,1.18222 -0.8682,2.27372 l 3.44056,11.0706 c 4.92985,-1.53124 9.72799,-3.45808 14.34766,-5.76172 l 0.12695,0.0137 14.0293,5.44532 4.12174,-10.20577 c 0.7548,-1.86894 -0.0184,-2.7016 -1.59462,-3.31324 l -14.72114,-5.71251 c -1.86679,-0.7244 -3.68834,-0.60144 -5.83185,0.55572 z" + id="path5005" inkscape:connector-curvature="0" - id="path3845" - d="M 478.64112,1025.218 447.36049,898.60749 c 19.89028,-31.99834 26.74288,-69.57172 26.74288,-109.76189 0,-116.81686 -96.79943,-211.48385 -216.18374,-211.48385 -119.38425,0 -216.183656,94.66699 -216.183656,211.48385 0,116.81685 96.799406,211.5536 216.183656,211.5536 39.63617,0 68.58847,-8.14219 105.19417,-21.76075 z" - style="opacity:0;fill:none;stroke:#000000;stroke-width:23.55835724;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:94.23343197, 94.23343197;stroke-dashoffset:0" /> + sodipodi:nodetypes="cssccccsssc" + transform="translate(4.9999996,-1.9374999)" /> <path + style="display:inline;fill:#80d080;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath6882)" + d="m 186.53125,152.80469 -10.6386,2.70888 c -0.78879,0.20085 -1.67397,1.02386 -1.35494,2.33801 l 9.75918,40.15428 c 8.56713,5.97538 15.30408,3.06731 11.01563,-9.47266 z" + id="path5071" inkscape:connector-curvature="0" - id="path3855" - d="m 253.18246,561.05889 c -5.38379,-0.002 -10.7413,0.0871 -12.77023,0.22089 -16.80965,1.10727 -29.68729,3.05317 -44.38296,6.77453 -5.64799,1.43026 -9.96811,2.69833 -15.19914,4.41816 -3.34052,1.09828 -8.41764,2.85364 -8.68521,3.01909 -0.082,0.0507 3.32705,9.32907 7.98597,21.79631 0.0466,0.12496 0.17057,0.13832 0.33123,0.0736 1.11322,-0.44815 6.45699,-2.29745 8.94283,-3.09273 21.39718,-6.84518 43.95735,-10.19531 66.31683,-9.86723 3.14874,0.0461 7.13319,0.15915 8.83245,0.25775 1.69921,0.0987 3.12161,0.15378 3.16493,0.11037 0.0685,-0.0684 1.53237,-23.21444 1.47209,-23.26905 -0.0122,-0.0117 -1.41064,-0.10943 -3.12817,-0.22089 -2.0869,-0.13546 -7.49683,-0.21852 -12.88062,-0.22088 z m 110.81021,28.16581 c -0.10125,0.10911 -11.15095,20.28455 -11.15095,20.36043 0,0.0184 0.50701,0.31641 1.14084,0.66267 8.38104,4.57856 17.56037,10.63803 25.90897,17.04889 3.23527,2.48434 6.34578,5.02146 9.23674,7.54561 4.2123,3.67784 8.41256,7.68117 12.42754,11.81905 6.38417,6.5796 12.29989,13.4994 17.05071,19.99177 0.65274,0.89212 0.79099,1.01157 1.03047,0.84681 1.13402,-0.7802 18.39736,-13.76959 18.40089,-13.84358 0.005,-0.1 -3.33561,-4.52525 -4.74744,-6.29593 -5.64395,-7.07831 -10.59769,-12.59166 -17.26005,-19.25582 -8.26499,-8.26722 -16.14264,-15.121 -25.02569,-21.71453 -2.5667,-1.90515 -5.21733,-3.78858 -7.9855,-5.6781 -6.60132,-4.50598 -18.71149,-11.82683 -19.02653,-11.48727 z m -274.762206,39.94759 -2.428914,2.57732 c -21.579098,22.69359 -38.068397,49.23025 -48.467963,78.05431 -0.50904,1.41091 -0.957247,2.67589 -0.993643,2.83498 -0.04781,0.20904 2.956962,1.31003 10.78292,3.93954 5.956638,2.00143 10.92488,3.63791 11.040538,3.64502 0.115645,0.007 0.916879,-1.94564 1.803285,-4.34458 9.098432,-24.62317 23.187184,-47.25662 41.659643,-66.93523 l 3.05453,-3.27681 -8.206806,-8.24726 z m 388.222146,115.167 -9.89972,1.91451 c -5.44839,1.06994 -10.56998,2.07187 -11.40857,2.20908 -1.04711,0.17137 -1.54564,0.35016 -1.54564,0.55228 0,0.16187 0.23325,1.63204 0.51522,3.2768 2.70275,15.76547 3.28356,34.63258 1.69287,55.26394 -0.7281,9.44363 -2.34823,21.04449 -3.90099,28.01857 -0.23345,1.0486 -0.37949,1.97667 -0.33118,2.02499 0.0483,0.0483 5.12585,1.1561 11.29809,2.46683 6.17232,1.31067 11.30751,2.37915 11.3718,2.39315 0.0641,0.014 0.45734,-1.73307 0.88322,-3.90272 3.35867,-17.11028 4.82653,-33.18977 4.85786,-53.27572 0.0219,-14.08945 -0.79161,-24.35571 -2.87056,-36.96537 z m -427.268845,64.06345 -0.772838,0.11039 c -0.421858,0.0612 -5.59823,0.67716 -11.482161,1.36226 -5.883927,0.68512 -10.759171,1.30169 -10.819725,1.36228 -0.141991,0.142 0.252313,2.91986 1.140854,8.32086 4.869392,29.59836 15.038358,56.25732 31.539139,82.61977 0.450701,0.72005 0.931445,1.27763 1.06725,1.25182 0.361709,-0.0685 19.423106,-12.2036 19.431349,-12.3709 0.0036,-0.0779 -0.796734,-1.40341 -1.766487,-2.94542 -4.266677,-6.78447 -9.935035,-17.45299 -13.064635,-24.5945 -7.52905,-17.18062 -12.488823,-34.71382 -15.051936,-53.27567 z M 462.40066,926.44149 c -0.46898,0.009 -22.08567,5.38002 -22.2283,5.52269 -0.098,0.0981 22.04129,90.06142 22.37549,91.01382 0.40286,1.1482 3.73284,10.5298 13.56323,8.9156 10.95786,-2.3434 9.8458,-14.6677 8.99628,-14.4751 -0.11284,0.025 -5.02627,-20.61508 -11.18774,-45.58033 -8.79763,-35.64656 -11.26829,-45.40142 -11.51896,-45.39668 z M 143.91794,953.1714 c -0.40943,0.0131 -1.21588,1.3276 -6.29312,9.64634 -3.31435,5.43031 -6.03549,9.92123 -6.03549,9.9777 0,0.13674 3.42858,2.19027 7.06593,4.27089 22.35182,12.78549 47.08561,21.82095 72.42596,26.43487 3.59043,0.654 5.67261,1.0064 11.04051,1.804 0.69401,0.1031 1.36954,0.2073 1.50889,0.2212 0.31484,0.031 0.24386,0.6279 1.87691,-11.45018 0.75094,-5.5542 1.40492,-10.43428 1.47207,-10.86126 0.11781,-0.74877 0.0863,-0.77677 -0.58887,-0.88362 -0.38487,-0.0607 -2.68651,-0.4127 -5.11543,-0.77322 -22.30454,-3.3101 -45.25895,-10.90321 -65.32317,-21.57538 -3.55401,-1.89038 -10.16752,-5.64292 -11.85018,-6.73769 -0.0531,-0.0345 -0.12551,-0.0755 -0.18401,-0.0737 z m 214.22322,9.09404 c -1.98095,0.13013 -4.60205,1.01767 -10.4517,3.12954 -11.29964,4.0795 -24.13159,8.26507 -29.91986,9.75681 -0.83741,0.21582 -1.5445,0.50692 -1.54566,0.62592 -0.002,0.21503 5.72469,22.22722 5.81468,22.34847 0.0552,0.075 6.34708,-1.70267 10.2677,-2.90858 5.09669,-1.5677 13.67295,-4.44246 19.79936,-6.62722 l 6.07232,-2.17225 22.30187,8.98356 c 18.14341,7.31671 22.30098,8.95081 22.41231,8.68881 0.9061,-2.1322 8.61707,-21.32757 8.57483,-21.35419 -0.38803,-0.24492 -49.13929,-19.77601 -49.90327,-19.99221 -1.25781,-0.35596 -2.23399,-0.55669 -3.42258,-0.47866 z" - style="opacity:0.5;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:5.88958931;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" - sodipodi:nodetypes="ccssccssscccccccssssscccsssscccscsssccccccssssssssssccccscssccsscccsscsscsssssccsccsscsscsccscccccssc" /> + sodipodi:nodetypes="cssccc" + transform="translate(4.9999996,-1.9374999)" /> </g> <g + style="display:inline" + inkscape:label="dots" + id="layer2" inkscape:groupmode="layer" - id="layer4" - inkscape:label="Dots"> - <path - sodipodi:type="arc" - style="opacity:0.928;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none" - id="path3047" - sodipodi:cx="173.57143" - sodipodi:cy="241.28571" - sodipodi:rx="26.428572" - sodipodi:ry="20" - d="m 200,241.28571 a 26.428572,20 0 1 1 -52.85715,0 26.428572,20 0 1 1 52.85715,0 z" - transform="matrix(0.94594594,0,0,1.25,-18.332045,-54.607132)" /> - <path - sodipodi:type="arc" - style="opacity:0.928;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none" - id="path3047-1" - sodipodi:cx="173.57143" - sodipodi:cy="241.28571" - sodipodi:rx="26.428572" - sodipodi:ry="20" - d="m 200,241.28571 a 26.428572,20 0 1 1 -52.85715,0 26.428572,20 0 1 1 52.85715,0 z" - transform="matrix(0.94594594,0,0,1.25,91.38502,-54.607132)" /> + transform="translate(-4,2.6816348)"> + <g + inkscape:export-ydpi="100" + inkscape:export-xdpi="100" + style="fill:#f5f5f5;fill-opacity:1" + transform="matrix(0.3835576,0,0,0.3835576,-248.17635,-138.86977)" + id="g5126"> + <circle + r="27.299093" + style="opacity:1;fill:#f5f5f5;fill-opacity:1;fill-rule:evenodd;stroke:none" + id="path3047-4" + cx="799.11273" + cy="609.86285" /> + <circle + r="27.299093" + style="opacity:1;fill:#f5f5f5;fill-opacity:1;fill-rule:evenodd;stroke:none" + id="path3047-1-2" + cx="918.91962" + cy="609.86285" /> + <circle + r="27.299093" + style="opacity:1;fill:#f5f5f5;fill-opacity:1;fill-rule:evenodd;stroke:none" + id="path3047-1-8-6" + cx="1039.0352" + cy="609.86285" /> + </g> + </g> + <g + inkscape:groupmode="layer" + id="layer1" + inkscape:label="light" + style="display:inline" + transform="translate(-4,2.6816164)"> <path - sodipodi:type="arc" - style="opacity:0.928;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none" - id="path3047-1-8" - sodipodi:cx="173.57143" - sodipodi:cy="241.28571" - sodipodi:rx="26.428572" - sodipodi:ry="20" - d="m 200,241.28571 a 26.428572,20 0 1 1 -52.85715,0 26.428572,20 0 1 1 52.85715,0 z" - transform="matrix(0.94594594,0,0,1.25,201.38502,-54.607132)" /> + style="display:inline;opacity:0.19211821;fill:url(#radialGradient3883);fill-opacity:1;stroke:none" + d="m 192.44891,47.715674 c -61.69765,0 -111.704333,49.103472 -111.704333,109.668976 0,12.77573 2.228815,25.0414 6.321575,36.4393 5.069139,0.70557 10.251828,1.06876 15.514978,1.06876 18.80489,0 30.91434,7.28449 47.46533,1.26909 l 54.00234,6.06606 c 5.24363,2.11897 11.63381,1.37954 10.27166,-4.11162 l -14.23663,-57.56735 c 9.15073,-16.06873 12.27539,-34.36633 12.27539,-53.240271 0,-13.72556 -2.63167,-26.842322 -7.42478,-38.909717 -4.09925,-0.447474 -8.2658,-0.683228 -12.48553,-0.683228 z" + id="path3878" + inkscape:connector-curvature="0" + clip-path="url(#clipPath5745)" + transform="translate(4.9800894,-1.9374999)" + sodipodi:nodetypes="sscsccccscs" /> </g> </svg> diff --git a/art/md_switch_thumb_disable.svg b/art/md_switch_thumb_disable.svg new file mode 100644 index 00000000..efd83c2d --- /dev/null +++ b/art/md_switch_thumb_disable.svg @@ -0,0 +1,156 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:osb="http://www.openswatchbook.org/uri/2009/osb" + 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:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + sodipodi:docname="md_switch_thumb_disable_centered_square.svg" + viewBox="0 0 120 120" + height="120" + width="120" + inkscape:version="0.91 r13725" + version="1.1" + id="svg2"> + <metadata + id="metadata8"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs6"> + <linearGradient + inkscape:collect="always" + id="linearGradient4222"> + <stop + style="stop-color:#000000;stop-opacity:1" + offset="0" + id="stop4224" /> + <stop + style="stop-color:#ffffff;stop-opacity:1" + offset="1" + id="stop4226" /> + </linearGradient> + <linearGradient + id="linearGradient4179" + osb:paint="gradient"> + <stop + style="stop-color:#ffffff;stop-opacity:1;" + offset="0" + id="stop4181" /> + <stop + style="stop-color:#ffffff;stop-opacity:0.25454545" + offset="1" + id="stop4183" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4222" + id="linearGradient4228" + x1="159.38722" + y1="19.802504" + x2="212.27522" + y2="19.802504" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(-260.32215,163.27594)" /> + <filter + inkscape:collect="always" + style="color-interpolation-filters:sRGB" + id="filter4230" + x="-0.012" + width="1.024" + y="-0.012" + height="1.024"> + <feGaussianBlur + inkscape:collect="always" + stdDeviation="0.25916904" + id="feGaussianBlur4232" /> + </filter> + <filter + inkscape:collect="always" + style="color-interpolation-filters:sRGB" + id="filter4371" + x="-0.23999999" + width="1.48" + y="-0.23999999" + height="1.48"> + <feGaussianBlur + inkscape:collect="always" + stdDeviation="5.2888" + id="feGaussianBlur4373" /> + </filter> + </defs> + <sodipodi:namedview + inkscape:current-layer="layer2" + inkscape:window-maximized="1" + inkscape:window-y="0" + inkscape:window-x="1400" + inkscape:cy="61.379767" + inkscape:cx="10.572032" + inkscape:zoom="3.8530612" + showgrid="false" + id="namedview4" + inkscape:window-height="1024" + inkscape:window-width="1680" + inkscape:pageshadow="2" + inkscape:pageopacity="0" + guidetolerance="10" + gridtolerance="10" + objecttolerance="10" + borderopacity="1" + bordercolor="#666666" + pagecolor="#ffffff" /> + <g + inkscape:groupmode="layer" + id="layer1" + inkscape:label="PNG" + style="display:none" + sodipodi:insensitive="true" + transform="translate(0,-2.5)" /> + <g + inkscape:groupmode="layer" + id="layer2" + inkscape:label="SVG" + style="display:inline" + transform="translate(0,-2.5)"> + <g + id="g6404"> + <circle + style="opacity:1;fill:#000404;fill-opacity:0.45531915;stroke:none;stroke-width:1.05419147;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter4371)" + id="circle4234" + cx="59.999996" + cy="66.499878" + r="26.444" /> + <g + transform="translate(3.3103058e-6,0.33229253)" + id="g4148"> + <circle + style="opacity:1;fill:#bdbdbd;fill-opacity:1;stroke:#bdbdbd;stroke-width:1.05419147;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path4218" + cx="59.999996" + cy="62.167587" + r="25.916904" /> + <circle + r="25.916904" + cy="183.07845" + cx="-74.490921" + id="circle4220" + style="opacity:0.3;fill:none;fill-opacity:1;stroke:url(#linearGradient4228);stroke-width:1.05419147;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter4230)" + transform="matrix(0,-1,1,0,-123.07845,-12.323334)" /> + </g> + </g> + </g> +</svg> diff --git a/art/md_switch_thumb_off_normal.svg b/art/md_switch_thumb_off_normal.svg new file mode 100644 index 00000000..25d1761d --- /dev/null +++ b/art/md_switch_thumb_off_normal.svg @@ -0,0 +1,153 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:osb="http://www.openswatchbook.org/uri/2009/osb" + 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:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + sodipodi:docname="md_switch_thumb_off_normal_centered.svg" + viewBox="0 0 120 120" + height="120" + width="120" + inkscape:version="0.91 r13725" + version="1.1" + id="svg2"> + <metadata + id="metadata8"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs6"> + <linearGradient + inkscape:collect="always" + id="linearGradient4222"> + <stop + style="stop-color:#000000;stop-opacity:1" + offset="0" + id="stop4224" /> + <stop + style="stop-color:#ffffff;stop-opacity:1" + offset="1" + id="stop4226" /> + </linearGradient> + <linearGradient + id="linearGradient4179" + osb:paint="gradient"> + <stop + style="stop-color:#ffffff;stop-opacity:1;" + offset="0" + id="stop4181" /> + <stop + style="stop-color:#ffffff;stop-opacity:0.25454545" + offset="1" + id="stop4183" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4222" + id="linearGradient4228" + x1="159.38722" + y1="19.802504" + x2="212.27522" + y2="19.802504" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(-260.32215,163.27594)" /> + <filter + inkscape:collect="always" + style="color-interpolation-filters:sRGB" + id="filter4230" + x="-0.012" + width="1.024" + y="-0.012" + height="1.024"> + <feGaussianBlur + inkscape:collect="always" + stdDeviation="0.25916904" + id="feGaussianBlur4232" /> + </filter> + <filter + inkscape:collect="always" + style="color-interpolation-filters:sRGB" + id="filter4371" + x="-0.23999999" + width="1.48" + y="-0.23999999" + height="1.48"> + <feGaussianBlur + inkscape:collect="always" + stdDeviation="5.2888" + id="feGaussianBlur4373" /> + </filter> + </defs> + <sodipodi:namedview + inkscape:current-layer="layer2" + inkscape:window-maximized="1" + inkscape:window-y="0" + inkscape:window-x="1400" + inkscape:cy="61.379767" + inkscape:cx="10.052965" + inkscape:zoom="3.8530612" + showgrid="false" + id="namedview4" + inkscape:window-height="1024" + inkscape:window-width="1680" + inkscape:pageshadow="2" + inkscape:pageopacity="0" + guidetolerance="10" + gridtolerance="10" + objecttolerance="10" + borderopacity="1" + bordercolor="#666666" + pagecolor="#ffffff" /> + <g + inkscape:groupmode="layer" + id="layer1" + inkscape:label="PNG" + style="display:none" + sodipodi:insensitive="true" + transform="translate(0,-2.5)" /> + <g + inkscape:groupmode="layer" + id="layer2" + inkscape:label="SVG" + style="display:inline" + transform="translate(0,-2.5)"> + <circle + r="26.444" + cy="66.5" + cx="59.999996" + id="circle4234" + style="opacity:1;fill:#000404;fill-opacity:0.45531915;stroke:none;stroke-width:1.05419147;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter4371)" /> + <g + id="g6390" + transform="translate(3.3103058e-6,-0.91758577)"> + <circle + r="25.916904" + cy="63.417587" + cx="59.999996" + id="path4218" + style="opacity:1;fill:#fafafa;fill-opacity:1;stroke:#fafafa;stroke-width:1.05419147;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <circle + transform="matrix(0,-1,1,0,-123.07845,-11.073334)" + style="opacity:0.3;fill:none;fill-opacity:1;stroke:url(#linearGradient4228);stroke-width:1.05419147;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter4230)" + id="circle4220" + cx="-74.490921" + cy="183.07845" + r="25.916904" /> + </g> + </g> +</svg> diff --git a/art/md_switch_thumb_off_pressed.svg b/art/md_switch_thumb_off_pressed.svg new file mode 100644 index 00000000..002b4781 --- /dev/null +++ b/art/md_switch_thumb_off_pressed.svg @@ -0,0 +1,159 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:osb="http://www.openswatchbook.org/uri/2009/osb" + 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:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + sodipodi:docname="md_switch_thumb_off_pressed_centered.svg" + viewBox="0 0 120 120" + height="120" + width="120" + inkscape:version="0.91 r13725" + version="1.1" + id="svg2"> + <metadata + id="metadata8"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs6"> + <linearGradient + inkscape:collect="always" + id="linearGradient4222"> + <stop + style="stop-color:#000000;stop-opacity:1" + offset="0" + id="stop4224" /> + <stop + style="stop-color:#ffffff;stop-opacity:1" + offset="1" + id="stop4226" /> + </linearGradient> + <linearGradient + id="linearGradient4179" + osb:paint="gradient"> + <stop + style="stop-color:#ffffff;stop-opacity:1;" + offset="0" + id="stop4181" /> + <stop + style="stop-color:#ffffff;stop-opacity:0.25454545" + offset="1" + id="stop4183" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4222" + id="linearGradient4228" + x1="159.38722" + y1="19.802504" + x2="212.27522" + y2="19.802504" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(-260.32215,163.27594)" /> + <filter + inkscape:collect="always" + style="color-interpolation-filters:sRGB" + id="filter4230" + x="-0.012" + width="1.024" + y="-0.012" + height="1.024"> + <feGaussianBlur + inkscape:collect="always" + stdDeviation="0.25916904" + id="feGaussianBlur4232" /> + </filter> + <filter + inkscape:collect="always" + style="color-interpolation-filters:sRGB" + id="filter4371" + x="-0.23999999" + width="1.48" + y="-0.23999999" + height="1.48"> + <feGaussianBlur + inkscape:collect="always" + stdDeviation="5.2888" + id="feGaussianBlur4373" /> + </filter> + </defs> + <sodipodi:namedview + inkscape:current-layer="layer2" + inkscape:window-maximized="1" + inkscape:window-y="0" + inkscape:window-x="1400" + inkscape:cy="61.379767" + inkscape:cx="10.572032" + inkscape:zoom="3.8530612" + showgrid="false" + id="namedview4" + inkscape:window-height="1024" + inkscape:window-width="1680" + inkscape:pageshadow="2" + inkscape:pageopacity="0" + guidetolerance="10" + gridtolerance="10" + objecttolerance="10" + borderopacity="1" + bordercolor="#666666" + pagecolor="#ffffff" /> + <g + inkscape:groupmode="layer" + id="layer1" + inkscape:label="PNG" + style="display:none" + sodipodi:insensitive="true" + transform="translate(0,-2.5)" /> + <g + inkscape:groupmode="layer" + id="layer2" + inkscape:label="SVG" + style="display:inline" + transform="translate(0,-2.5)"> + <circle + style="opacity:1;fill:#313131;fill-opacity:0.10196078;fill-rule:nonzero;stroke:none;stroke-width:1.00100005;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.10196078" + id="path4819" + cx="60" + cy="62.5" + r="60" /> + <circle + r="26.444" + cy="66.5" + cx="59.999996" + id="circle4234" + style="opacity:1;fill:#000404;fill-opacity:0.45531915;stroke:none;stroke-width:1.05419147;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter4371)" /> + <g + id="g6417" + transform="translate(3.3103058e-6,-0.91758577)"> + <circle + r="25.916904" + cy="63.417587" + cx="59.999996" + id="path4218" + style="opacity:1;fill:#fafafa;fill-opacity:1;stroke:#fafafa;stroke-width:1.05419147;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <circle + transform="matrix(0,-1,1,0,-123.07845,-11.073334)" + style="opacity:0.3;fill:none;fill-opacity:1;stroke:url(#linearGradient4228);stroke-width:1.05419147;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter4230)" + id="circle4220" + cx="-74.490921" + cy="183.07845" + r="25.916904" /> + </g> + </g> +</svg> diff --git a/art/md_switch_thumb_on_normal.svg b/art/md_switch_thumb_on_normal.svg new file mode 100644 index 00000000..5e8f90f3 --- /dev/null +++ b/art/md_switch_thumb_on_normal.svg @@ -0,0 +1,146 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:osb="http://www.openswatchbook.org/uri/2009/osb" + 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:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + sodipodi:docname="md_switch_thumb_on_normal_centered_square.svg" + viewBox="0 0 120 120" + height="120" + width="120" + inkscape:version="0.91 r13725" + version="1.1" + id="svg2"> + <metadata + id="metadata8"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs6"> + <linearGradient + inkscape:collect="always" + id="linearGradient4222"> + <stop + style="stop-color:#000000;stop-opacity:1" + offset="0" + id="stop4224" /> + <stop + style="stop-color:#ffffff;stop-opacity:1" + offset="1" + id="stop4226" /> + </linearGradient> + <linearGradient + id="linearGradient4179" + osb:paint="gradient"> + <stop + style="stop-color:#ffffff;stop-opacity:1;" + offset="0" + id="stop4181" /> + <stop + style="stop-color:#ffffff;stop-opacity:0.25454545" + offset="1" + id="stop4183" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4222" + id="linearGradient4228" + x1="159.38722" + y1="19.802504" + x2="212.27522" + y2="19.802504" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(-260.32215,163.27594)" /> + <filter + inkscape:collect="always" + style="color-interpolation-filters:sRGB" + id="filter4230" + x="-0.012" + width="1.024" + y="-0.012" + height="1.024"> + <feGaussianBlur + inkscape:collect="always" + stdDeviation="0.25916904" + id="feGaussianBlur4232" /> + </filter> + <filter + inkscape:collect="always" + style="color-interpolation-filters:sRGB" + id="filter4371" + x="-0.23999999" + width="1.48" + y="-0.23999999" + height="1.48"> + <feGaussianBlur + inkscape:collect="always" + stdDeviation="5.2888" + id="feGaussianBlur4373" /> + </filter> + </defs> + <sodipodi:namedview + inkscape:current-layer="layer2" + inkscape:window-maximized="1" + inkscape:window-y="0" + inkscape:window-x="1400" + inkscape:cy="61.379767" + inkscape:cx="-14.397519" + inkscape:zoom="3.8530612" + showgrid="false" + id="namedview4" + inkscape:window-height="1024" + inkscape:window-width="1680" + inkscape:pageshadow="2" + inkscape:pageopacity="0" + guidetolerance="10" + gridtolerance="10" + objecttolerance="10" + borderopacity="1" + bordercolor="#666666" + pagecolor="#ffffff" /> + <g + inkscape:groupmode="layer" + id="layer2" + inkscape:label="SVG" + style="display:inline" + transform="translate(0,-2.5)"> + <circle + r="26.444" + cy="66.499878" + cx="59.999996" + id="circle4234" + style="opacity:1;fill:#000404;fill-opacity:0.45531915;stroke:none;stroke-width:1.05419147;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter4371)" /> + <g + id="g6440" + transform="translate(3.3103058e-6,0.33241423)"> + <circle + r="25.916904" + cy="62.167587" + cx="59.999996" + id="path4218" + style="opacity:1;fill:#0091ea;fill-opacity:1;stroke:#0091ea;stroke-width:1.05419147;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <circle + transform="matrix(0,-1,1,0,-123.07845,-12.323334)" + style="opacity:0.3;fill:none;fill-opacity:1;stroke:url(#linearGradient4228);stroke-width:1.05419147;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter4230)" + id="circle4220" + cx="-74.490921" + cy="183.07845" + r="25.916904" /> + </g> + </g> +</svg> diff --git a/art/md_switch_thumb_on_pressed.svg b/art/md_switch_thumb_on_pressed.svg new file mode 100644 index 00000000..e0331e7b --- /dev/null +++ b/art/md_switch_thumb_on_pressed.svg @@ -0,0 +1,162 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:osb="http://www.openswatchbook.org/uri/2009/osb" + 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:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + sodipodi:docname="md_switch_thumb_on_pressed_centered_square.svg" + viewBox="0 0 120 120" + height="120" + width="120" + inkscape:version="0.91 r13725" + version="1.1" + id="svg2"> + <metadata + id="metadata8"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title /> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs6"> + <linearGradient + inkscape:collect="always" + id="linearGradient4222"> + <stop + style="stop-color:#000000;stop-opacity:1" + offset="0" + id="stop4224" /> + <stop + style="stop-color:#ffffff;stop-opacity:1" + offset="1" + id="stop4226" /> + </linearGradient> + <linearGradient + id="linearGradient4179" + osb:paint="gradient"> + <stop + style="stop-color:#ffffff;stop-opacity:1;" + offset="0" + id="stop4181" /> + <stop + style="stop-color:#ffffff;stop-opacity:0.25454545" + offset="1" + id="stop4183" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4222" + id="linearGradient4228" + x1="159.38722" + y1="19.802504" + x2="212.27522" + y2="19.802504" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(-260.32215,163.27594)" /> + <filter + inkscape:collect="always" + style="color-interpolation-filters:sRGB" + id="filter4230" + x="-0.012" + width="1.024" + y="-0.012" + height="1.024"> + <feGaussianBlur + inkscape:collect="always" + stdDeviation="0.25916904" + id="feGaussianBlur4232" /> + </filter> + <filter + inkscape:collect="always" + style="color-interpolation-filters:sRGB" + id="filter4371" + x="-0.23999999" + width="1.48" + y="-0.23999999" + height="1.48"> + <feGaussianBlur + inkscape:collect="always" + stdDeviation="5.2888" + id="feGaussianBlur4373" /> + </filter> + </defs> + <sodipodi:namedview + inkscape:current-layer="layer2" + inkscape:window-maximized="1" + inkscape:window-y="0" + inkscape:window-x="1400" + inkscape:cy="61.379767" + inkscape:cx="-46.31369" + inkscape:zoom="3.8530612" + showgrid="false" + id="namedview4" + inkscape:window-height="1024" + inkscape:window-width="1680" + inkscape:pageshadow="2" + inkscape:pageopacity="0" + guidetolerance="10" + gridtolerance="10" + objecttolerance="10" + borderopacity="1" + bordercolor="#666666" + pagecolor="#ffffff" /> + <g + inkscape:groupmode="layer" + id="layer1" + inkscape:label="PNG" + style="display:none" + sodipodi:insensitive="true" + transform="translate(0,-2.5)" /> + <g + inkscape:groupmode="layer" + id="layer2" + inkscape:label="SVG" + style="display:inline" + transform="translate(0,-2.5)"> + <circle + style="opacity:1;fill:#0093e8;fill-opacity:0.10196078;fill-rule:nonzero;stroke:none;stroke-width:1.00100005;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.10196078" + id="path4819" + cx="60" + cy="62.5" + r="60" /> + <g + id="g4156"> + <circle + style="opacity:1;fill:#000404;fill-opacity:0.45531915;stroke:none;stroke-width:1.05419147;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter4371)" + id="circle4234" + cx="59.999996" + cy="66.5" + r="26.444" /> + <g + transform="translate(3.3103058e-6,0.33241423)" + id="g4149"> + <circle + style="opacity:1;fill:#0091ea;fill-opacity:1;stroke:#0091ea;stroke-width:1.05419147;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path4218" + cx="59.999996" + cy="62.167587" + r="25.916904" /> + <circle + r="25.916904" + cy="183.07845" + cx="-74.490921" + id="circle4220" + style="opacity:0.3;fill:none;fill-opacity:1;stroke:url(#linearGradient4228);stroke-width:1.05419147;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter4230)" + transform="matrix(0,-1,1,0,-123.07845,-12.323334)" /> + </g> + </g> + </g> +</svg> diff --git a/art/message_bubble_received.svg b/art/message_bubble_received.svg new file mode 100644 index 00000000..815892ed --- /dev/null +++ b/art/message_bubble_received.svg @@ -0,0 +1,165 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<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" + width="36" + height="26" + id="svg2" + version="1.1" + inkscape:version="0.48.5 r10040" + sodipodi:docname="message_bubble_received.svg"> + <defs + id="defs4"> + <filter + x="-0.25" + y="-0.25" + width="1.5" + height="1.5" + inkscape:label="Drop Shadow" + id="filter3811" + color-interpolation-filters="sRGB"> + <feFlood + flood-opacity="0.25" + flood-color="rgb(0,0,0)" + result="flood" + id="feFlood3813" /> + <feComposite + in="flood" + in2="SourceGraphic" + operator="in" + result="composite1" + id="feComposite3815" /> + <feGaussianBlur + stdDeviation="0.5" + result="blur" + id="feGaussianBlur3817" /> + <feOffset + dx="0" + dy="1" + result="offset" + id="feOffset3819" /> + <feComposite + in="SourceGraphic" + in2="offset" + operator="over" + result="composite2" + id="feComposite3821" /> + </filter> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="16" + inkscape:cx="25.745257" + inkscape:cy="9.618802" + inkscape:document-units="px" + inkscape:current-layer="layer1" + showgrid="true" + inkscape:window-width="989" + inkscape:window-height="755" + inkscape:window-x="22" + inkscape:window-y="16" + inkscape:window-maximized="0" + showguides="true" + inkscape:guide-bbox="true" + guidecolor="#000000" + guideopacity="0.49803922"> + <inkscape:grid + type="xygrid" + id="grid2985" + empspacing="4" + visible="true" + enabled="true" + snapvisiblegridlinesonly="true" + spacingx="1px" + spacingy="1px" + originx="0px" + originy="0px" + color="#0000ff" + opacity="0.03137255" /> + <sodipodi:guide + orientation="1,0" + position="20,26" + id="guide3060" /> + <sodipodi:guide + orientation="1,0" + position="24,26" + id="guide3062" /> + <sodipodi:guide + orientation="0,1" + position="36,22" + id="guide3064" /> + <sodipodi:guide + orientation="0,1" + position="36,6" + id="guide3066" /> + <sodipodi:guide + orientation="1,0" + position="26,0" + id="guide3068" /> + <sodipodi:guide + orientation="1,0" + position="18,0" + id="guide3070" /> + <sodipodi:guide + orientation="0,1" + position="0,10" + id="guide3074" /> + <sodipodi:guide + orientation="0,1" + position="0,8" + id="guide3076" /> + </sodipodi:namedview> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer" + inkscape:groupmode="layer" + id="layer" + transform="translate(0,-2)"> + <g + id="g3759" + style="fill:#4b9b4a;fill-opacity:1;stroke:none;fill-rule:nonzero;filter:url(#filter3811)"> + <path + style="display:none" + d="m 8,6 c 2,2 4,6 4,10 L 16,6 z" + id="path3805" + inkscape:connector-curvature="0" + transform="translate(0,2)" + sodipodi:nodetypes="cccc" /> + <path + inkscape:connector-curvature="0" + id="path2989" + d="M 4,4 16,16 16,4 z" + sodipodi:nodetypes="cccc" /> + <rect + ry="2" + y="4" + x="12" + height="20" + width="20" + id="rect2987" /> + </g> + </g> +</svg> diff --git a/art/message_bubble_received_warning.svg b/art/message_bubble_received_warning.svg new file mode 100644 index 00000000..9353492b --- /dev/null +++ b/art/message_bubble_received_warning.svg @@ -0,0 +1,165 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<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" + width="36" + height="26" + id="svg2" + version="1.1" + inkscape:version="0.48.5 r10040" + sodipodi:docname="message_bubble_received.svg"> + <defs + id="defs4"> + <filter + x="-0.25" + y="-0.25" + width="1.5" + height="1.5" + inkscape:label="Drop Shadow" + id="filter3811" + color-interpolation-filters="sRGB"> + <feFlood + flood-opacity="0.25" + flood-color="rgb(0,0,0)" + result="flood" + id="feFlood3813" /> + <feComposite + in="flood" + in2="SourceGraphic" + operator="in" + result="composite1" + id="feComposite3815" /> + <feGaussianBlur + stdDeviation="0.5" + result="blur" + id="feGaussianBlur3817" /> + <feOffset + dx="0" + dy="1" + result="offset" + id="feOffset3819" /> + <feComposite + in="SourceGraphic" + in2="offset" + operator="over" + result="composite2" + id="feComposite3821" /> + </filter> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="16" + inkscape:cx="25.745257" + inkscape:cy="9.618802" + inkscape:document-units="px" + inkscape:current-layer="layer1" + showgrid="true" + inkscape:window-width="989" + inkscape:window-height="755" + inkscape:window-x="22" + inkscape:window-y="16" + inkscape:window-maximized="0" + showguides="true" + inkscape:guide-bbox="true" + guidecolor="#000000" + guideopacity="0.49803922"> + <inkscape:grid + type="xygrid" + id="grid2985" + empspacing="4" + visible="true" + enabled="true" + snapvisiblegridlinesonly="true" + spacingx="1px" + spacingy="1px" + originx="0px" + originy="0px" + color="#0000ff" + opacity="0.03137255" /> + <sodipodi:guide + orientation="1,0" + position="20,26" + id="guide3060" /> + <sodipodi:guide + orientation="1,0" + position="24,26" + id="guide3062" /> + <sodipodi:guide + orientation="0,1" + position="36,22" + id="guide3064" /> + <sodipodi:guide + orientation="0,1" + position="36,6" + id="guide3066" /> + <sodipodi:guide + orientation="1,0" + position="26,0" + id="guide3068" /> + <sodipodi:guide + orientation="1,0" + position="18,0" + id="guide3070" /> + <sodipodi:guide + orientation="0,1" + position="0,10" + id="guide3074" /> + <sodipodi:guide + orientation="0,1" + position="0,8" + id="guide3076" /> + </sodipodi:namedview> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer" + inkscape:groupmode="layer" + id="layer" + transform="translate(0,-2)"> + <g + id="g3759" + style="fill:#c64545;fill-opacity:1;stroke:none;fill-rule:nonzero;filter:url(#filter3811)"> + <path + style="display:none" + d="m 8,6 c 2,2 4,6 4,10 L 16,6 z" + id="path3805" + inkscape:connector-curvature="0" + transform="translate(0,2)" + sodipodi:nodetypes="cccc" /> + <path + inkscape:connector-curvature="0" + id="path2989" + d="M 4,4 16,16 16,4 z" + sodipodi:nodetypes="cccc" /> + <rect + ry="2" + y="4" + x="12" + height="20" + width="20" + id="rect2987" /> + </g> + </g> +</svg> diff --git a/art/message_bubble_received_white.svg b/art/message_bubble_received_white.svg new file mode 100644 index 00000000..52e599f0 --- /dev/null +++ b/art/message_bubble_received_white.svg @@ -0,0 +1,165 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<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" + width="36" + height="26" + id="svg2" + version="1.1" + inkscape:version="0.48.5 r10040" + sodipodi:docname="message_bubble_received.svg"> + <defs + id="defs4"> + <filter + x="-0.25" + y="-0.25" + width="1.5" + height="1.5" + inkscape:label="Drop Shadow" + id="filter3811" + color-interpolation-filters="sRGB"> + <feFlood + flood-opacity="0.25" + flood-color="rgb(0,0,0)" + result="flood" + id="feFlood3813" /> + <feComposite + in="flood" + in2="SourceGraphic" + operator="in" + result="composite1" + id="feComposite3815" /> + <feGaussianBlur + stdDeviation="0.5" + result="blur" + id="feGaussianBlur3817" /> + <feOffset + dx="0" + dy="1" + result="offset" + id="feOffset3819" /> + <feComposite + in="SourceGraphic" + in2="offset" + operator="over" + result="composite2" + id="feComposite3821" /> + </filter> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="16" + inkscape:cx="25.745257" + inkscape:cy="9.618802" + inkscape:document-units="px" + inkscape:current-layer="layer1" + showgrid="true" + inkscape:window-width="989" + inkscape:window-height="755" + inkscape:window-x="22" + inkscape:window-y="16" + inkscape:window-maximized="0" + showguides="true" + inkscape:guide-bbox="true" + guidecolor="#000000" + guideopacity="0.49803922"> + <inkscape:grid + type="xygrid" + id="grid2985" + empspacing="4" + visible="true" + enabled="true" + snapvisiblegridlinesonly="true" + spacingx="1px" + spacingy="1px" + originx="0px" + originy="0px" + color="#0000ff" + opacity="0.03137255" /> + <sodipodi:guide + orientation="1,0" + position="20,26" + id="guide3060" /> + <sodipodi:guide + orientation="1,0" + position="24,26" + id="guide3062" /> + <sodipodi:guide + orientation="0,1" + position="36,22" + id="guide3064" /> + <sodipodi:guide + orientation="0,1" + position="36,6" + id="guide3066" /> + <sodipodi:guide + orientation="1,0" + position="26,0" + id="guide3068" /> + <sodipodi:guide + orientation="1,0" + position="18,0" + id="guide3070" /> + <sodipodi:guide + orientation="0,1" + position="0,10" + id="guide3074" /> + <sodipodi:guide + orientation="0,1" + position="0,8" + id="guide3076" /> + </sodipodi:namedview> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer" + inkscape:groupmode="layer" + id="layer" + transform="translate(0,-2)"> + <g + id="g3759" + style="fill:#fafafa;fill-opacity:1;stroke:none;fill-rule:nonzero;filter:url(#filter3811)"> + <path + style="display:none" + d="m 8,6 c 2,2 4,6 4,10 L 16,6 z" + id="path3805" + inkscape:connector-curvature="0" + transform="translate(0,2)" + sodipodi:nodetypes="cccc" /> + <path + inkscape:connector-curvature="0" + id="path2989" + d="M 4,4 16,16 16,4 z" + sodipodi:nodetypes="cccc" /> + <rect + ry="2" + y="4" + x="12" + height="20" + width="20" + id="rect2987" /> + </g> + </g> +</svg> diff --git a/art/message_bubble_sent.svg b/art/message_bubble_sent.svg new file mode 100644 index 00000000..90ad5091 --- /dev/null +++ b/art/message_bubble_sent.svg @@ -0,0 +1,165 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<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" + width="36" + height="26" + id="svg2" + version="1.1" + inkscape:version="0.48.5 r10040" + sodipodi:docname="message_bubble_sent.svg"> + <defs + id="defs4"> + <filter + x="-0.25" + y="-0.25" + width="1.5" + height="1.5" + inkscape:label="Drop Shadow" + id="filter3811" + color-interpolation-filters="sRGB"> + <feFlood + flood-opacity="0.25" + flood-color="rgb(0,0,0)" + result="flood" + id="feFlood3813" /> + <feComposite + in="flood" + in2="SourceGraphic" + operator="in" + result="composite1" + id="feComposite3815" /> + <feGaussianBlur + stdDeviation="0.5" + result="blur" + id="feGaussianBlur3817" /> + <feOffset + dx="0" + dy="1" + result="offset" + id="feOffset3819" /> + <feComposite + in="SourceGraphic" + in2="offset" + operator="over" + result="composite2" + id="feComposite3821" /> + </filter> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="16" + inkscape:cx="14.269338" + inkscape:cy="16.118802" + inkscape:document-units="px" + inkscape:current-layer="layer1" + showgrid="true" + inkscape:window-width="989" + inkscape:window-height="755" + inkscape:window-x="434" + inkscape:window-y="16" + inkscape:window-maximized="0" + showguides="true" + inkscape:guide-bbox="true" + guidecolor="#404040" + guideopacity="0.49803922"> + <inkscape:grid + type="xygrid" + id="grid2985" + empspacing="4" + visible="true" + enabled="true" + snapvisiblegridlinesonly="true" + spacingx="1px" + spacingy="1px" + originx="0px" + originy="0px" + color="#0000ff" + opacity="0.03137255" /> + <sodipodi:guide + orientation="1,0" + position="12,26" + id="guide3146" /> + <sodipodi:guide + orientation="1,0" + position="16,26" + id="guide3148" /> + <sodipodi:guide + orientation="0,1" + position="36,22" + id="guide3150" /> + <sodipodi:guide + orientation="0,1" + position="36,6" + id="guide3152" /> + <sodipodi:guide + orientation="1,0" + position="18,0" + id="guide3154" /> + <sodipodi:guide + orientation="1,0" + position="10,0" + id="guide3160" /> + <sodipodi:guide + orientation="0,1" + position="0,20" + id="guide3162" /> + <sodipodi:guide + orientation="0,1" + position="0,18" + id="guide3164" /> + </sodipodi:namedview> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer" + inkscape:groupmode="layer" + id="layer" + transform="translate(0,-2)"> + <g + id="g3759" + style="fill:#fafafa;fill-opacity:1;stroke:none;fill-rule:nonzero;filter:url(#filter3811)"> + <path + style="display:none" + d="M 28,18 C 26,16 24,12 24,8 l -4,10 z" + id="path3809" + inkscape:connector-curvature="0" + transform="translate(0,2)" + sodipodi:nodetypes="cccc" /> + <path + inkscape:connector-curvature="0" + id="path2989" + d="m 20,12 0,12 12,0 z" + sodipodi:nodetypes="cccc" /> + <rect + ry="2" + y="4" + x="4" + height="20" + width="20" + id="rect2987" /> + </g> + </g> +</svg> diff --git a/art/omemo_logo.svg b/art/omemo_logo.svg new file mode 100644 index 00000000..ca20a5b9 --- /dev/null +++ b/art/omemo_logo.svg @@ -0,0 +1,273 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:osb="http://www.openswatchbook.org/uri/2009/osb" + 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" + id="svg4196" + version="1.1" + inkscape:version="0.91 r13725" + width="2367.5596" + height="1451.5084" + viewBox="0 0 2367.5595 1451.5084" + sodipodi:docname="omemo_logo.svg"> + <metadata + id="metadata4202"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs4200"> + <linearGradient + id="linearGradient4245" + osb:paint="solid"> + <stop + style="stop-color:#000000;stop-opacity:1;" + offset="0" + id="stop4247" /> + </linearGradient> + </defs> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1600" + inkscape:window-height="836" + id="namedview4198" + showgrid="false" + inkscape:zoom="0.32" + inkscape:cx="1158.7782" + inkscape:cy="667.71025" + inkscape:window-x="0" + inkscape:window-y="27" + inkscape:window-maximized="1" + inkscape:current-layer="svg4196" + fit-margin-top="0" + fit-margin-left="0" + fit-margin-right="0" + fit-margin-bottom="0" /> + <path + style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 1160.235,302.29735 271.9745,-131.35135 186.9826,134.44197 24.7249,151.44038 -86.5373,135.98729 z" + id="path4267" + inkscape:connector-curvature="0" + inkscape:export-xdpi="15.191093" + inkscape:export-ydpi="15.191093" /> + <path + style="fill:#f57c00;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 598.8809,1125.9476 -43.05491,131.8557 -21.52745,94.8553 4.0364,47.0913 67.27328,6.7273 80.72795,-58.5277 43.72764,-78.7098 7.40006,-55.1641 -21.52745,-71.9824 z" + id="path4259" + inkscape:connector-curvature="0" + inkscape:export-xdpi="15.191093" + inkscape:export-ydpi="15.191093" /> + <path + style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:10;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 709.52231,1171.4517 C 480.05218,1174.9052 321.72113,1008.3849 269.81593,895.97589 206.11648,758.02449 215.35674,596.92706 303.94612,450.17116 390.00741,320.24292 538.03872,188.34494 665.64434,170.1992 c 86.87989,-10.63238 215.40898,15.76659 250.11793,24.23821 35.046,8.55388 138.10213,41.16536 192.58973,67.91907 53.5186,26.27793 164.698,69.05834 309.1218,196.39025 100.3317,88.4579 183.2875,109.97875 279.7545,106.68109 52.9405,-1.80973 148.8273,-10.56706 171.5302,-24.72865 679.9746,-424.15329 639.4516,799.03733 13.1124,405.39142 -158.3183,-74.1014 -440.1478,10.5521 -637.0436,91.78671 -223.8429,92.3524 -350.01628,130.7858 -535.30499,133.5744 z" + id="path4225" + inkscape:connector-curvature="0" + sodipodi:nodetypes="ssccssssscss" + inkscape:export-xdpi="15.191093" + inkscape:export-ydpi="15.191093" /> + <path + inkscape:connector-curvature="0" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 2121.4484,451.36293 c -26.791,-0.0103 -69.7877,2.87028 -101.1871,10.73905 -68.1167,46.199 -138.5457,83.35128 -167.446,144.67176 -12.1866,25.8575 -15.1986,221.06115 -3.3883,250.53885 22.0574,55.0538 36.5353,68.5186 75.8437,113.5484 490.8133,255.43581 586.5854,-519.34849 196.1777,-519.49806 z" + id="path4225-4" + inkscape:export-xdpi="15.191093" + inkscape:export-ydpi="15.191093" + sodipodi:nodetypes="scsscs" /> + <path + style="fill:#f57c00;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 1879.4205,872.05219 c -30.0884,-43.2017 -23.0447,-213.01732 -11.2518,-239.49258 19.553,-43.89704 110.0168,-119.19707 177.1545,-153.50421 62.2867,-31.14337 245.3285,107.06591 242.3844,259.61033 -2.4489,126.88796 -74.9751,256.91706 -216.1596,260.51446 -95.0727,-15.7629 -143.2721,-56.9801 -192.1275,-127.128 z" + id="path4313" + inkscape:connector-curvature="0" + sodipodi:nodetypes="sscscs" + inkscape:export-xdpi="15.191093" + inkscape:export-ydpi="15.191093" /> + <path + style="fill:#f57c00;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 712.41248,50.975873 130.5787,23.17966 80.35619,97.354527 11.5898,38.63275 -335.33229,-24.72496 56.4038,-112.807627 z" + id="path4317" + inkscape:connector-curvature="0" + inkscape:export-xdpi="15.191093" + inkscape:export-ydpi="15.191093" /> + <path + inkscape:connector-curvature="0" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 1414.8968,95.744433 c -119.2326,0.1221 -252.577,46.677797 -357.9883,141.492197 59.2267,85.339 179.6057,681.13776 68.8789,839.94337 91.9688,196.1395 767.4955,273.501 557.166,-210.17391 -15.7049,-36.1151 -49.7142,-108.75426 -41.832,-193.48626 8.4493,-90.8299 56.4409,-192.1808 64.2324,-223.9238 57.3257,-233.5482 -98.225,-354.048497 -290.457,-353.851597 z m -37.9434,48.607397 c 179.9257,-1.202 313.9232,108.10167 295.8852,273.14927 -49.0308,223.244 -65.6093,352.99519 9.7574,506.70029 0.9067,322.06951 -372.1528,246.99471 -531.1856,150.28521 136.0694,-390.78747 -67.0566,-814.79857 -78.5644,-831.62107 107.9381,-67.831 213.0862,-97.9056 304.1074,-98.5137 z" + id="path4227-8" + inkscape:export-xdpi="15.191093" + inkscape:export-ydpi="15.191093" + sodipodi:nodetypes="sccsssssccccs" /> + <path + inkscape:connector-curvature="0" + style="fill:#f57c00;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 1371.9686,142.84932 c -91.0213,0.60808 -196.1693,30.68269 -304.1075,98.51367 11.5078,16.82249 214.6339,440.83547 78.5645,831.62311 159.0328,96.7094 532.0903,171.7842 531.1836,-150.28521 -75.3667,-153.7051 -47.9691,-295.82084 1.0617,-519.06483 19.5833,-183.59134 -126.7767,-261.98875 -306.7023,-260.78674 z m 42.0957,76.75039 c 158.8265,-0.80887 251.0755,161.9003 140.5517,325.36606 -113.709,-40.69316 -178.0341,-143.3305 -350.0787,-233.47358 73.9173,-58.593 149.3003,-91.58576 209.527,-91.89248 z" + id="path4229-6" + sodipodi:nodetypes="sccccssccs" + inkscape:export-xdpi="15.191093" + inkscape:export-ydpi="15.191093" /> + <path + style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 1354.944,727.52278 -6.0809,177.36791 128.2608,-32.6939 7.361,-132.81901 c 65.526,-55.1437 -11.1658,-135.6742 -75.9144,-147.0284 -93.1144,-16.3282 -143.1451,90.3398 -53.6265,135.1734 z" + id="path4233" + inkscape:connector-curvature="0" + sodipodi:nodetypes="ccccsc" + inkscape:export-xdpi="15.191093" + inkscape:export-ydpi="15.191093" /> + <path + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 598.8809,1127.2931 c -14.1274,92.1644 -82.99521,244.9415 -51.12771,263.7113 36.46239,21.4761 172.66811,-90.819 192.40161,-197.7835 18.83652,133.4254 -129.0419,247.1826 -195.76526,219.9837 -38.73013,-15.7879 4.93336,-176.7045 54.49136,-285.9115 z" + id="path4257" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cscsc" + inkscape:export-xdpi="15.191093" + inkscape:export-ydpi="15.191093" /> + <path + style="fill:#f57c00;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 713.64035,1156.0811 23.95231,78.8109 72.62957,139.0779 118.98884,69.5389 -1.5453,-78.0381 -40.9507,-101.9905 -65.67567,-100.4452 -27.04293,-32.4515 z" + id="path4261" + inkscape:connector-curvature="0" + inkscape:export-xdpi="15.191093" + inkscape:export-ydpi="15.191093" /> + <path + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 719.04894,1169.2163 c 9.27185,21.6343 50.66928,211.7208 189.30053,231.7965 30.3325,4.3925 -14.6805,-140.6232 -105.85379,-251.8855 102.24809,93.7488 161.32989,298.1418 122.07959,299.7901 C 810.58,1453.7045 732.44164,1267.601 719.04894,1169.2163 Z" + id="path4255" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cscsc" + inkscape:export-xdpi="15.191093" + inkscape:export-ydpi="15.191093" /> + <path + style="fill:#f57c00;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 234.13659,657.90289 -48.22924,-33.49254 -68.9946,-29.47343 -68.324762,2.00956 -31.48299,135.97969 54.2579,195.59632 92.028162,42.154 87.08057,10.8767 79.91784,5.717 49.77454,-1.1406 z" + id="path4265" + inkscape:connector-curvature="0" + inkscape:export-xdpi="15.191093" + inkscape:export-ydpi="15.191093" + sodipodi:nodetypes="ccccccccccc" /> + <path + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.81825721px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 362.2228,973.50299 c -87.01468,18.7244 -206.31388,2.7914 -260.29527,-66.2183 C 40.854878,829.20969 44.412488,641.34522 72.212698,611.40084 98.152348,583.46053 206.19233,642.42569 258.48372,672.39141 226.33414,633.9643 97.758248,551.92129 22.266478,615.19423 c -39.234376,32.88402 -22.2634293,269.25766 24.02476,303.47066 82.593032,61.047 269.567992,98.25131 315.931562,54.8381 z" + id="path4263" + inkscape:connector-curvature="0" + sodipodi:nodetypes="csscssc" + inkscape:export-xdpi="15.191093" + inkscape:export-ydpi="15.191093" /> + <path + inkscape:connector-curvature="0" + style="fill:#f57c00;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:10;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 705.09299,169.2159 c -15.02488,0.0627 -29.55297,0.81546 -43.06769,2.46948 -127.0389,18.06512 -274.41191,149.37803 -360.09102,278.72918 -88.19593,146.10416 -97.39583,306.48603 -33.97922,443.82493 51.67469,111.90981 209.30324,277.68941 437.75427,274.25121 103.34093,-1.5552 188.21293,-14.2523 282.05624,-41.2806 l 53.34803,-135.65461 7.6922,-153.845 -32.3069,-87.691 -68.46233,-87.69283 -22.3067,-99.22916 30.769,-200.76658 -28.3598,-161.94745 c -6.6814,-1.88509 -12.5089,-3.44563 -17.1054,-4.56753 -29.15558,-7.11615 -124.80661,-26.93763 -205.94068,-26.60004 z" + id="path4225-42-9" + inkscape:export-xdpi="15.191093" + inkscape:export-ydpi="15.191093" /> + <path + style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 447.07437,279.1177 -67.22098,67.22099 -37.8601,46.3593 180.02862,4.63593 96.58187,45.58664 70.31161,81.90143 47.13196,130.5787 -4.63593,166.8935 -88.85533,154.531 -78.03816,55.63111 16.99841,16.2258 81.12878,2.318 72.62957,-32.4515 L 807.90426,918.10339 837.26515,722.62168 810.99488,518.64075 734.50204,415.87764 630.96627,335.52152 535.9297,298.43408 Z" + id="path4247" + inkscape:connector-curvature="0" + inkscape:export-xdpi="15.191093" + inkscape:export-ydpi="15.191093" /> + <path + style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 704.34544,1170.0033 C 474.87531,1173.4568 316.54426,1006.9366 264.63906,894.52759 200.93961,756.57613 210.17987,595.47873 298.76925,448.72283 384.83054,318.7946 532.86185,186.89663 660.46747,168.75089 c 86.87989,-10.63238 215.40898,15.76659 250.1179,24.23821 35.046,8.55388 138.10213,41.16536 192.58973,67.91907 53.5186,26.27792 164.698,69.05836 309.1218,196.39026 100.3317,88.4579 183.2875,109.9787 279.7545,106.6811 52.9405,-1.8098 148.8273,-10.5671 171.5302,-24.7287 679.9746,-424.15326 639.4516,799.03727 13.1124,405.39146 -158.3183,-74.1014 -440.1478,10.5521 -637.0436,91.78671 -223.8429,92.3524 -350.0163,130.7857 -535.30496,133.5743 z" + id="path4225-42" + inkscape:connector-curvature="0" + sodipodi:nodetypes="ssccssssscss" + inkscape:export-xdpi="15.191093" + inkscape:export-ydpi="15.191093" /> + <path + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 600.77485,188.39742 c 45.59931,-168.111187 93.61702,-207.521997 165.6011,-175.903587 28.11465,12.34913 88.59168,36.45928 110.93127,66.5789 46.81515,63.119057 81.36115,162.974077 99.35615,284.156557 -8.7416,75.03201 -41.5452,164.02089 -27.3175,238.20842 17.5559,91.54126 116.68213,142.15421 125.66043,234.93028 9.4985,98.1511 -22.9467,217.44721 -86.32323,293.93611 36.78763,-80.4955 64.77883,-202.86651 55.72773,-281.91641 -15.7564,-137.61237 -102.80503,-141.89728 -115.82623,-244.76458 -9.3046,-73.506 20.1158,-155.47823 24.0394,-229.46683 3.7424,-70.5705 -32.2949,-195.09979 -74.30353,-233.83762 C 781.36459,50.911903 652.78479,43.071673 600.77485,188.39742 Z" + id="path4405" + inkscape:connector-curvature="0" + sodipodi:nodetypes="csscsscssssc" + inkscape:export-xdpi="15.191093" + inkscape:export-ydpi="15.191093" /> + <path + style="fill:#f57c00;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 978.84877,929.24739 143.14363,-41.5225 87.4159,-74.3036 5.4635,-186.85146 -73.2108,-30.5956 -65.562,22.9467 -77.58163,46.986 -87.416,87.416 z" + id="path4289" + inkscape:connector-curvature="0" + inkscape:export-xdpi="15.191093" + inkscape:export-ydpi="15.191093" /> + <path + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 913.12497,751.04553 c 45.1649,-86.3232 245.53153,-195.3877 312.51203,-177.0172 29.651,8.1322 84.3992,143.4773 -29.5028,270.98936 -50.127,56.1165 -219.63263,88.5086 -219.63263,88.5086 l 2.1854,-6.5562 c 0,0 154.41873,-31.7084 192.31513,-91.7868 38.4759,-60.9969 52.9259,-177.98746 0,-216.35436 -79.3518,-57.5234 -257.87713,132.2166 -257.87713,132.2166 z" + id="path4287" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cssccssc" + inkscape:export-xdpi="15.191093" + inkscape:export-ydpi="15.191093" /> + <path + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 330.40347,402.7425 c 114.35294,-10.55962 249.75787,-2.93669 319.87917,81.90143 79.96514,96.74795 96.08396,242.01494 62.58506,351.55806 -23.16188,75.7405 -98.38474,154.531 -163.80286,199.34501 60.19701,36.3696 76.03151,31.5859 158.39427,3.8632 C 761.64992,1021.17 829.49446,914.53909 837.26515,833.88399 848.77388,714.43039 855.97093,574.91586 790.90585,472.28145 719.85004,360.19719 579.71348,287.1018 454.02827,276.79974 l -13.13513,9.27185 c 166.63592,15.4531 280.2303,99.1189 342.28616,214.02545 65.19894,120.72647 36.96723,291.05045 30.9062,330.69635 -11.59484,75.8433 -39.28607,162.0595 -121.30683,197.02701 -32.03238,13.6562 -80.61368,27.043 -116.67091,8.4993 C 636.37485,988.41499 708.98856,931.86239 729.09345,855.51829 762.38235,729.11061 744.53737,534.45916 642.55609,446.01118 573.9429,386.50322 397.62445,372.60895 347.40188,381.10816 Z" + id="path4245" + inkscape:connector-curvature="0" + sodipodi:nodetypes="csscsssccssscsscc" + inkscape:export-xdpi="15.191093" + inkscape:export-ydpi="15.191093" /> + <path + style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:10;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="path4249" + sodipodi:type="arc" + sodipodi:cx="592.71979" + sodipodi:cy="560.36414" + sodipodi:rx="41.337044" + sodipodi:ry="48.677265" + sodipodi:start="0" + sodipodi:end="6.2714218" + sodipodi:open="true" + d="m 634.05683,560.36414 a 41.337044,48.677265 0 0 1 -41.21548,48.67705 41.337044,48.677265 0 0 1 -41.45789,-48.39075 41.337044,48.677265 0 0 1 40.97163,-48.96167 41.337044,48.677265 0 0 1 41.69888,48.10276" + inkscape:export-xdpi="15.191093" + inkscape:export-ydpi="15.191093" /> + <path + style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 329.82495,822.87809 c 50.9573,98.8977 80.17049,31.9344 81.80769,19.0432 2.98204,-23.4803 -26.03926,-8.0283 -44.87764,-12.8177 -24.76611,-6.2965 -49.64587,-30.9043 -36.93005,-6.2255 z" + id="path4253" + inkscape:connector-curvature="0" + sodipodi:nodetypes="ssss" + inkscape:export-xdpi="15.191093" + inkscape:export-ydpi="15.191093" /> + <path + style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:10;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 349.24366,1015.5476 c 24.85373,22.5357 29.23211,28.8458 48.29094,41.7233 13.9627,-4.7761 21.9738,-0.484 43.60813,-17.9975 -43.655,-2.9618 -58.6749,-15.7418 -91.89907,-23.7258 z" + id="path4339" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cccc" + inkscape:export-xdpi="15.191093" + inkscape:export-ydpi="15.191093" /> + <path + style="fill:#000000;fill-opacity:0.11764706;fill-rule:evenodd;stroke:none;stroke-width:10;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 1840.2193,917.45789 c -144.4722,-64.5024 -401.6544,9.1852 -581.3301,79.8965 C 1054.6229,1077.7432 939.48447,1111.1985 770.40085,1113.6259 560.99961,1116.632 416.51658,971.68219 369.15085,873.83489 311.02239,753.75418 319.45382,613.52678 400.29538,485.78208 478.83,372.68518 613.91429,257.87398 730.35984,242.07898 c 12.38775,-1.4461 25.70459,-2.1055 39.47656,-2.1601 74.36866,-0.2952 162.04327,17.0358 188.76757,23.2578 31.981,7.4458 126.02373,35.8332 175.74613,59.1211 48.8379,22.8738 150.2931,60.1123 282.0859,170.9492 91.5569,76.9988 167.2589,95.7317 255.2891,92.8613 48.3104,-1.5753 135.8099,-9.1984 156.5274,-21.5254 l 39.4824,-26.4785 c -22.7029,14.1616 -118.5888,22.9187 -171.5293,24.7285 -96.467,3.2976 -179.4222,-18.2237 -279.7539,-106.6816 -144.4238,-127.3319 -255.6045,-170.1108 -309.1231,-196.3887 -54.4876,-26.7537 -157.54383,-59.3661 -192.58983,-67.9199 -29.28571,-7.148 -125.36331,-27.0578 -206.8594,-26.7188 -15.09187,0.063 -29.68283,0.8192 -43.25782,2.4805 -127.60562,18.1458 -275.63793,150.0445 -361.69921,279.9727 -88.58938,146.7559 -97.82836,307.8532 -34.12891,445.80461 51.9052,112.40901 210.23495,278.92821 439.70508,275.47471 185.28865,-2.7886 311.46379,-41.2219 535.30669,-133.5743 196.8958,-81.23461 478.7247,-165.88851 637.043,-91.78711 z" + id="path4225-42-3" + inkscape:connector-curvature="0" + sodipodi:nodetypes="csssccsssssccsssssccssscc" + inkscape:export-xdpi="15.191093" + inkscape:export-ydpi="15.191093" /> + <path + style="fill:#000000;fill-opacity:0.11764706;fill-rule:evenodd;stroke:none;stroke-width:10;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 707.63885,164.4148 c -15.0918,0.063 -29.6848,0.8211 -43.2597,2.4824 -127.6056,18.1458 -275.6361,150.0425 -361.6973,279.9707 -88.5894,146.7559 -97.8304,307.8533 -34.1309,445.80469 51.9052,112.40901 210.237,278.93011 439.7071,275.47661 185.2887,-2.7886 311.46185,-41.2218 535.30475,-133.5742 195.3026,-80.57741 474.1694,-164.51701 633.1757,-93.55281 -7.0564,-4.019 -14.1914,-8.2481 -21.4101,-12.7011 -151.9427,-69.8031 -422.4225,9.9405 -611.3887,86.46281 -214.8282,86.9953 -335.92133,123.1994 -513.74805,125.8262 -220.2288,3.2531 -372.1832,-153.60771 -421.9981,-259.49611 -61.1341,-129.94919 -52.2658,-281.70249 32.7559,-419.94529 82.5954,-122.3913 224.6641,-246.6373 347.1308,-263.7305 83.381,-10.0156 206.734,14.8519 240.04502,22.8321 33.6347,8.0576 132.54073,38.7767 184.83393,63.9785 51.3632,24.7535 158.0664,65.0525 296.6738,184.998 96.2911,83.3266 175.9061,103.5985 268.4883,100.4922 50.8085,-1.7048 142.8325,-9.9528 164.6211,-23.2929 0.3844,-0.2354 0.7646,-0.4611 1.1485,-0.6954 -38.3016,9.244 -106.3509,14.9537 -147.9278,16.375 -96.467,3.2976 -179.4222,-18.2237 -279.7539,-106.6816 C 1271.7855,328.1122 1160.6067,285.3314 1107.0881,259.0535 1052.6005,232.2998 949.54427,199.6894 914.49827,191.1355 885.21265,183.9876 789.13495,164.0757 707.63885,164.4148 Z" + id="path4421" + inkscape:connector-curvature="0" + inkscape:export-xdpi="15.191093" + inkscape:export-ydpi="15.191093" /> +</svg> diff --git a/art/render.rb b/art/render.rb index 23548d94..b4f84769 100755 --- a/art/render.rb +++ b/art/render.rb @@ -1,47 +1,129 @@ #!/bin/env ruby -resolutions={ - 'mdpi'=> 1, + +require 'xml' + +resolutions = { + 'mdpi' => 1, 'hdpi' => 1.5, 'xhdpi' => 2, 'xxhdpi' => 3, 'xxxhdpi' => 4, } + images = { - #'conversations_baloon.svg' => ['ic_launcher', 48], - 'conversations_plus_baloons.svg' => ['ic_launcher', 48], + 'conversations_baloon.svg' => ['ic_launcher', 48], 'conversations_mono.svg' => ['ic_notification', 24], 'ic_received_indicator.svg' => ['ic_received_indicator', 12], 'ic_send_text_offline.svg' => ['ic_send_text_offline', 36], 'ic_send_text_online.svg' => ['ic_send_text_online', 36], 'ic_send_text_away.svg' => ['ic_send_text_away', 36], 'ic_send_text_dnd.svg' => ['ic_send_text_dnd', 36], - 'ic_send_photo_online.svg' => ['ic_send_photo_online', 36], - 'ic_send_photo_offline.svg' => ['ic_send_photo_offline', 36], - 'ic_send_photo_away.svg' => ['ic_send_photo_away', 36], - 'ic_send_photo_dnd.svg' => ['ic_send_photo_dnd', 36], + 'ic_send_photo_online.svg' => ['ic_send_photo_online', 36], + 'ic_send_photo_offline.svg' => ['ic_send_photo_offline', 36], + 'ic_send_photo_away.svg' => ['ic_send_photo_away', 36], + 'ic_send_photo_dnd.svg' => ['ic_send_photo_dnd', 36], 'ic_send_location_online.svg' => ['ic_send_location_online', 36], - 'ic_send_location_offline.svg' => ['ic_send_location_offline', 36], - 'ic_send_location_away.svg' => ['ic_send_location_away', 36], - 'ic_send_location_dnd.svg' => ['ic_send_location_dnd', 36], + 'ic_send_location_offline.svg' => ['ic_send_location_offline', 36], + 'ic_send_location_away.svg' => ['ic_send_location_away', 36], + 'ic_send_location_dnd.svg' => ['ic_send_location_dnd', 36], 'ic_send_voice_online.svg' => ['ic_send_voice_online', 36], - 'ic_send_voice_offline.svg' => ['ic_send_voice_offline', 36], - 'ic_send_voice_away.svg' => ['ic_send_voice_away', 36], - 'ic_send_voice_dnd.svg' => ['ic_send_voice_dnd', 36], + 'ic_send_voice_offline.svg' => ['ic_send_voice_offline', 36], + 'ic_send_voice_away.svg' => ['ic_send_voice_away', 36], + 'ic_send_voice_dnd.svg' => ['ic_send_voice_dnd', 36], 'ic_send_cancel_online.svg' => ['ic_send_cancel_online', 36], - 'ic_send_cancel_offline.svg' => ['ic_send_cancel_offline', 36], - 'ic_send_cancel_away.svg' => ['ic_send_cancel_away', 36], - 'ic_send_cancel_dnd.svg' => ['ic_send_cancel_dnd', 36], - 'ic_send_picture_online.svg' => ['ic_send_picture_online', 36], - 'ic_send_picture_offline.svg' => ['ic_send_picture_offline', 36], - 'ic_send_picture_away.svg' => ['ic_send_picture_away', 36], - 'ic_send_picture_dnd.svg' => ['ic_send_picture_dnd', 36] + 'ic_send_cancel_offline.svg' => ['ic_send_cancel_offline', 36], + 'ic_send_cancel_away.svg' => ['ic_send_cancel_away', 36], + 'ic_send_cancel_dnd.svg' => ['ic_send_cancel_dnd', 36], + 'ic_send_picture_online.svg' => ['ic_send_picture_online', 36], + 'ic_send_picture_offline.svg' => ['ic_send_picture_offline', 36], + 'ic_send_picture_away.svg' => ['ic_send_picture_away', 36], + 'ic_send_picture_dnd.svg' => ['ic_send_picture_dnd', 36], + 'md_switch_thumb_disable.svg' => ['switch_thumb_disable', 48], + 'md_switch_thumb_off_normal.svg' => ['switch_thumb_off_normal', 48], + 'md_switch_thumb_off_pressed.svg' => ['switch_thumb_off_pressed', 48], + 'md_switch_thumb_on_normal.svg' => ['switch_thumb_on_normal', 48], + 'md_switch_thumb_on_pressed.svg' => ['switch_thumb_on_pressed', 48], + 'message_bubble_received.svg' => ['message_bubble_received.9', 0], + 'message_bubble_received_warning.svg' => ['message_bubble_received_warning.9', 0], + 'message_bubble_received_white.svg' => ['message_bubble_received_white.9', 0], + 'message_bubble_sent.svg' => ['message_bubble_sent.9', 0], } -images.each do |source, result| - resolutions.each do |name, factor| - size = factor * result[1] - path = "../src/main/res/drawable-#{name}/#{result[0]}.png" - cmd = "inkscape -e #{path} -C -h #{size} -w #{size} #{source}" - puts cmd - system cmd + +# Executable paths for Mac OSX +# "/Applications/Inkscape.app/Contents/Resources/bin/inkscape" + +inkscape = "inkscape" +imagemagick = "convert" + +def execute_cmd(cmd) + puts cmd + system cmd +end + +images.each do |source_filename, settings| + svg_content = File.read(source_filename) + + svg = XML::Document.string(svg_content) + base_width = svg.root["width"].to_i + base_height = svg.root["height"].to_i + + guides = svg.find(".//sodipodi:guide") + + resolutions.each do |resolution, factor| + output_filename, base_size = settings + + if base_size > 0 + width = factor * base_size + height = factor * base_size + else + width = factor * base_width + height = factor * base_height + end + + path = "../src/main/res/drawable-#{resolution}/#{output_filename}.png" + execute_cmd "#{inkscape} -f #{source_filename} -z -C -w #{width} -h #{height} -e #{path}" + + top = [] + right = [] + bottom = [] + left = [] + + guides.each do |guide| + orientation = guide["orientation"] + x, y = guide["position"].split(",") + x, y = x.to_i, y.to_i + + if orientation == "1,0" and y == base_height + top.push(x * factor) + end + + if orientation == "0,1" and x == base_width + right.push((base_height - y) * factor) + end + + if orientation == "1,0" and y == 0 + bottom.push(x * factor) + end + + if orientation == "0,1" and x == 0 + left.push((base_height - y) * factor) + end + end + + next if top.length != 2 + next if right.length != 2 + next if bottom.length != 2 + next if left.length != 2 + + execute_cmd "#{imagemagick} -background none PNG32:#{path} -gravity center -extent #{width+2}x#{height+2} PNG32:#{path}" + + draw_format = "-draw \"rectangle %d,%d %d,%d\"" + top_line = draw_format % [top.min + 1, 0, top.max, 0] + right_line = draw_format % [width + 1, right.min + 1, width + 1, right.max] + bottom_line = draw_format % [bottom.min + 1, height + 1, bottom.max, height + 1] + left_line = draw_format % [0, left.min + 1, 0, left.max] + draws = "#{top_line} #{right_line} #{bottom_line} #{left_line}" + + execute_cmd "#{imagemagick} -background none PNG32:#{path} -fill black -stroke none #{draws} PNG32:#{path}" end end diff --git a/build.gradle b/build.gradle index 61521f2d..5e5f9b07 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:1.2.3' + classpath 'com.android.tools.build:gradle:1.3.1' } } @@ -14,6 +14,9 @@ allprojects { repositories { jcenter() mavenCentral() + maven { + url 'http://lorenzo.villani.me/android-cropimage/' + } } } @@ -23,19 +26,27 @@ repositories { flatDir { dirs 'libs/3rdParty', 'libs/3rdParty/zxing' } + jcenter() + mavenCentral() } dependencies { + compile 'com.soundcloud.android:android-crop:1.0.1@aar' + compile 'org.bouncycastle:bcmail-jdk15on:1.52' + compile 'com.kyleduo.switchbutton:library:1.2.8' + compile 'org.whispersystems:axolotl-android:1.3.4' + compile 'com.makeramen:roundedimageview:2.2.0' + // Local JAR files //compile fileTree(dir: 'libs/zxing', includes: ['core-3.1.0.jar', 'android-integration-3.1.0.jar']) - compile name: 'core-3.1.0' //zxing - compile name: 'android-integration-3.1.0' //zxing + compile name: 'core-3.2.1' //zxing + compile name: 'android-integration-3.2.1' //zxing compile name: 'libidn-1.15' - compile name: 'minidns-0.1.3' + compile name: 'minidns-0.1.7' compile name: 'org.otr4j-0.22' - compile name: 'bcprov-jdk15on-1.51' + compile name: 'bcprov-jdk15on-1.52' compile name: 'EnhancedListView-0.3.4', ext: 'aar' - compile name: 'ShortcutBadger-1.1.1', ext: 'aar' + compile name: 'ShortcutBadger-1.1.3', ext: 'aar' compile name: 'swipy-1.2.1', ext: 'aar' // Local modules @@ -50,14 +61,15 @@ dependencies { } android { - compileSdkVersion 22 - buildToolsVersion "22.0.1" + compileSdkVersion 23 + buildToolsVersion "23.0.2" defaultConfig { minSdkVersion 14 - targetSdkVersion 21 - versionCode 65 - versionName "1.5.2.2" + targetSdkVersion 23 + versionCode 122 + versionName "1.9.3" + project.ext.set(archivesBaseName, archivesBaseName + "-" + versionName); } compileOptions { @@ -104,7 +116,7 @@ android { } lintOptions { - disable 'ExtraTranslation', 'MissingTranslation', 'InvalidPackage', 'MissingQuantity' + disable 'ExtraTranslation', 'MissingTranslation', 'InvalidPackage', 'MissingQuantity', 'AppCompatResource' } subprojects { diff --git a/libs/openpgp-api-lib/.gitignore b/libs/openpgp-api-lib/.gitignore deleted file mode 100644 index aa8bb576..00000000 --- a/libs/openpgp-api-lib/.gitignore +++ /dev/null @@ -1,29 +0,0 @@ -#Android specific -bin -gen -obj -lint.xml -local.properties -release.properties -ant.properties -*.class -*.apk - -#Gradle -.gradle -build -gradle.properties - -#Maven -target -pom.xml.* - -#Eclipse -.project -.classpath -.settings -.metadata - -#IntelliJ IDEA -.idea -*.iml diff --git a/libs/openpgp-api-lib/.tx/config b/libs/openpgp-api-lib/.tx/config deleted file mode 100644 index 9e6de616..00000000 --- a/libs/openpgp-api-lib/.tx/config +++ /dev/null @@ -1,8 +0,0 @@ -[main] -host = https://www.transifex.com -lang_map = af_ZA: af-rZA, am_ET: am-rET, ar_AE: ar-rAE, ar_BH: ar-rBH, ar_DZ: ar-rDZ, ar_EG: ar-rEG, ar_IQ: ar-rIQ, ar_JO: ar-rJO, ar_KW: ar-rKW, ar_LB: ar-rLB, ar_LY: ar-rLY, ar_MA: ar-rMA, ar_OM: ar-rOM, ar_QA: ar-rQA, ar_SA: ar-rSA, ar_SY: ar-rSY, ar_TN: ar-rTN, ar_YE: ar-rYE, arn_CL: arn-rCL, as_IN: as-rIN, az_AZ: az-rAZ, ba_RU: ba-rRU, be_BY: be-rBY, bg_BG: bg-rBG, bn_BD: bn-rBD, bn_IN: bn-rIN, bo_CN: bo-rCN, br_FR: br-rFR, bs_BA: bs-rBA, ca_ES: ca-rES, co_FR: co-rFR, cs_CZ: cs-rCZ, cy_GB: cy-rGB, da_DK: da-rDK, de_AT: de-rAT, de_CH: de-rCH, de_DE: de-rDE, de_LI: de-rLI, de_LU: de-rLU, dsb_DE: dsb-rDE, dv_MV: dv-rMV, el_GR: el-rGR, en_AU: en-rAU, en_BZ: en-rBZ, en_CA: en-rCA, en_GB: en-rGB, en_IE: en-rIE, en_IN: en-rIN, en_JM: en-rJM, en_MY: en-rMY, en_NZ: en-rNZ, en_PH: en-rPH, en_SG: en-rSG, en_TT: en-rTT, en_US: en-rUS, en_ZA: en-rZA, en_ZW: en-rZW, es_AR: es-rAR, es_BO: es-rBO, es_CL: es-rCL, es_CO: es-rCO, es_CR: es-rCR, es_DO: es-rDO, es_EC: es-rEC, es_ES: es-rES, es_GT: es-rGT, es_HN: es-rHN, es_MX: es-rMX, es_NI: es-rNI, es_PA: es-rPA, es_PE: es-rPE, es_PR: es-rPR, es_PY: es-rPY, es_SV: es-rSV, es_US: es-rUS, es_UY: es-rUY, es_VE: es-rVE, et_EE: et-rEE, eu_ES: eu-rES, fa_IR: fa-rIR, fi_FI: fi-rFI, fil_PH: fil-rPH, fo_FO: fo-rFO, fr_BE: fr-rBE, fr_CA: fr-rCA, fr_CH: fr-rCH, fr_FR: fr-rFR, fr_LU: fr-rLU, fr_MC: fr-rMC, fy_NL: fy-rNL, ga_IE: ga-rIE, gd_GB: gd-rGB, gl_ES: gl-rES, gsw_FR: gsw-rFR, gu_IN: gu-rIN, ha_NG: ha-rNG, hi_IN: hi-rIN, hr_BA: hr-rBA, hr_HR: hr-rHR, hsb_DE: hsb-rDE, hu_HU: hu-rHU, hy_AM: hy-rAM, id_ID: id-rID, ig_NG: ig-rNG, ii_CN: ii-rCN, is_IS: is-rIS, it_CH: it-rCH, it_IT: it-rIT, iu_CA: iu-rCA, ja_JP: ja-rJP, ka_GE: ka-rGE, kk_KZ: kk-rKZ, kl_GL: kl-rGL, km_KH: km-rKH, kn_IN: kn-rIN, ko_KR: ko-rKR, kok_IN: kok-rIN, ky_KG: ky-rKG, lb_LU: lb-rLU, lo_LA: lo-rLA, lt_LT: lt-rLT, lv_LV: lv-rLV, mi_NZ: mi-rNZ, mk_MK: mk-rMK, ml_IN: ml-rIN, mn_CN: mn-rCN, mn_MN: mn-rMN, moh_CA: moh-rCA, mr_IN: mr-rIN, ms_BN: ms-rBN, ms_MY: ms-rMY, mt_MT: mt-rMT, nb_NO: nb-rNO, ne_NP: ne-rNP, nl_BE: nl-rBE, nl_NL: nl-rNL, nn_NO: nn-rNO, nso_ZA: nso-rZA, oc_FR: oc-rFR, or_IN: or-rIN, pa_IN: pa-rIN, pl_PL: pl-rPL, prs_AF: prs-rAF, ps_AF: ps-rAF, pt_BR: pt-rBR, pt_PT: pt-rPT, qut_GT: qut-rGT, quz_BO: quz-rBO, quz_EC: quz-rEC, quz_PE: quz-rPE, rm_CH: rm-rCH, ro_RO: ro-rRO, ru_RU: ru-rRU, rw_RW: rw-rRW, sa_IN: sa-rIN, sah_RU: sah-rRU, se_FI: se-rFI, se_NO: se-rNO, se_SE: se-rSE, si_LK: si-rLK, sk_SK: sk-rSK, sl_SI: sl-rSI, sma_NO: sma-rNO, sma_SE: sma-rSE, smj_NO: smj-rNO, smj_SE: smj-rSE, smn_FI: smn-rFI, sms_FI: sms-rFI, sq_AL: sq-rAL, sr_BA: sr-rBA, sr_CS: sr-rCS, sr_ME: sr-rME, sr_RS: sr-rRS, sv_FI: sv-rFI, sv_SE: sv-rSE, sw_KE: sw-rKE, syr_SY: syr-rSY, ta_IN: ta-rIN, te_IN: te-rIN, tg_TJ: tg-rTJ, th_TH: th-rTH, tk_TM: tk-rTM, tn_ZA: tn-rZA, tr_TR: tr-rTR, tt_RU: tt-rRU, tzm_DZ: tzm-rDZ, ug_CN: ug-rCN, uk_UA: uk-rUA, ur_PK: ur-rPK, uz_UZ: uz-rUZ, vi_VN: vi-rVN, wo_SN: wo-rSN, xh_ZA: xh-rZA, yo_NG: yo-rNG, zh_CN: zh-rCN, zh_HK: zh-rHK, zh_MO: zh-rMO, zh_SG: zh-rSG, zh_TW: zh-rTW, zu_ZA: zu-rZA, no_NO: no-rNO, he_IL: iw-rIL, he: iw - -[open-keychain.api-strings] -file_filter = res/values-<lang>/strings.xml -source_file = res/values/strings.xml -source_lang = en diff --git a/libs/openpgp-api-lib/AndroidManifest.xml b/libs/openpgp-api-lib/AndroidManifest.xml deleted file mode 100644 index 98cb89fa..00000000 --- a/libs/openpgp-api-lib/AndroidManifest.xml +++ /dev/null @@ -1,13 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="org.openintents.openpgp" - android:versionCode="1" - android:versionName="1.0" > - - <uses-sdk - android:minSdkVersion="9" - android:targetSdkVersion="19" /> - - <application/> - -</manifest>
\ No newline at end of file diff --git a/libs/openpgp-api-lib/LICENSE b/libs/openpgp-api-lib/LICENSE deleted file mode 100644 index d6456956..00000000 --- a/libs/openpgp-api-lib/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/libs/openpgp-api-lib/README.md b/libs/openpgp-api-lib/README.md deleted file mode 100644 index aefc9ed3..00000000 --- a/libs/openpgp-api-lib/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# OpenPGP API library - -The OpenPGP API provides methods to execute OpenPGP operations, such as sign, encrypt, decrypt, verify, and more without user interaction from background threads. This is done by connecting your client application to a remote service provided by [OpenKeychain](http://www.openkeychain.org) or other OpenPGP providers. - -For usage instructions, please consult our Wiki page about the [OpenPGP API](https://github.com/open-keychain/open-keychain/wiki/OpenPGP-API). - -License -======= - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - diff --git a/libs/openpgp-api-lib/build.xml b/libs/openpgp-api-lib/build.xml deleted file mode 100644 index 48ebf198..00000000 --- a/libs/openpgp-api-lib/build.xml +++ /dev/null @@ -1,92 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<project name="keychain-api-library" default="help"> - - <!-- The local.properties file is created and updated by the 'android' tool. - It contains the path to the SDK. It should *NOT* be checked into - Version Control Systems. --> - <property file="local.properties" /> - - <!-- The ant.properties file can be created by you. It is only edited by the - 'android' tool to add properties to it. - This is the place to change some Ant specific build properties. - Here are some properties you may want to change/update: - - source.dir - The name of the source directory. Default is 'src'. - out.dir - The name of the output directory. Default is 'bin'. - - For other overridable properties, look at the beginning of the rules - files in the SDK, at tools/ant/build.xml - - Properties related to the SDK location or the project target should - be updated using the 'android' tool with the 'update' action. - - This file is an integral part of the build system for your - application and should be checked into Version Control Systems. - - --> - <property file="ant.properties" /> - - <!-- if sdk.dir was not set from one of the property file, then - get it from the ANDROID_HOME env var. - This must be done before we load project.properties since - the proguard config can use sdk.dir --> - <property environment="env" /> - <condition property="sdk.dir" value="${env.ANDROID_HOME}"> - <isset property="env.ANDROID_HOME" /> - </condition> - - <!-- The project.properties file is created and updated by the 'android' - tool, as well as ADT. - - This contains project specific properties such as project target, and library - dependencies. Lower level build properties are stored in ant.properties - (or in .classpath for Eclipse projects). - - This file is an integral part of the build system for your - application and should be checked into Version Control Systems. --> - <loadproperties srcFile="project.properties" /> - - <!-- quick check on sdk.dir --> - <fail - message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable." - unless="sdk.dir" - /> - - <!-- - Import per project custom build rules if present at the root of the project. - This is the place to put custom intermediary targets such as: - -pre-build - -pre-compile - -post-compile (This is typically used for code obfuscation. - Compiled code location: ${out.classes.absolute.dir} - If this is not done in place, override ${out.dex.input.absolute.dir}) - -post-package - -post-build - -pre-clean - --> - <import file="custom_rules.xml" optional="true" /> - - <!-- Import the actual build file. - - To customize existing targets, there are two options: - - Customize only one target: - - copy/paste the target into this file, *before* the - <import> task. - - customize it to your needs. - - Customize the whole content of build.xml - - copy/paste the content of the rules files (minus the top node) - into this file, replacing the <import> task. - - customize to your needs. - - *********************** - ****** IMPORTANT ****** - *********************** - In all cases you must update the value of version-tag below to read 'custom' instead of an integer, - in order to avoid having your file be overridden by tools such as "android update project" - --> - <!-- version-tag: 1 --> - <import file="${sdk.dir}/tools/ant/build.xml" /> - -</project> diff --git a/libs/openpgp-api-lib/proguard-project.txt b/libs/openpgp-api-lib/proguard-project.txt deleted file mode 100644 index f2fe1559..00000000 --- a/libs/openpgp-api-lib/proguard-project.txt +++ /dev/null @@ -1,20 +0,0 @@ -# To enable ProGuard in your project, edit project.properties -# to define the proguard.config property as described in that file. -# -# Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in ${sdk.dir}/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the ProGuard -# include property in project.properties. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# Add any project specific keep options here: - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} diff --git a/libs/openpgp-api-lib/project.properties b/libs/openpgp-api-lib/project.properties deleted file mode 100644 index 91d2b024..00000000 --- a/libs/openpgp-api-lib/project.properties +++ /dev/null @@ -1,15 +0,0 @@ -# This file is automatically generated by Android Tools. -# Do not modify this file -- YOUR CHANGES WILL BE ERASED! -# -# This file must be checked in Version Control Systems. -# -# To customize properties used by the Ant build system edit -# "ant.properties", and override values to adapt the script to your -# project structure. -# -# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): -#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt - -# Project target. -target=android-19 -android.library=true diff --git a/libs/openpgp-api-lib/res/drawable-hdpi/ic_action_cancel_launchersize.png b/libs/openpgp-api-lib/res/drawable-hdpi/ic_action_cancel_launchersize.png Binary files differdeleted file mode 100644 index 71b9118d..00000000 --- a/libs/openpgp-api-lib/res/drawable-hdpi/ic_action_cancel_launchersize.png +++ /dev/null diff --git a/libs/openpgp-api-lib/res/drawable-hdpi/ic_action_cancel_launchersize_light.png b/libs/openpgp-api-lib/res/drawable-hdpi/ic_action_cancel_launchersize_light.png Binary files differdeleted file mode 100644 index 73b1d08f..00000000 --- a/libs/openpgp-api-lib/res/drawable-hdpi/ic_action_cancel_launchersize_light.png +++ /dev/null diff --git a/libs/openpgp-api-lib/res/drawable-mdpi/ic_action_cancel_launchersize.png b/libs/openpgp-api-lib/res/drawable-mdpi/ic_action_cancel_launchersize.png Binary files differdeleted file mode 100644 index 270abf45..00000000 --- a/libs/openpgp-api-lib/res/drawable-mdpi/ic_action_cancel_launchersize.png +++ /dev/null diff --git a/libs/openpgp-api-lib/res/drawable-mdpi/ic_action_cancel_launchersize_light.png b/libs/openpgp-api-lib/res/drawable-mdpi/ic_action_cancel_launchersize_light.png Binary files differdeleted file mode 100644 index d841821c..00000000 --- a/libs/openpgp-api-lib/res/drawable-mdpi/ic_action_cancel_launchersize_light.png +++ /dev/null diff --git a/libs/openpgp-api-lib/res/drawable-xhdpi/ic_action_cancel_launchersize.png b/libs/openpgp-api-lib/res/drawable-xhdpi/ic_action_cancel_launchersize.png Binary files differdeleted file mode 100644 index 1e3571fa..00000000 --- a/libs/openpgp-api-lib/res/drawable-xhdpi/ic_action_cancel_launchersize.png +++ /dev/null diff --git a/libs/openpgp-api-lib/res/drawable-xhdpi/ic_action_cancel_launchersize_light.png b/libs/openpgp-api-lib/res/drawable-xhdpi/ic_action_cancel_launchersize_light.png Binary files differdeleted file mode 100644 index d505046b..00000000 --- a/libs/openpgp-api-lib/res/drawable-xhdpi/ic_action_cancel_launchersize_light.png +++ /dev/null diff --git a/libs/openpgp-api-lib/res/drawable-xxhdpi/ic_action_cancel_launchersize.png b/libs/openpgp-api-lib/res/drawable-xxhdpi/ic_action_cancel_launchersize.png Binary files differdeleted file mode 100644 index 52044601..00000000 --- a/libs/openpgp-api-lib/res/drawable-xxhdpi/ic_action_cancel_launchersize.png +++ /dev/null diff --git a/libs/openpgp-api-lib/res/drawable-xxhdpi/ic_action_cancel_launchersize_light.png b/libs/openpgp-api-lib/res/drawable-xxhdpi/ic_action_cancel_launchersize_light.png Binary files differdeleted file mode 100644 index d6fb86bd..00000000 --- a/libs/openpgp-api-lib/res/drawable-xxhdpi/ic_action_cancel_launchersize_light.png +++ /dev/null diff --git a/libs/openpgp-api-lib/res/values-cs/strings.xml b/libs/openpgp-api-lib/res/values-cs/strings.xml deleted file mode 100644 index c9fe1fab..00000000 --- a/libs/openpgp-api-lib/res/values-cs/strings.xml +++ /dev/null @@ -1,5 +0,0 @@ -<?xml version='1.0' encoding='UTF-8'?> -<resources> - <string name="openpgp_list_preference_none">Žádný</string> - <string name="openpgp_install_openkeychain_via">Instalovat OpenKeychain pomocí %s</string> -</resources> diff --git a/libs/openpgp-api-lib/res/values-de/strings.xml b/libs/openpgp-api-lib/res/values-de/strings.xml deleted file mode 100644 index 91e800ad..00000000 --- a/libs/openpgp-api-lib/res/values-de/strings.xml +++ /dev/null @@ -1,5 +0,0 @@ -<?xml version='1.0' encoding='UTF-8'?> -<resources> - <string name="openpgp_list_preference_none">Keine Auswahl</string> - <string name="openpgp_install_openkeychain_via">Installiere OpenKeychain mit %s</string> -</resources> diff --git a/libs/openpgp-api-lib/res/values-es/strings.xml b/libs/openpgp-api-lib/res/values-es/strings.xml deleted file mode 100644 index da8979b4..00000000 --- a/libs/openpgp-api-lib/res/values-es/strings.xml +++ /dev/null @@ -1,5 +0,0 @@ -<?xml version='1.0' encoding='UTF-8'?> -<resources> - <string name="openpgp_list_preference_none">Ninguno</string> - <string name="openpgp_install_openkeychain_via">Instalar OpenKeychain mediante %s</string> -</resources> diff --git a/libs/openpgp-api-lib/res/values-fr/strings.xml b/libs/openpgp-api-lib/res/values-fr/strings.xml deleted file mode 100644 index 9b36df2d..00000000 --- a/libs/openpgp-api-lib/res/values-fr/strings.xml +++ /dev/null @@ -1,5 +0,0 @@ -<?xml version='1.0' encoding='UTF-8'?> -<resources> - <string name="openpgp_list_preference_none">Aucun</string> - <string name="openpgp_install_openkeychain_via">Installer OpenKeychain par %s</string> -</resources> diff --git a/libs/openpgp-api-lib/res/values-it/strings.xml b/libs/openpgp-api-lib/res/values-it/strings.xml deleted file mode 100644 index 23e8e801..00000000 --- a/libs/openpgp-api-lib/res/values-it/strings.xml +++ /dev/null @@ -1,5 +0,0 @@ -<?xml version='1.0' encoding='UTF-8'?> -<resources> - <string name="openpgp_list_preference_none">Nessuno</string> - <string name="openpgp_install_openkeychain_via">Installa OpenKeychain via %s</string> -</resources> diff --git a/libs/openpgp-api-lib/res/values-ja/strings.xml b/libs/openpgp-api-lib/res/values-ja/strings.xml deleted file mode 100644 index 5e337f5a..00000000 --- a/libs/openpgp-api-lib/res/values-ja/strings.xml +++ /dev/null @@ -1,5 +0,0 @@ -<?xml version='1.0' encoding='UTF-8'?> -<resources> - <string name="openpgp_list_preference_none">無し</string> - <string name="openpgp_install_openkeychain_via">%s 経由でOpenKeychainをインストール</string> -</resources> diff --git a/libs/openpgp-api-lib/res/values-nl/strings.xml b/libs/openpgp-api-lib/res/values-nl/strings.xml deleted file mode 100644 index c757504a..00000000 --- a/libs/openpgp-api-lib/res/values-nl/strings.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version='1.0' encoding='UTF-8'?> -<resources/> diff --git a/libs/openpgp-api-lib/res/values-pl/strings.xml b/libs/openpgp-api-lib/res/values-pl/strings.xml deleted file mode 100644 index c757504a..00000000 --- a/libs/openpgp-api-lib/res/values-pl/strings.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version='1.0' encoding='UTF-8'?> -<resources/> diff --git a/libs/openpgp-api-lib/res/values-pt/strings.xml b/libs/openpgp-api-lib/res/values-pt/strings.xml deleted file mode 100644 index c757504a..00000000 --- a/libs/openpgp-api-lib/res/values-pt/strings.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version='1.0' encoding='UTF-8'?> -<resources/> diff --git a/libs/openpgp-api-lib/res/values-ru/strings.xml b/libs/openpgp-api-lib/res/values-ru/strings.xml deleted file mode 100644 index e8fd1ddf..00000000 --- a/libs/openpgp-api-lib/res/values-ru/strings.xml +++ /dev/null @@ -1,5 +0,0 @@ -<?xml version='1.0' encoding='UTF-8'?> -<resources> - <string name="openpgp_list_preference_none">Нет</string> - <string name="openpgp_install_openkeychain_via">Установить OpenKeychain через %s</string> -</resources> diff --git a/libs/openpgp-api-lib/res/values-sl/strings.xml b/libs/openpgp-api-lib/res/values-sl/strings.xml deleted file mode 100644 index 20bf70b0..00000000 --- a/libs/openpgp-api-lib/res/values-sl/strings.xml +++ /dev/null @@ -1,5 +0,0 @@ -<?xml version='1.0' encoding='UTF-8'?> -<resources> - <string name="openpgp_list_preference_none">Brez</string> - <string name="openpgp_install_openkeychain_via">Namesti OpenKeychain prek %s</string> -</resources> diff --git a/libs/openpgp-api-lib/res/values-tr/strings.xml b/libs/openpgp-api-lib/res/values-tr/strings.xml deleted file mode 100644 index c757504a..00000000 --- a/libs/openpgp-api-lib/res/values-tr/strings.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version='1.0' encoding='UTF-8'?> -<resources/> diff --git a/libs/openpgp-api-lib/res/values-uk/strings.xml b/libs/openpgp-api-lib/res/values-uk/strings.xml deleted file mode 100644 index baf600a9..00000000 --- a/libs/openpgp-api-lib/res/values-uk/strings.xml +++ /dev/null @@ -1,5 +0,0 @@ -<?xml version='1.0' encoding='UTF-8'?> -<resources> - <string name="openpgp_list_preference_none">Жоден</string> - <string name="openpgp_install_openkeychain_via">Встановити OpenKeychain через %s</string> -</resources> diff --git a/libs/openpgp-api-lib/res/values-zh/strings.xml b/libs/openpgp-api-lib/res/values-zh/strings.xml deleted file mode 100644 index c757504a..00000000 --- a/libs/openpgp-api-lib/res/values-zh/strings.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version='1.0' encoding='UTF-8'?> -<resources/> diff --git a/libs/openpgp-api-lib/res/values/strings.xml b/libs/openpgp-api-lib/res/values/strings.xml deleted file mode 100644 index 0119831c..00000000 --- a/libs/openpgp-api-lib/res/values/strings.xml +++ /dev/null @@ -1,7 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<resources> - - <string name="openpgp_list_preference_none">None</string> - <string name="openpgp_install_openkeychain_via">Install OpenKeychain via %s</string> - -</resources>
\ No newline at end of file diff --git a/libs/openpgp-api-lib/src/org/openintents/openpgp/IOpenPgpService.aidl b/libs/openpgp-api-lib/src/org/openintents/openpgp/IOpenPgpService.aidl deleted file mode 100644 index 7ee79d6a..00000000 --- a/libs/openpgp-api-lib/src/org/openintents/openpgp/IOpenPgpService.aidl +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.openintents.openpgp; - -interface IOpenPgpService { - - // see OpenPgpApi for documentation - Intent execute(in Intent data, in ParcelFileDescriptor input, in ParcelFileDescriptor output); - -}
\ No newline at end of file diff --git a/libs/openpgp-api-lib/src/org/openintents/openpgp/OpenPgpError.java b/libs/openpgp-api-lib/src/org/openintents/openpgp/OpenPgpError.java deleted file mode 100644 index b894a460..00000000 --- a/libs/openpgp-api-lib/src/org/openintents/openpgp/OpenPgpError.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.openintents.openpgp; - -import android.os.Parcel; -import android.os.Parcelable; - -/** - * Parcelable versioning has been copied from Dashclock Widget - * https://code.google.com/p/dashclock/source/browse/api/src/main/java/com/google/android/apps/dashclock/api/ExtensionData.java - */ -public class OpenPgpError implements Parcelable { - /** - * Since there might be a case where new versions of the client using the library getting - * old versions of the protocol (and thus old versions of this class), we need a versioning - * system for the parcels sent between the clients and the providers. - */ - public static final int PARCELABLE_VERSION = 1; - - // possible values for errorId - public static final int CLIENT_SIDE_ERROR = -1; - public static final int GENERIC_ERROR = 0; - public static final int INCOMPATIBLE_API_VERSIONS = 1; - public static final int NO_OR_WRONG_PASSPHRASE = 2; - public static final int NO_USER_IDS = 3; - - int errorId; - String message; - - public OpenPgpError() { - } - - public OpenPgpError(int errorId, String message) { - this.errorId = errorId; - this.message = message; - } - - public OpenPgpError(OpenPgpError b) { - this.errorId = b.errorId; - this.message = b.message; - } - - public int getErrorId() { - return errorId; - } - - public void setErrorId(int errorId) { - this.errorId = errorId; - } - - public String getMessage() { - return message; - } - - public void setMessage(String message) { - this.message = message; - } - - public int describeContents() { - return 0; - } - - public void writeToParcel(Parcel dest, int flags) { - /** - * NOTE: When adding fields in the process of updating this API, make sure to bump - * {@link #PARCELABLE_VERSION}. - */ - dest.writeInt(PARCELABLE_VERSION); - // Inject a placeholder that will store the parcel size from this point on - // (not including the size itself). - int sizePosition = dest.dataPosition(); - dest.writeInt(0); - int startPosition = dest.dataPosition(); - // version 1 - dest.writeInt(errorId); - dest.writeString(message); - // Go back and write the size - int parcelableSize = dest.dataPosition() - startPosition; - dest.setDataPosition(sizePosition); - dest.writeInt(parcelableSize); - dest.setDataPosition(startPosition + parcelableSize); - } - - public static final Creator<OpenPgpError> CREATOR = new Creator<OpenPgpError>() { - public OpenPgpError createFromParcel(final Parcel source) { - int parcelableVersion = source.readInt(); - int parcelableSize = source.readInt(); - int startPosition = source.dataPosition(); - - OpenPgpError error = new OpenPgpError(); - error.errorId = source.readInt(); - error.message = source.readString(); - - // skip over all fields added in future versions of this parcel - source.setDataPosition(startPosition + parcelableSize); - - return error; - } - - public OpenPgpError[] newArray(final int size) { - return new OpenPgpError[size]; - } - }; -} diff --git a/libs/openpgp-api-lib/src/org/openintents/openpgp/OpenPgpMetadata.java b/libs/openpgp-api-lib/src/org/openintents/openpgp/OpenPgpMetadata.java deleted file mode 100644 index 2a99e406..00000000 --- a/libs/openpgp-api-lib/src/org/openintents/openpgp/OpenPgpMetadata.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.openintents.openpgp; - -import android.os.Parcel; -import android.os.Parcelable; - -/** - * Parcelable versioning has been copied from Dashclock Widget - * https://code.google.com/p/dashclock/source/browse/api/src/main/java/com/google/android/apps/dashclock/api/ExtensionData.java - */ -public class OpenPgpMetadata implements Parcelable { - /** - * Since there might be a case where new versions of the client using the library getting - * old versions of the protocol (and thus old versions of this class), we need a versioning - * system for the parcels sent between the clients and the providers. - */ - public static final int PARCELABLE_VERSION = 1; - - String filename; - String mimeType; - long modificationTime; - long originalSize; - - public String getFilename() { - return filename; - } - - public String getMimeType() { - return mimeType; - } - - public long getModificationTime() { - return modificationTime; - } - - public long getOriginalSize() { - return originalSize; - } - - public OpenPgpMetadata() { - } - - public OpenPgpMetadata(String filename, String mimeType, long modificationTime, - long originalSize) { - this.filename = filename; - this.mimeType = mimeType; - this.modificationTime = modificationTime; - this.originalSize = originalSize; - } - - public OpenPgpMetadata(OpenPgpMetadata b) { - this.filename = b.filename; - this.mimeType = b.mimeType; - this.modificationTime = b.modificationTime; - this.originalSize = b.originalSize; - } - - public int describeContents() { - return 0; - } - - public void writeToParcel(Parcel dest, int flags) { - /** - * NOTE: When adding fields in the process of updating this API, make sure to bump - * {@link #PARCELABLE_VERSION}. - */ - dest.writeInt(PARCELABLE_VERSION); - // Inject a placeholder that will store the parcel size from this point on - // (not including the size itself). - int sizePosition = dest.dataPosition(); - dest.writeInt(0); - int startPosition = dest.dataPosition(); - // version 1 - dest.writeString(filename); - dest.writeString(mimeType); - dest.writeLong(modificationTime); - dest.writeLong(originalSize); - // Go back and write the size - int parcelableSize = dest.dataPosition() - startPosition; - dest.setDataPosition(sizePosition); - dest.writeInt(parcelableSize); - dest.setDataPosition(startPosition + parcelableSize); - } - - public static final Creator<OpenPgpMetadata> CREATOR = new Creator<OpenPgpMetadata>() { - public OpenPgpMetadata createFromParcel(final Parcel source) { - int parcelableVersion = source.readInt(); - int parcelableSize = source.readInt(); - int startPosition = source.dataPosition(); - - OpenPgpMetadata vr = new OpenPgpMetadata(); - vr.filename = source.readString(); - vr.mimeType = source.readString(); - vr.modificationTime = source.readLong(); - vr.originalSize = source.readLong(); - - // skip over all fields added in future versions of this parcel - source.setDataPosition(startPosition + parcelableSize); - - return vr; - } - - public OpenPgpMetadata[] newArray(final int size) { - return new OpenPgpMetadata[size]; - } - }; - - @Override - public String toString() { - String out = "\nfilename: " + filename; - out += "\nmimeType: " + mimeType; - out += "\nmodificationTime: " + modificationTime; - out += "\noriginalSize: " + originalSize; - return out; - } - -} diff --git a/libs/openpgp-api-lib/src/org/openintents/openpgp/OpenPgpSignatureResult.java b/libs/openpgp-api-lib/src/org/openintents/openpgp/OpenPgpSignatureResult.java deleted file mode 100644 index dbcd74b6..00000000 --- a/libs/openpgp-api-lib/src/org/openintents/openpgp/OpenPgpSignatureResult.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.openintents.openpgp; - -import android.os.Parcel; -import android.os.Parcelable; - -import org.openintents.openpgp.util.OpenPgpUtils; - -import java.util.ArrayList; -import java.util.Locale; - -/** - * Parcelable versioning has been copied from Dashclock Widget - * https://code.google.com/p/dashclock/source/browse/api/src/main/java/com/google/android/apps/dashclock/api/ExtensionData.java - */ -public class OpenPgpSignatureResult implements Parcelable { - /** - * Since there might be a case where new versions of the client using the library getting - * old versions of the protocol (and thus old versions of this class), we need a versioning - * system for the parcels sent between the clients and the providers. - */ - public static final int PARCELABLE_VERSION = 2; - - // generic error on signature verification - public static final int SIGNATURE_ERROR = 0; - // successfully verified signature, with certified key - public static final int SIGNATURE_SUCCESS_CERTIFIED = 1; - // no key was found for this signature verification - public static final int SIGNATURE_KEY_MISSING = 2; - // successfully verified signature, but with uncertified key - public static final int SIGNATURE_SUCCESS_UNCERTIFIED = 3; - // key has been revoked - public static final int SIGNATURE_KEY_REVOKED = 4; - // key is expired - public static final int SIGNATURE_KEY_EXPIRED = 5; - - int status; - boolean signatureOnly; - String primaryUserId; - ArrayList<String> userIds; - long keyId; - - public int getStatus() { - return status; - } - - public void setStatus(int status) { - this.status = status; - } - - public boolean isSignatureOnly() { - return signatureOnly; - } - - public void setSignatureOnly(boolean signatureOnly) { - this.signatureOnly = signatureOnly; - } - - public String getPrimaryUserId() { - return primaryUserId; - } - - public void setPrimaryUserId(String primaryUserId) { - this.primaryUserId = primaryUserId; - } - - public ArrayList<String> getUserIds() { - return userIds; - } - - public void setUserIds(ArrayList<String> userIds) { - this.userIds = userIds; - } - - public long getKeyId() { - return keyId; - } - - public void setKeyId(long keyId) { - this.keyId = keyId; - } - - public OpenPgpSignatureResult() { - - } - - public OpenPgpSignatureResult(int signatureStatus, String signatureUserId, - boolean signatureOnly, long keyId, ArrayList<String> userIds) { - this.status = signatureStatus; - this.signatureOnly = signatureOnly; - this.primaryUserId = signatureUserId; - this.keyId = keyId; - this.userIds = userIds; - } - - public OpenPgpSignatureResult(OpenPgpSignatureResult b) { - this.status = b.status; - this.primaryUserId = b.primaryUserId; - this.signatureOnly = b.signatureOnly; - this.keyId = b.keyId; - this.userIds = b.userIds; - } - - public int describeContents() { - return 0; - } - - public void writeToParcel(Parcel dest, int flags) { - /** - * NOTE: When adding fields in the process of updating this API, make sure to bump - * {@link #PARCELABLE_VERSION}. - */ - dest.writeInt(PARCELABLE_VERSION); - // Inject a placeholder that will store the parcel size from this point on - // (not including the size itself). - int sizePosition = dest.dataPosition(); - dest.writeInt(0); - int startPosition = dest.dataPosition(); - // version 1 - dest.writeInt(status); - dest.writeByte((byte) (signatureOnly ? 1 : 0)); - dest.writeString(primaryUserId); - dest.writeLong(keyId); - // version 2 - dest.writeStringList(userIds); - // Go back and write the size - int parcelableSize = dest.dataPosition() - startPosition; - dest.setDataPosition(sizePosition); - dest.writeInt(parcelableSize); - dest.setDataPosition(startPosition + parcelableSize); - } - - public static final Creator<OpenPgpSignatureResult> CREATOR = new Creator<OpenPgpSignatureResult>() { - public OpenPgpSignatureResult createFromParcel(final Parcel source) { - int parcelableVersion = source.readInt(); - int parcelableSize = source.readInt(); - int startPosition = source.dataPosition(); - - OpenPgpSignatureResult vr = new OpenPgpSignatureResult(); - vr.status = source.readInt(); - vr.signatureOnly = source.readByte() == 1; - vr.primaryUserId = source.readString(); - vr.keyId = source.readLong(); - vr.userIds = new ArrayList<String>(); - source.readStringList(vr.userIds); - - // skip over all fields added in future versions of this parcel - source.setDataPosition(startPosition + parcelableSize); - - return vr; - } - - public OpenPgpSignatureResult[] newArray(final int size) { - return new OpenPgpSignatureResult[size]; - } - }; - - @Override - public String toString() { - String out = "\nstatus: " + status; - out += "\nprimaryUserId: " + primaryUserId; - out += "\nuserIds: " + userIds; - out += "\nsignatureOnly: " + signatureOnly; - out += "\nkeyId: " + OpenPgpUtils.convertKeyIdToHex(keyId); - return out; - } - -} diff --git a/libs/openpgp-api-lib/src/org/openintents/openpgp/util/OpenPgpApi.java b/libs/openpgp-api-lib/src/org/openintents/openpgp/util/OpenPgpApi.java deleted file mode 100644 index 3e18ab0c..00000000 --- a/libs/openpgp-api-lib/src/org/openintents/openpgp/util/OpenPgpApi.java +++ /dev/null @@ -1,306 +0,0 @@ -/* - * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.openintents.openpgp.util; - -import android.annotation.TargetApi; -import android.content.Context; -import android.content.Intent; -import android.os.AsyncTask; -import android.os.Build; -import android.os.ParcelFileDescriptor; -import android.util.Log; - -import org.openintents.openpgp.IOpenPgpService; -import org.openintents.openpgp.OpenPgpError; - -import java.io.InputStream; -import java.io.OutputStream; - -public class OpenPgpApi { - - public static final String TAG = "OpenPgp API"; - - public static final String SERVICE_INTENT = "org.openintents.openpgp.IOpenPgpService"; - - /** - * Version history - * --------------- - * <p/> - * 3: - * - first public stable version - * <p/> - * 4: - * - No changes to existing methods -> backward compatible - * - Introduction of ACTION_DECRYPT_METADATA, RESULT_METADATA, EXTRA_ORIGINAL_FILENAME, and OpenPgpMetadata parcel - * - Introduction of internal NFC extras: EXTRA_NFC_SIGNED_HASH, EXTRA_NFC_SIG_CREATION_TIMESTAMP - * 5: - * - OpenPgpSignatureResult: new consts SIGNATURE_KEY_REVOKED and SIGNATURE_KEY_EXPIRED - * - OpenPgpSignatureResult: ArrayList<String> userIds - */ - public static final int API_VERSION = 5; - - /** - * General extras - * -------------- - * - * required extras: - * int EXTRA_API_VERSION (always required) - * - * returned extras: - * int RESULT_CODE (RESULT_CODE_ERROR, RESULT_CODE_SUCCESS or RESULT_CODE_USER_INTERACTION_REQUIRED) - * OpenPgpError RESULT_ERROR (if RESULT_CODE == RESULT_CODE_ERROR) - * PendingIntent RESULT_INTENT (if RESULT_CODE == RESULT_CODE_USER_INTERACTION_REQUIRED) - */ - - /** - * Sign only - * <p/> - * optional extras: - * boolean EXTRA_REQUEST_ASCII_ARMOR (request ascii armor for output) - * String EXTRA_PASSPHRASE (key passphrase) - */ - public static final String ACTION_SIGN = "org.openintents.openpgp.action.SIGN"; - - /** - * Encrypt - * <p/> - * required extras: - * String[] EXTRA_USER_IDS (=emails of recipients, if more than one key has a user_id, a PendingIntent is returned via RESULT_INTENT) - * or - * long[] EXTRA_KEY_IDS - * <p/> - * optional extras: - * boolean EXTRA_REQUEST_ASCII_ARMOR (request ascii armor for output) - * String EXTRA_PASSPHRASE (key passphrase) - * String EXTRA_ORIGINAL_FILENAME (original filename to be encrypted as metadata) - */ - public static final String ACTION_ENCRYPT = "org.openintents.openpgp.action.ENCRYPT"; - - /** - * Sign and encrypt - * <p/> - * required extras: - * String[] EXTRA_USER_IDS (=emails of recipients, if more than one key has a user_id, a PendingIntent is returned via RESULT_INTENT) - * or - * long[] EXTRA_KEY_IDS - * <p/> - * optional extras: - * boolean EXTRA_REQUEST_ASCII_ARMOR (request ascii armor for output) - * String EXTRA_PASSPHRASE (key passphrase) - * String EXTRA_ORIGINAL_FILENAME (original filename to be encrypted as metadata) - */ - public static final String ACTION_SIGN_AND_ENCRYPT = "org.openintents.openpgp.action.SIGN_AND_ENCRYPT"; - - /** - * Decrypts and verifies given input stream. This methods handles encrypted-only, signed-and-encrypted, - * and also signed-only input. - * <p/> - * If OpenPgpSignatureResult.getStatus() == OpenPgpSignatureResult.SIGNATURE_KEY_MISSING - * in addition a PendingIntent is returned via RESULT_INTENT to download missing keys. - * <p/> - * optional extras: - * boolean EXTRA_REQUEST_ASCII_ARMOR (request ascii armor for output) - * <p/> - * returned extras: - * OpenPgpSignatureResult RESULT_SIGNATURE - * OpenPgpDecryptMetadata RESULT_METADATA - */ - public static final String ACTION_DECRYPT_VERIFY = "org.openintents.openpgp.action.DECRYPT_VERIFY"; - - /** - * Decrypts the header of an encrypted file to retrieve metadata such as original filename. - * <p/> - * This does not decrypt the actual content of the file. - * <p/> - * returned extras: - * OpenPgpDecryptMetadata RESULT_METADATA - */ - public static final String ACTION_DECRYPT_METADATA = "org.openintents.openpgp.action.DECRYPT_METADATA"; - - /** - * Get key ids based on given user ids (=emails) - * <p/> - * required extras: - * String[] EXTRA_USER_IDS - * <p/> - * returned extras: - * long[] RESULT_KEY_IDS - */ - public static final String ACTION_GET_KEY_IDS = "org.openintents.openpgp.action.GET_KEY_IDS"; - - /** - * This action returns RESULT_CODE_SUCCESS if the OpenPGP Provider already has the key - * corresponding to the given key id in its database. - * <p/> - * It returns RESULT_CODE_USER_INTERACTION_REQUIRED if the Provider does not have the key. - * The PendingIntent from RESULT_INTENT can be used to retrieve those from a keyserver. - * <p/> - * required extras: - * long EXTRA_KEY_ID - */ - public static final String ACTION_GET_KEY = "org.openintents.openpgp.action.GET_KEY"; - - /* Intent extras */ - public static final String EXTRA_API_VERSION = "api_version"; - - public static final String EXTRA_ACCOUNT_NAME = "account_name"; - - // SIGN, ENCRYPT, SIGN_AND_ENCRYPT, DECRYPT_VERIFY - // request ASCII Armor for output - // OpenPGP Radix-64, 33 percent overhead compared to binary, see http://tools.ietf.org/html/rfc4880#page-53) - public static final String EXTRA_REQUEST_ASCII_ARMOR = "ascii_armor"; - - // ENCRYPT, SIGN_AND_ENCRYPT - public static final String EXTRA_USER_IDS = "user_ids"; - public static final String EXTRA_KEY_IDS = "key_ids"; - // optional extras: - public static final String EXTRA_PASSPHRASE = "passphrase"; - public static final String EXTRA_ORIGINAL_FILENAME = "original_filename"; - - // internal NFC states - public static final String EXTRA_NFC_SIGNED_HASH = "nfc_signed_hash"; - public static final String EXTRA_NFC_SIG_CREATION_TIMESTAMP = "nfc_sig_creation_timestamp"; - public static final String EXTRA_NFC_DECRYPTED_SESSION_KEY = "nfc_decrypted_session_key"; - - // GET_KEY - public static final String EXTRA_KEY_ID = "key_id"; - public static final String RESULT_KEY_IDS = "key_ids"; - - /* Service Intent returns */ - public static final String RESULT_CODE = "result_code"; - - // get actual error object from RESULT_ERROR - public static final int RESULT_CODE_ERROR = 0; - // success! - public static final int RESULT_CODE_SUCCESS = 1; - // get PendingIntent from RESULT_INTENT, start PendingIntent with startIntentSenderForResult, - // and execute service method again in onActivityResult - public static final int RESULT_CODE_USER_INTERACTION_REQUIRED = 2; - - public static final String RESULT_ERROR = "error"; - public static final String RESULT_INTENT = "intent"; - - // DECRYPT_VERIFY - public static final String RESULT_SIGNATURE = "signature"; - public static final String RESULT_METADATA = "metadata"; - - IOpenPgpService mService; - Context mContext; - - public OpenPgpApi(Context context, IOpenPgpService service) { - this.mContext = context; - this.mService = service; - } - - public interface IOpenPgpCallback { - void onReturn(final Intent result); - } - - private class OpenPgpAsyncTask extends AsyncTask<Void, Integer, Intent> { - Intent data; - InputStream is; - OutputStream os; - IOpenPgpCallback callback; - - private OpenPgpAsyncTask(Intent data, InputStream is, OutputStream os, IOpenPgpCallback callback) { - this.data = data; - this.is = is; - this.os = os; - this.callback = callback; - } - - @Override - protected Intent doInBackground(Void... unused) { - return executeApi(data, is, os); - } - - protected void onPostExecute(Intent result) { - callback.onReturn(result); - } - - } - - @TargetApi(Build.VERSION_CODES.HONEYCOMB) - public void executeApiAsync(Intent data, InputStream is, OutputStream os, IOpenPgpCallback callback) { - OpenPgpAsyncTask task = new OpenPgpAsyncTask(data, is, os, callback); - - // don't serialize async tasks! - // http://commonsware.com/blog/2012/04/20/asynctask-threading-regression-confirmed.html - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null); - } else { - task.execute((Void[]) null); - } - } - - public Intent executeApi(Intent data, InputStream is, OutputStream os) { - try { - data.putExtra(EXTRA_API_VERSION, OpenPgpApi.API_VERSION); - - Intent result; - - // pipe the input and output - ParcelFileDescriptor input = null; - if (is != null) { - input = ParcelFileDescriptorUtil.pipeFrom(is, - new ParcelFileDescriptorUtil.IThreadListener() { - - @Override - public void onThreadFinished(Thread thread) { - //Log.d(OpenPgpApi.TAG, "Copy to service finished"); - } - } - ); - } - ParcelFileDescriptor output = null; - if (os != null) { - output = ParcelFileDescriptorUtil.pipeTo(os, - new ParcelFileDescriptorUtil.IThreadListener() { - - @Override - public void onThreadFinished(Thread thread) { - //Log.d(OpenPgpApi.TAG, "Service finished writing!"); - } - } - ); - } - - // blocks until result is ready - result = mService.execute(data, input, output); - // close() is required to halt the TransferThread - if (output != null) { - output.close(); - } - // TODO: close input? - - // set class loader to current context to allow unparcelling - // of OpenPgpError and OpenPgpSignatureResult - // http://stackoverflow.com/a/3806769 - result.setExtrasClassLoader(mContext.getClassLoader()); - - return result; - } catch (Exception e) { - Log.e(OpenPgpApi.TAG, "Exception in executeApi call", e); - Intent result = new Intent(); - result.putExtra(RESULT_CODE, RESULT_CODE_ERROR); - result.putExtra(RESULT_ERROR, - new OpenPgpError(OpenPgpError.CLIENT_SIDE_ERROR, e.getMessage())); - return result; - } - } - -} diff --git a/libs/openpgp-api-lib/src/org/openintents/openpgp/util/OpenPgpListPreference.java b/libs/openpgp-api-lib/src/org/openintents/openpgp/util/OpenPgpListPreference.java deleted file mode 100644 index cf586462..00000000 --- a/libs/openpgp-api-lib/src/org/openintents/openpgp/util/OpenPgpListPreference.java +++ /dev/null @@ -1,257 +0,0 @@ -/* - * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.openintents.openpgp.util; - -import android.app.AlertDialog.Builder; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.pm.ResolveInfo; -import android.content.res.TypedArray; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.preference.DialogPreference; -import android.util.AttributeSet; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.ListAdapter; -import android.widget.TextView; -import org.openintents.openpgp.R; - -import java.util.ArrayList; -import java.util.List; - -/** - * Does not extend ListPreference, but is very similar to it! - * http://grepcode.com/file_/repository.grepcode.com/java/ext/com.google.android/android/4.4_r1/android/preference/ListPreference.java/?v=source - */ -public class OpenPgpListPreference extends DialogPreference { - private static final String OPENKEYCHAIN_PACKAGE = "org.sufficientlysecure.keychain"; - private static final String MARKET_INTENT_URI_BASE = "market://details?id=%s"; - private static final Intent MARKET_INTENT = new Intent(Intent.ACTION_VIEW, Uri.parse( - String.format(MARKET_INTENT_URI_BASE, OPENKEYCHAIN_PACKAGE))); - - private ArrayList<OpenPgpProviderEntry> mLegacyList = new ArrayList<OpenPgpProviderEntry>(); - private ArrayList<OpenPgpProviderEntry> mList = new ArrayList<OpenPgpProviderEntry>(); - - private String mSelectedPackage; - - public OpenPgpListPreference(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public OpenPgpListPreference(Context context) { - this(context, null); - } - - /** - * Public method to add new entries for legacy applications - * - * @param packageName - * @param simpleName - * @param icon - */ - public void addLegacyProvider(int position, String packageName, String simpleName, Drawable icon) { - mLegacyList.add(position, new OpenPgpProviderEntry(packageName, simpleName, icon)); - } - - @Override - protected void onPrepareDialogBuilder(Builder builder) { - mList.clear(); - - // add "none"-entry - mList.add(0, new OpenPgpProviderEntry("", - getContext().getString(R.string.openpgp_list_preference_none), - getContext().getResources().getDrawable(R.drawable.ic_action_cancel_launchersize))); - - // add all additional (legacy) providers - mList.addAll(mLegacyList); - - // search for OpenPGP providers... - ArrayList<OpenPgpProviderEntry> providerList = new ArrayList<OpenPgpProviderEntry>(); - Intent intent = new Intent(OpenPgpApi.SERVICE_INTENT); - List<ResolveInfo> resInfo = getContext().getPackageManager().queryIntentServices(intent, 0); - if (!resInfo.isEmpty()) { - for (ResolveInfo resolveInfo : resInfo) { - if (resolveInfo.serviceInfo == null) - continue; - - String packageName = resolveInfo.serviceInfo.packageName; - String simpleName = String.valueOf(resolveInfo.serviceInfo.loadLabel(getContext() - .getPackageManager())); - Drawable icon = resolveInfo.serviceInfo.loadIcon(getContext().getPackageManager()); - - providerList.add(new OpenPgpProviderEntry(packageName, simpleName, icon)); - } - } - - if (providerList.isEmpty()) { - // add install links if provider list is empty - resInfo = getContext().getPackageManager().queryIntentActivities - (MARKET_INTENT, 0); - for (ResolveInfo resolveInfo : resInfo) { - Intent marketIntent = new Intent(MARKET_INTENT); - marketIntent.setPackage(resolveInfo.activityInfo.packageName); - Drawable icon = resolveInfo.activityInfo.loadIcon(getContext().getPackageManager()); - String marketName = String.valueOf(resolveInfo.activityInfo.applicationInfo - .loadLabel(getContext().getPackageManager())); - String simpleName = String.format(getContext().getString(R.string - .openpgp_install_openkeychain_via), marketName); - mList.add(new OpenPgpProviderEntry(OPENKEYCHAIN_PACKAGE, simpleName, - icon, marketIntent)); - } - } else { - // add provider - mList.addAll(providerList); - } - - // Init ArrayAdapter with OpenPGP Providers - ListAdapter adapter = new ArrayAdapter<OpenPgpProviderEntry>(getContext(), - android.R.layout.select_dialog_singlechoice, android.R.id.text1, mList) { - public View getView(int position, View convertView, ViewGroup parent) { - // User super class to create the View - View v = super.getView(position, convertView, parent); - TextView tv = (TextView) v.findViewById(android.R.id.text1); - - // Put the image on the TextView - tv.setCompoundDrawablesWithIntrinsicBounds(mList.get(position).icon, null, - null, null); - - // Add margin between image and text (support various screen densities) - int dp10 = (int) (10 * getContext().getResources().getDisplayMetrics().density + 0.5f); - tv.setCompoundDrawablePadding(dp10); - - return v; - } - }; - - builder.setSingleChoiceItems(adapter, getIndexOfProviderList(getValue()), - new DialogInterface.OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - OpenPgpProviderEntry entry = mList.get(which); - - if (entry.intent != null) { - /* - * Intents are called as activity - * - * Current approach is to assume the user installed the app. - * If he does not, the selected package is not valid. - * - * However applications should always consider this could happen, - * as the user might remove the currently used OpenPGP app. - */ - getContext().startActivity(entry.intent); - } - - mSelectedPackage = entry.packageName; - - /* - * Clicking on an item simulates the positive button click, and dismisses - * the dialog. - */ - OpenPgpListPreference.this.onClick(dialog, DialogInterface.BUTTON_POSITIVE); - dialog.dismiss(); - } - }); - - /* - * The typical interaction for list-based dialogs is to have click-on-an-item dismiss the - * dialog instead of the user having to press 'Ok'. - */ - builder.setPositiveButton(null, null); - } - - @Override - protected void onDialogClosed(boolean positiveResult) { - super.onDialogClosed(positiveResult); - - if (positiveResult && (mSelectedPackage != null)) { - if (callChangeListener(mSelectedPackage)) { - setValue(mSelectedPackage); - } - } - } - - private int getIndexOfProviderList(String packageName) { - for (OpenPgpProviderEntry app : mList) { - if (app.packageName.equals(packageName)) { - return mList.indexOf(app); - } - } - - return -1; - } - - public void setValue(String packageName) { - mSelectedPackage = packageName; - persistString(packageName); - } - - public String getValue() { - return mSelectedPackage; - } - - public String getEntry() { - return getEntryByValue(mSelectedPackage); - } - - @Override - protected Object onGetDefaultValue(TypedArray a, int index) { - return a.getString(index); - } - - @Override - protected void onSetInitialValue(boolean restoreValue, Object defaultValue) { - setValue(restoreValue ? getPersistedString(mSelectedPackage) : (String) defaultValue); - } - - public String getEntryByValue(String packageName) { - for (OpenPgpProviderEntry app : mList) { - if (app.packageName.equals(packageName)) { - return app.simpleName; - } - } - - return null; - } - - private static class OpenPgpProviderEntry { - private String packageName; - private String simpleName; - private Drawable icon; - private Intent intent; - - public OpenPgpProviderEntry(String packageName, String simpleName, Drawable icon) { - this.packageName = packageName; - this.simpleName = simpleName; - this.icon = icon; - } - - public OpenPgpProviderEntry(String packageName, String simpleName, Drawable icon, Intent intent) { - this(packageName, simpleName, icon); - this.intent = intent; - } - - @Override - public String toString() { - return simpleName; - } - } -} diff --git a/libs/openpgp-api-lib/src/org/openintents/openpgp/util/OpenPgpServiceConnection.java b/libs/openpgp-api-lib/src/org/openintents/openpgp/util/OpenPgpServiceConnection.java deleted file mode 100644 index 15096d9e..00000000 --- a/libs/openpgp-api-lib/src/org/openintents/openpgp/util/OpenPgpServiceConnection.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.openintents.openpgp.util; - -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.os.IBinder; - -import org.openintents.openpgp.IOpenPgpService; - -public class OpenPgpServiceConnection { - - // callback interface - public interface OnBound { - public void onBound(IOpenPgpService service); - - public void onError(Exception e); - } - - private Context mApplicationContext; - - private IOpenPgpService mService; - private String mProviderPackageName; - - private OnBound mOnBoundListener; - - /** - * Create new connection - * - * @param context - * @param providerPackageName specify package name of OpenPGP provider, - * e.g., "org.sufficientlysecure.keychain" - */ - public OpenPgpServiceConnection(Context context, String providerPackageName) { - this.mApplicationContext = context.getApplicationContext(); - this.mProviderPackageName = providerPackageName; - } - - /** - * Create new connection with callback - * - * @param context - * @param providerPackageName specify package name of OpenPGP provider, - * e.g., "org.sufficientlysecure.keychain" - * @param onBoundListener callback, executed when connection to service has been established - */ - public OpenPgpServiceConnection(Context context, String providerPackageName, - OnBound onBoundListener) { - this(context, providerPackageName); - this.mOnBoundListener = onBoundListener; - } - - public IOpenPgpService getService() { - return mService; - } - - public boolean isBound() { - return (mService != null); - } - - private ServiceConnection mServiceConnection = new ServiceConnection() { - public void onServiceConnected(ComponentName name, IBinder service) { - mService = IOpenPgpService.Stub.asInterface(service); - if (mOnBoundListener != null) { - mOnBoundListener.onBound(mService); - } - } - - public void onServiceDisconnected(ComponentName name) { - mService = null; - } - }; - - /** - * If not already bound, bind to service! - * - * @return - */ - public void bindToService() { - // if not already bound... - if (mService == null) { - try { - Intent serviceIntent = new Intent(OpenPgpApi.SERVICE_INTENT); - // NOTE: setPackage is very important to restrict the intent to this provider only! - serviceIntent.setPackage(mProviderPackageName); - boolean connect = mApplicationContext.bindService(serviceIntent, mServiceConnection, - Context.BIND_AUTO_CREATE); - if (!connect) { - throw new Exception("bindService() returned false!"); - } - } catch (Exception e) { - if (mOnBoundListener != null) { - mOnBoundListener.onError(e); - } - } - } else { - // already bound, but also inform client about it with callback - if (mOnBoundListener != null) { - mOnBoundListener.onBound(mService); - } - } - } - - public void unbindFromService() { - mApplicationContext.unbindService(mServiceConnection); - } - -} diff --git a/libs/openpgp-api-lib/src/org/openintents/openpgp/util/OpenPgpUtils.java b/libs/openpgp-api-lib/src/org/openintents/openpgp/util/OpenPgpUtils.java deleted file mode 100644 index 416b2841..00000000 --- a/libs/openpgp-api-lib/src/org/openintents/openpgp/util/OpenPgpUtils.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.openintents.openpgp.util; - -import java.util.List; -import java.util.Locale; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import android.content.Context; -import android.content.Intent; -import android.content.pm.ResolveInfo; - -public class OpenPgpUtils { - - public static final Pattern PGP_MESSAGE = Pattern.compile( - ".*?(-----BEGIN PGP MESSAGE-----.*?-----END PGP MESSAGE-----).*", - Pattern.DOTALL); - - public static final Pattern PGP_SIGNED_MESSAGE = Pattern.compile( - ".*?(-----BEGIN PGP SIGNED MESSAGE-----.*?-----BEGIN PGP SIGNATURE-----.*?-----END PGP SIGNATURE-----).*", - Pattern.DOTALL); - - public static final int PARSE_RESULT_NO_PGP = -1; - public static final int PARSE_RESULT_MESSAGE = 0; - public static final int PARSE_RESULT_SIGNED_MESSAGE = 1; - - public static int parseMessage(String message) { - Matcher matcherSigned = PGP_SIGNED_MESSAGE.matcher(message); - Matcher matcherMessage = PGP_MESSAGE.matcher(message); - - if (matcherMessage.matches()) { - return PARSE_RESULT_MESSAGE; - } else if (matcherSigned.matches()) { - return PARSE_RESULT_SIGNED_MESSAGE; - } else { - return PARSE_RESULT_NO_PGP; - } - } - - public static boolean isAvailable(Context context) { - Intent intent = new Intent(OpenPgpApi.SERVICE_INTENT); - List<ResolveInfo> resInfo = context.getPackageManager().queryIntentServices(intent, 0); - if (!resInfo.isEmpty()) { - return true; - } else { - return false; - } - } - - public static String convertKeyIdToHex(long keyId) { - return "0x" + convertKeyIdToHex32bit(keyId >> 32) + convertKeyIdToHex32bit(keyId); - } - - private static String convertKeyIdToHex32bit(long keyId) { - String hexString = Long.toHexString(keyId & 0xffffffffL).toLowerCase(Locale.ENGLISH); - while (hexString.length() < 8) { - hexString = "0" + hexString; - } - return hexString; - } -} diff --git a/libs/openpgp-api-lib/src/org/openintents/openpgp/util/ParcelFileDescriptorUtil.java b/libs/openpgp-api-lib/src/org/openintents/openpgp/util/ParcelFileDescriptorUtil.java deleted file mode 100644 index 4fd4b39a..00000000 --- a/libs/openpgp-api-lib/src/org/openintents/openpgp/util/ParcelFileDescriptorUtil.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> - * 2013 Florian Schmaus <flo@geekplace.eu> - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.openintents.openpgp.util; - -import android.os.ParcelFileDescriptor; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -/** - * Partially based on <a href="http://stackoverflow.com/questions/18212152/">Stackoverflow: Transfer InputStream to another Service (across process boundaries)</a> - **/ -public class ParcelFileDescriptorUtil { - - public interface IThreadListener { - void onThreadFinished(final Thread thread); - } - - public static ParcelFileDescriptor pipeFrom(InputStream inputStream, IThreadListener listener) - throws IOException { - ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe(); - ParcelFileDescriptor readSide = pipe[0]; - ParcelFileDescriptor writeSide = pipe[1]; - - // start the transfer thread - new TransferThread(inputStream, new ParcelFileDescriptor.AutoCloseOutputStream(writeSide), - listener) - .start(); - - return readSide; - } - - public static ParcelFileDescriptor pipeTo(OutputStream outputStream, IThreadListener listener) - throws IOException { - ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe(); - ParcelFileDescriptor readSide = pipe[0]; - ParcelFileDescriptor writeSide = pipe[1]; - - // start the transfer thread - new TransferThread(new ParcelFileDescriptor.AutoCloseInputStream(readSide), outputStream, - listener) - .start(); - - return writeSide; - } - - static class TransferThread extends Thread { - final InputStream mIn; - final OutputStream mOut; - final IThreadListener mListener; - - TransferThread(InputStream in, OutputStream out, IThreadListener listener) { - super("ParcelFileDescriptor Transfer Thread"); - mIn = in; - mOut = out; - mListener = listener; - setDaemon(true); - } - - @Override - public void run() { - byte[] buf = new byte[1024]; - int len; - - try { - while ((len = mIn.read(buf)) > 0) { - mOut.write(buf, 0, len); - } - mOut.flush(); // just to be safe - } catch (IOException e) { - //Log.e(OpenPgpApi.TAG, "TransferThread" + getId() + ": writing failed", e); - } finally { - try { - mIn.close(); - } catch (IOException e) { - //Log.e(OpenPgpApi.TAG, "TransferThread" + getId(), e); - } - try { - mOut.close(); - } catch (IOException e) { - //Log.e(OpenPgpApi.TAG, "TransferThread" + getId(), e); - } - } - if (mListener != null) { - //Log.d(OpenPgpApi.TAG, "TransferThread " + getId() + " finished!"); - mListener.onThreadFinished(this); - } - } - } -} diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index be7aac5a..999ec516 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -5,16 +5,21 @@ xmlns:tools="http://schemas.android.com/tools" android:installLocation="auto"> - <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> - <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> - <uses-permission android:name="android.permission.READ_CONTACTS" /> - <uses-permission android:name="android.permission.READ_PROFILE" /> - <uses-permission android:name="android.permission.INTERNET" /> - <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> - <uses-permission android:name="android.permission.WAKE_LOCK" /> - <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> - <uses-permission android:name="android.permission.VIBRATE" /> - <uses-permission android:name="android.permission.NFC" /> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> + <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> + <uses-permission android:name="android.permission.READ_CONTACTS"/> + <uses-permission android:name="android.permission.READ_PROFILE"/> + <uses-permission android:name="android.permission.INTERNET"/> + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> + <uses-permission android:name="android.permission.WAKE_LOCK"/> + <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> + <uses-permission android:name="android.permission.VIBRATE"/> + <uses-permission android:name="android.permission.NFC"/> + <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/> + + <uses-permission + android:name="android.permission.READ_PHONE_STATE" + tools:node="remove"/> <application android:allowBackup="true" @@ -25,11 +30,12 @@ android:name="de.thedevstack.conversationsplus.ConversationsPlusApplication"> <service android:name=".services.XmppConnectionService" /> - <receiver android:name=".services.EventReceiver" > + <receiver android:name=".services.EventReceiver"> <intent-filter> - <action android:name="android.intent.action.BOOT_COMPLETED" /> - <action android:name="android.net.conn.CONNECTIVITY_CHANGE" /> - <action android:name="android.intent.action.ACTION_SHUTDOWN" /> + <action android:name="android.intent.action.BOOT_COMPLETED"/> + <action android:name="android.net.conn.CONNECTIVITY_CHANGE"/> + <action android:name="android.intent.action.ACTION_SHUTDOWN"/> + <action android:name="android.media.RINGER_MODE_CHANGED"/> </intent-filter> </receiver> @@ -37,112 +43,121 @@ android:name=".ui.ConversationActivity" android:label="@string/app_name" android:launchMode="singleTask" - android:windowSoftInputMode="stateHidden" > + android:windowSoftInputMode="stateHidden"> <intent-filter> - <action android:name="android.intent.action.MAIN" /> + <action android:name="android.intent.action.MAIN"/> - <category android:name="android.intent.category.LAUNCHER" /> + <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> <activity android:name=".ui.StartConversationActivity" android:configChanges="orientation|screenSize" - android:label="@string/title_activity_start_conversation" > + android:label="@string/title_activity_start_conversation" + android:launchMode="singleTask"> <intent-filter> - <action android:name="android.intent.action.SENDTO" /> + <action android:name="android.intent.action.SENDTO"/> - <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.DEFAULT"/> - <data android:scheme="imto" /> - <data android:host="jabber" /> + <data android:scheme="imto"/> + <data android:host="jabber"/> </intent-filter> <intent-filter> - <action android:name="android.intent.action.VIEW" /> + <action android:name="android.intent.action.VIEW"/> - <category android:name="android.intent.category.DEFAULT" /> - <category android:name="android.intent.category.BROWSABLE" /> + <category android:name="android.intent.category.DEFAULT"/> + <category android:name="android.intent.category.BROWSABLE"/> - <data android:scheme="xmpp" /> + <data android:scheme="xmpp"/> </intent-filter> <intent-filter> - <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 android:scheme="xmpp" /> + <data android:scheme="xmpp"/> </intent-filter> </activity> <activity android:name=".ui.SettingsActivity" - android:label="@string/title_activity_settings" /> + android:label="@string/title_activity_settings"/> <activity android:name=".ui.ChooseContactActivity" - android:label="@string/title_activity_choose_contact" /> + android:label="@string/title_activity_choose_contact"/> <activity android:name=".ui.BlocklistActivity" - android:label="@string/title_activity_block_list" /> - <activity - android:name=".ui.ChangePasswordActivity" - android:label="@string/change_password_on_server" /> + android:label="@string/title_activity_block_list"/> + <activity + android:name=".ui.ChangePasswordActivity" + android:label="@string/change_password_on_server"/> <activity android:name=".ui.ManageAccountActivity" - android:configChanges="orientation|screenSize" - android:label="@string/title_activity_manage_accounts" /> + android:label="@string/title_activity_manage_accounts" + android:launchMode="singleTask"/> <activity android:name=".ui.EditAccountActivity" - android:windowSoftInputMode="stateHidden|adjustResize" /> + android:launchMode="singleTask" + android:windowSoftInputMode="stateHidden|adjustResize"/> <activity android:name=".ui.ConferenceDetailsActivity" android:label="@string/title_activity_conference_details" - android:windowSoftInputMode="stateHidden" /> + android:windowSoftInputMode="stateHidden"/> <activity android:name=".ui.ContactDetailsActivity" android:label="@string/title_activity_contact_details" - android:windowSoftInputMode="stateHidden" /> + android:windowSoftInputMode="stateHidden"/> <activity android:name=".ui.PublishProfilePictureActivity" android:label="@string/mgmt_account_publish_avatar" - android:windowSoftInputMode="stateHidden" /> + android:windowSoftInputMode="stateHidden"/> <activity android:name=".ui.VerifyOTRActivity" android:label="@string/verify_otr" - android:windowSoftInputMode="stateHidden" /> + android:windowSoftInputMode="stateHidden"/> <activity android:name=".ui.ShareWithActivity" - android:label="@string/app_name" > + android:label="@string/app_name"> <intent-filter> - <action android:name="android.intent.action.SEND" /> + <action android:name="android.intent.action.SEND"/> - <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.DEFAULT"/> - <data android:mimeType="text/plain" /> + <data android:mimeType="text/plain"/> </intent-filter> <intent-filter> - <action android:name="android.intent.action.SEND" /> + <action android:name="android.intent.action.SEND"/> - <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.DEFAULT"/> - <data android:mimeType="*/*" /> + <data android:mimeType="*/*"/> </intent-filter> <intent-filter> - <action android:name="android.intent.action.SEND_MULTIPLE" /> + <action android:name="android.intent.action.SEND_MULTIPLE"/> - <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.DEFAULT"/> - <data android:mimeType="image/*" /> + <data android:mimeType="image/*"/> </intent-filter> + <meta-data + android:name="android.service.chooser.chooser_target_service" + android:value=".services.ContactChooserTargetService" /> </activity> <activity + android:name=".ui.TrustKeysActivity" + android:label="@string/trust_omemo_fingerprints" + android:windowSoftInputMode="stateAlwaysHidden"/> + <activity android:name="de.duenndns.ssl.MemorizingActivity" android:theme="@style/ConversationsTheme" tools:replace="android:theme"/> <activity android:name=".ui.AboutActivity" android:label="@string/title_activity_about" - android:parentActivityName=".ui.SettingsActivity" > + android:parentActivityName=".ui.SettingsActivity"> <meta-data android:name="android.support.PARENT_ACTIVITY" - android:value="eu.siacs.conversations.ui.SettingsActivity" /> + android:value="eu.siacs.conversations.ui.SettingsActivity"/> </activity> <activity android:name="de.thedevstack.conversationsplus.ui.LogCatOutputActivity" @@ -152,6 +167,14 @@ android:name="android.support.PARENT_ACTIVITY" android:value="de.thedevstack.conversationsplus.ui.SettingsActivity" /> </activity> + <activity android:name="com.soundcloud.android.crop.CropImageActivity" /> + <service android:name=".services.ExportLogsService"/> + <service android:name=".services.ContactChooserTargetService" + android:permission="android.permission.BIND_CHOOSER_TARGET_SERVICE"> + <intent-filter> + <action android:name="android.service.chooser.ChooserTargetService" /> + </intent-filter> + </service> </application> </manifest> diff --git a/src/main/java/eu/siacs/conversations/Config.java b/src/main/java/eu/siacs/conversations/Config.java index 20c793e8..2ba1177e 100644 --- a/src/main/java/eu/siacs/conversations/Config.java +++ b/src/main/java/eu/siacs/conversations/Config.java @@ -8,17 +8,37 @@ public final class Config { public static final String LOGTAG = "conversations"; + + public static final String DOMAIN_LOCK = null; //only allow account creation for this domain + public static final boolean DISALLOW_REGISTRATION_IN_UI = false; //hide the register checkbox + public static final boolean HIDE_PGP_IN_UI = false; //some more consumer focused clients might want to disable OpenPGP + public static final boolean FORCE_E2E_ENCRYPTION = false; //disables ability to send unencrypted 1-on-1 + public static final boolean ALLOW_NON_TLS_CONNECTIONS = false; //very dangerous. you should have a good reason to set this to true + public static final boolean FORCE_ORBOT = false; // always use TOR + public static final boolean HIDE_MESSAGE_TEXT_IN_NOTIFICATION = false; + public static final boolean SHOW_CONNECTED_ACCOUNTS = false; //show number of connected accounts in foreground notification + + public static final boolean ALWAYS_NOTIFY_BY_DEFAULT = false; + + public static final boolean LEGACY_NAMESPACE_HTTP_UPLOAD = false; + public static final int PING_MAX_INTERVAL = 300; public static final int PING_MIN_INTERVAL = 30; - public static final int PING_TIMEOUT = 10; + public static final int PING_TIMEOUT = 15; public static final int SOCKET_TIMEOUT = 15; public static final int CONNECT_TIMEOUT = 90; - public static final int CARBON_GRACE_PERIOD = 60; + public static final int CONNECT_DISCO_TIMEOUT = 20; + public static final int CARBON_GRACE_PERIOD = 90; public static final int MINI_GRACE_PERIOD = 750; public static final int AVATAR_SIZE = 192; public static final Bitmap.CompressFormat AVATAR_FORMAT = Bitmap.CompressFormat.PNG; + public static final int IMAGE_SIZE = 1920; + public static final Bitmap.CompressFormat IMAGE_FORMAT = Bitmap.CompressFormat.JPEG; + public static final int IMAGE_QUALITY = 75; + public static final int IMAGE_MAX_SIZE = 524288; //512KiB + public static final int MESSAGE_MERGE_WINDOW = 20; public static final boolean UTF8_EMOTICONS = false; @@ -29,13 +49,22 @@ public final class Config { public static final int PROGRESS_UI_UPDATE_INTERVAL = 750; public static final int REFRESH_UI_INTERVAL = 500; - public static final boolean NO_PROXY_LOOKUP = false; //useful to debug ibb + public static final boolean DISABLE_PROXY_LOOKUP = false; //useful to debug ibb + public static final boolean DISABLE_HTTP_UPLOAD = false; public static final boolean DISABLE_STRING_PREP = false; // setting to true might increase startup performance - public static final boolean EXTENDED_SM_LOGGING = true; // log stanza counts + public static final boolean EXTENDED_SM_LOGGING = false; // log stanza counts public static final boolean RESET_ATTEMPT_COUNT_ON_NETWORK_CHANGE = true; //setting to true might increase power consumption public static final boolean ENCRYPT_ON_HTTP_UPLOADED = false; + public static final boolean REPORT_WRONG_FILESIZE_IN_OTR_JINGLE = true; + + public static final boolean SHOW_REGENERATE_AXOLOTL_KEYS_BUTTON = false; + + public static final boolean X509_VERIFICATION = false; //use x509 certificates to verify OMEMO keys + + public static final boolean IGNORE_ID_REWRITE_IN_MUC = true; + public static final long MILLISECONDS_IN_DAY = 24 * 60 * 60 * 1000; public static final long MAM_MAX_CATCHUP = MILLISECONDS_IN_DAY / 2; public static final int MAM_MAX_MESSAGES = 500; diff --git a/src/main/java/eu/siacs/conversations/crypto/OtrService.java b/src/main/java/eu/siacs/conversations/crypto/OtrService.java index 73ed06e8..8a6bfc44 100644 --- a/src/main/java/eu/siacs/conversations/crypto/OtrService.java +++ b/src/main/java/eu/siacs/conversations/crypto/OtrService.java @@ -1,5 +1,20 @@ package eu.siacs.conversations.crypto; +import android.util.Log; + +import net.java.otr4j.OtrEngineHost; +import net.java.otr4j.OtrException; +import net.java.otr4j.OtrPolicy; +import net.java.otr4j.OtrPolicyImpl; +import net.java.otr4j.crypto.OtrCryptoEngineImpl; +import net.java.otr4j.crypto.OtrCryptoException; +import net.java.otr4j.session.FragmenterInstructions; +import net.java.otr4j.session.InstanceTag; +import net.java.otr4j.session.SessionID; + +import org.json.JSONException; +import org.json.JSONObject; + import java.math.BigInteger; import java.security.KeyFactory; import java.security.KeyPair; @@ -20,6 +35,7 @@ import eu.siacs.conversations.Config; import de.thedevstack.conversationsplus.ConversationsPlusPreferences; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Conversation; +import eu.siacs.conversations.generator.MessageGenerator; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.xmpp.chatstate.ChatState; @@ -27,16 +43,6 @@ import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; import eu.siacs.conversations.xmpp.stanzas.MessagePacket; -import net.java.otr4j.OtrEngineHost; -import net.java.otr4j.OtrException; -import net.java.otr4j.OtrPolicy; -import net.java.otr4j.OtrPolicyImpl; -import net.java.otr4j.crypto.OtrCryptoEngineImpl; -import net.java.otr4j.crypto.OtrCryptoException; -import net.java.otr4j.session.InstanceTag; -import net.java.otr4j.session.SessionID; -import net.java.otr4j.session.FragmenterInstructions; - public class OtrService extends OtrCryptoEngineImpl implements OtrEngineHost { private Account account; @@ -181,10 +187,7 @@ public class OtrService extends OtrCryptoEngineImpl implements OtrEngineHost { packet.setAttribute("to", session.getAccountID() + "/" + session.getUserID()); } packet.setBody(body); - packet.addChild("private", "urn:xmpp:carbons:2"); - packet.addChild("no-copy", "urn:xmpp:hints"); - packet.addChild("no-permanent-store", "urn:xmpp:hints"); - packet.addChild("no-permanent-storage", "urn:xmpp:hints"); + MessageGenerator.addMessageHints(packet); try { Jid jid = Jid.fromSessionID(session); Conversation conversation = mXmppConnectionService.find(account,jid); diff --git a/src/main/java/eu/siacs/conversations/crypto/PgpDecryptionService.java b/src/main/java/eu/siacs/conversations/crypto/PgpDecryptionService.java new file mode 100644 index 00000000..ed67dc65 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/crypto/PgpDecryptionService.java @@ -0,0 +1,162 @@ +package eu.siacs.conversations.crypto; + +import android.app.PendingIntent; + +import eu.siacs.conversations.entities.Message; +import eu.siacs.conversations.services.XmppConnectionService; +import eu.siacs.conversations.ui.UiCallback; + +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; + +public class PgpDecryptionService { + + private final XmppConnectionService xmppConnectionService; + private final ConcurrentHashMap<String, List<Message>> messages = new ConcurrentHashMap<>(); + private final ConcurrentHashMap<String, Boolean> decryptingMessages = new ConcurrentHashMap<>(); + private Boolean keychainLocked = false; + private final Object keychainLockedLock = new Object(); + + public PgpDecryptionService(XmppConnectionService xmppConnectionService) { + this.xmppConnectionService = xmppConnectionService; + } + + public void add(Message message) { + if (isRunning()) { + decryptDirectly(message); + } else { + store(message); + } + } + + public void addAll(List<Message> messagesList) { + if (!messagesList.isEmpty()) { + String conversationUuid = messagesList.get(0).getConversation().getUuid(); + if (!messages.containsKey(conversationUuid)) { + List<Message> list = Collections.synchronizedList(new LinkedList<Message>()); + messages.put(conversationUuid, list); + } + synchronized (messages.get(conversationUuid)) { + messages.get(conversationUuid).addAll(messagesList); + } + decryptAllMessages(); + } + } + + public void onKeychainUnlocked() { + synchronized (keychainLockedLock) { + keychainLocked = false; + } + decryptAllMessages(); + } + + public void onKeychainLocked() { + synchronized (keychainLockedLock) { + keychainLocked = true; + } + xmppConnectionService.updateConversationUi(); + } + + public void onOpenPgpServiceBound() { + decryptAllMessages(); + } + + public boolean isRunning() { + synchronized (keychainLockedLock) { + return !keychainLocked; + } + } + + private void store(Message message) { + if (messages.containsKey(message.getConversation().getUuid())) { + messages.get(message.getConversation().getUuid()).add(message); + } else { + List<Message> messageList = Collections.synchronizedList(new LinkedList<Message>()); + messageList.add(message); + messages.put(message.getConversation().getUuid(), messageList); + } + } + + private void decryptAllMessages() { + for (String uuid : messages.keySet()) { + decryptMessages(uuid); + } + } + + private void decryptMessages(final String uuid) { + synchronized (decryptingMessages) { + Boolean decrypting = decryptingMessages.get(uuid); + if ((decrypting != null && !decrypting) || decrypting == null) { + decryptingMessages.put(uuid, true); + decryptMessage(uuid); + } + } + } + + private void decryptMessage(final String uuid) { + Message message = null; + synchronized (messages.get(uuid)) { + while (!messages.get(uuid).isEmpty()) { + if (messages.get(uuid).get(0).getEncryption() == Message.ENCRYPTION_PGP) { + if (isRunning()) { + message = messages.get(uuid).remove(0); + } + break; + } else { + messages.get(uuid).remove(0); + } + } + if (message != null && xmppConnectionService.getPgpEngine() != null) { + xmppConnectionService.getPgpEngine().decrypt(message, new UiCallback<Message>() { + + @Override + public void userInputRequried(PendingIntent pi, Message message) { + messages.get(uuid).add(0, message); + decryptingMessages.put(uuid, false); + } + + @Override + public void success(Message message) { + xmppConnectionService.updateConversationUi(); + decryptMessage(uuid); + } + + @Override + public void error(int error, Message message) { + message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED); + xmppConnectionService.updateConversationUi(); + decryptMessage(uuid); + } + }); + } else { + decryptingMessages.put(uuid, false); + } + } + } + + private void decryptDirectly(final Message message) { + if (message.getEncryption() == Message.ENCRYPTION_PGP && xmppConnectionService.getPgpEngine() != null) { + xmppConnectionService.getPgpEngine().decrypt(message, new UiCallback<Message>() { + + @Override + public void userInputRequried(PendingIntent pi, Message message) { + store(message); + } + + @Override + public void success(Message message) { + xmppConnectionService.updateConversationUi(); + xmppConnectionService.getNotificationService().updateNotification(false); + } + + @Override + public void error(int error, Message message) { + message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED); + xmppConnectionService.updateConversationUi(); + } + }); + } + } +} diff --git a/src/main/java/eu/siacs/conversations/crypto/PgpEngine.java b/src/main/java/eu/siacs/conversations/crypto/PgpEngine.java index 4d422801..1f889cd8 100644 --- a/src/main/java/eu/siacs/conversations/crypto/PgpEngine.java +++ b/src/main/java/eu/siacs/conversations/crypto/PgpEngine.java @@ -1,5 +1,13 @@ package eu.siacs.conversations.crypto; +import android.app.PendingIntent; +import android.content.Intent; +import android.net.Uri; + +import org.openintents.openpgp.OpenPgpSignatureResult; +import org.openintents.openpgp.util.OpenPgpApi; +import org.openintents.openpgp.util.OpenPgpApi.IOpenPgpCallback; + import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.FileInputStream; @@ -9,10 +17,6 @@ import java.io.InputStream; import java.io.OutputStream; import java.net.URL; -import org.openintents.openpgp.OpenPgpSignatureResult; -import org.openintents.openpgp.util.OpenPgpApi; -import org.openintents.openpgp.util.OpenPgpApi.IOpenPgpCallback; - import de.thedevstack.conversationsplus.ConversationsPlusPreferences; import de.thedevstack.conversationsplus.utils.MessageUtil; @@ -27,10 +31,6 @@ import eu.siacs.conversations.persistance.FileBackend; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.ui.UiCallback; -import android.app.PendingIntent; -import android.content.Intent; -import android.net.Uri; - public class PgpEngine { private OpenPgpApi api; private XmppConnectionService mXmppConnectionService; @@ -44,8 +44,6 @@ public class PgpEngine { final UiCallback<Message> callback) { Intent params = new Intent(); params.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY); - params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, message - .getConversation().getAccount().getJid().toBareJid().toString()); if (message.getType() == Message.TYPE_TEXT) { InputStream is = new ByteArrayInputStream(message.getBody() .getBytes()); @@ -54,6 +52,7 @@ public class PgpEngine { @Override public void onReturn(Intent result) { + notifyPgpDecryptionService(message.getConversation().getAccount(), OpenPgpApi.ACTION_DECRYPT_VERIFY, result); switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) { case OpenPgpApi.RESULT_CODE_SUCCESS: @@ -70,6 +69,7 @@ public class PgpEngine { && ConversationsPlusPreferences.autoAcceptFileSize() > 0) { manager.createNewDownloadConnection(message); } + mXmppConnectionService.updateMessage(message); callback.success(message); } } catch (IOException e) { @@ -90,8 +90,10 @@ public class PgpEngine { }); } else if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) { try { - final DownloadableFile inputFile = FileBackend.getFile(message, false); - final DownloadableFile outputFile = FileBackend.getFile(message, true); + final DownloadableFile inputFile = this.mXmppConnectionService + .getFileBackend().getFile(message, false); + final DownloadableFile outputFile = this.mXmppConnectionService + .getFileBackend().getFile(message, true); outputFile.getParentFile().mkdirs(); outputFile.createNewFile(); InputStream is = new FileInputStream(inputFile); @@ -100,6 +102,7 @@ public class PgpEngine { @Override public void onReturn(Intent result) { + notifyPgpDecryptionService(message.getConversation().getAccount(), OpenPgpApi.ACTION_DECRYPT_VERIFY, result); switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) { case OpenPgpApi.RESULT_CODE_SUCCESS: @@ -132,21 +135,19 @@ public class PgpEngine { } } - public void encrypt(final Message message, - final UiCallback<Message> callback) { - + public void encrypt(final Message message, final UiCallback<Message> callback) { Intent params = new Intent(); params.setAction(OpenPgpApi.ACTION_ENCRYPT); - if (message.getConversation().getMode() == Conversation.MODE_SINGLE) { - long[] keys = { message.getConversation().getContact() - .getPgpKeyId() }; + final Conversation conversation = message.getConversation(); + if (conversation.getMode() == Conversation.MODE_SINGLE) { + long[] keys = { + conversation.getContact().getPgpKeyId(), + conversation.getAccount().getPgpId() + }; params.putExtra(OpenPgpApi.EXTRA_KEY_IDS, keys); } else { - params.putExtra(OpenPgpApi.EXTRA_KEY_IDS, message.getConversation() - .getMucOptions().getPgpKeyIds()); + params.putExtra(OpenPgpApi.EXTRA_KEY_IDS, conversation.getMucOptions().getPgpKeyIds()); } - params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, message - .getConversation().getAccount().getJid().toBareJid().toString()); if (!message.needsUploading()) { params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true); @@ -162,6 +163,7 @@ public class PgpEngine { @Override public void onReturn(Intent result) { + notifyPgpDecryptionService(message.getConversation().getAccount(), OpenPgpApi.ACTION_ENCRYPT, result); switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) { case OpenPgpApi.RESULT_CODE_SUCCESS: @@ -195,19 +197,28 @@ public class PgpEngine { }); } else { try { - DownloadableFile inputFile = FileBackend.getFile(message, true); - DownloadableFile outputFile = FileBackend.getFile(message, false); + DownloadableFile inputFile = this.mXmppConnectionService + .getFileBackend().getFile(message, true); + DownloadableFile outputFile = this.mXmppConnectionService + .getFileBackend().getFile(message, false); outputFile.getParentFile().mkdirs(); outputFile.createNewFile(); - InputStream is = new FileInputStream(inputFile); - OutputStream os = new FileOutputStream(outputFile); + final InputStream is = new FileInputStream(inputFile); + final OutputStream os = new FileOutputStream(outputFile); api.executeApiAsync(params, is, os, new IOpenPgpCallback() { @Override public void onReturn(Intent result) { + notifyPgpDecryptionService(message.getConversation().getAccount(), OpenPgpApi.ACTION_ENCRYPT, result); switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) { case OpenPgpApi.RESULT_CODE_SUCCESS: + try { + os.flush(); + } catch (IOException ignored) { + //ignored + } + FileBackend.close(os); callback.success(message); break; case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: @@ -250,10 +261,10 @@ public class PgpEngine { Intent params = new Intent(); params.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY); params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true); - params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, account.getJid().toBareJid().toString()); InputStream is = new ByteArrayInputStream(pgpSig.toString().getBytes()); ByteArrayOutputStream os = new ByteArrayOutputStream(); Intent result = api.executeApi(params, is, os); + notifyPgpDecryptionService(account, OpenPgpApi.ACTION_DECRYPT_VERIFY, result); switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) { case OpenPgpApi.RESULT_CODE_SUCCESS: @@ -272,18 +283,45 @@ public class PgpEngine { return 0; } + public void chooseKey(final Account account, final UiCallback<Account> callback) { + Intent p = new Intent(); + p.setAction(OpenPgpApi.ACTION_GET_SIGN_KEY_ID); + api.executeApiAsync(p, null, null, new IOpenPgpCallback() { + + @Override + public void onReturn(Intent result) { + switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) { + case OpenPgpApi.RESULT_CODE_SUCCESS: + callback.success(account); + return; + case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: + callback.userInputRequried((PendingIntent) result + .getParcelableExtra(OpenPgpApi.RESULT_INTENT), + account); + return; + case OpenPgpApi.RESULT_CODE_ERROR: + callback.error(R.string.openpgp_error, account); + } + } + }); + } + public void generateSignature(final Account account, String status, final UiCallback<Account> callback) { + if (account.getPgpId() == -1) { + return; + } Intent params = new Intent(); + params.setAction(OpenPgpApi.ACTION_CLEARTEXT_SIGN); params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true); - params.setAction(OpenPgpApi.ACTION_SIGN); - params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, account.getJid().toBareJid().toString()); + params.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, account.getPgpId()); InputStream is = new ByteArrayInputStream(status.getBytes()); final OutputStream os = new ByteArrayOutputStream(); api.executeApiAsync(params, is, os, new IOpenPgpCallback() { @Override public void onReturn(Intent result) { + notifyPgpDecryptionService(account, OpenPgpApi.ACTION_SIGN, result); switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) { case OpenPgpApi.RESULT_CODE_SUCCESS: StringBuilder signatureBuilder = new StringBuilder(); @@ -309,7 +347,7 @@ public class PgpEngine { callback.error(R.string.openpgp_error, account); return; } - account.setKey("pgp_signature", signatureBuilder.toString()); + account.setPgpSignature(signatureBuilder.toString()); callback.success(account); return; case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: @@ -328,8 +366,6 @@ public class PgpEngine { Intent params = new Intent(); params.setAction(OpenPgpApi.ACTION_GET_KEY); params.putExtra(OpenPgpApi.EXTRA_KEY_ID, contact.getPgpKeyId()); - params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, contact.getAccount() - .getJid().toBareJid().toString()); api.executeApiAsync(params, null, null, new IOpenPgpCallback() { @Override @@ -354,8 +390,6 @@ public class PgpEngine { Intent params = new Intent(); params.setAction(OpenPgpApi.ACTION_GET_KEY); params.putExtra(OpenPgpApi.EXTRA_KEY_ID, contact.getPgpKeyId()); - params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, contact.getAccount() - .getJid().toBareJid().toString()); Intent result = api.executeApi(params, null, null); return (PendingIntent) result .getParcelableExtra(OpenPgpApi.RESULT_INTENT); @@ -365,9 +399,21 @@ public class PgpEngine { Intent params = new Intent(); params.setAction(OpenPgpApi.ACTION_GET_KEY); params.putExtra(OpenPgpApi.EXTRA_KEY_ID, pgpKeyId); - params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, account.getJid().toBareJid().toString()); Intent result = api.executeApi(params, null, null); return (PendingIntent) result .getParcelableExtra(OpenPgpApi.RESULT_INTENT); } + + private void notifyPgpDecryptionService(Account account, String action, final Intent result) { + switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) { + case OpenPgpApi.RESULT_CODE_SUCCESS: + if (OpenPgpApi.ACTION_SIGN.equals(action)) { + account.getPgpDecryptionService().onKeychainUnlocked(); + } + break; + case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: + account.getPgpDecryptionService().onKeychainLocked(); + break; + } + } } diff --git a/src/main/java/eu/siacs/conversations/crypto/XmppDomainVerifier.java b/src/main/java/eu/siacs/conversations/crypto/XmppDomainVerifier.java new file mode 100644 index 00000000..1fca865e --- /dev/null +++ b/src/main/java/eu/siacs/conversations/crypto/XmppDomainVerifier.java @@ -0,0 +1,127 @@ +package eu.siacs.conversations.crypto; + +import android.util.Log; +import android.util.Pair; + +import org.bouncycastle.asn1.ASN1Primitive; +import org.bouncycastle.asn1.DERIA5String; +import org.bouncycastle.asn1.DERTaggedObject; +import org.bouncycastle.asn1.DERUTF8String; +import org.bouncycastle.asn1.DLSequence; +import org.bouncycastle.asn1.x500.RDN; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x500.style.BCStyle; +import org.bouncycastle.asn1.x500.style.IETFUtils; +import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder; + +import java.io.IOException; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLSession; + +public class XmppDomainVerifier implements HostnameVerifier { + + private static final String LOGTAG = "XmppDomainVerifier"; + + private final String SRVName = "1.3.6.1.5.5.7.8.7"; + private final String xmppAddr = "1.3.6.1.5.5.7.8.5"; + + @Override + public boolean verify(String domain, SSLSession sslSession) { + try { + Certificate[] chain = sslSession.getPeerCertificates(); + if (chain.length == 0 || !(chain[0] instanceof X509Certificate)) { + return false; + } + X509Certificate certificate = (X509Certificate) chain[0]; + Collection<List<?>> alternativeNames = certificate.getSubjectAlternativeNames(); + List<String> xmppAddrs = new ArrayList<>(); + List<String> srvNames = new ArrayList<>(); + List<String> domains = new ArrayList<>(); + if (alternativeNames != null) { + for (List<?> san : alternativeNames) { + Integer type = (Integer) san.get(0); + if (type == 0) { + Pair<String, String> otherName = parseOtherName((byte[]) san.get(1)); + if (otherName != null) { + switch (otherName.first) { + case SRVName: + srvNames.add(otherName.second); + break; + case xmppAddr: + xmppAddrs.add(otherName.second); + break; + default: + Log.d(LOGTAG, "oid: " + otherName.first + " value: " + otherName.second); + } + } + } else if (type == 2) { + Object value = san.get(1); + if (value instanceof String) { + domains.add((String) value); + } + } + } + } + if (srvNames.size() == 0 && xmppAddrs.size() == 0 && domains.size() == 0) { + X500Name x500name = new JcaX509CertificateHolder(certificate).getSubject(); + RDN[] rdns = x500name.getRDNs(BCStyle.CN); + for (int i = 0; i < rdns.length; ++i) { + domains.add(IETFUtils.valueToString(x500name.getRDNs(BCStyle.CN)[i].getFirst().getValue())); + } + } + Log.d(LOGTAG, "searching for " + domain + " in srvNames: " + srvNames + " xmppAddrs: " + xmppAddrs + " domains:" + domains); + return xmppAddrs.contains(domain) || srvNames.contains("_xmpp-client." + domain) || matchDomain(domain, domains); + } catch (Exception e) { + return false; + } + } + + private static Pair<String, String> parseOtherName(byte[] otherName) { + try { + ASN1Primitive asn1Primitive = ASN1Primitive.fromByteArray(otherName); + if (asn1Primitive instanceof DERTaggedObject) { + ASN1Primitive inner = ((DERTaggedObject) asn1Primitive).getObject(); + if (inner instanceof DLSequence) { + DLSequence sequence = (DLSequence) inner; + if (sequence.size() >= 2 && sequence.getObjectAt(1) instanceof DERTaggedObject) { + String oid = sequence.getObjectAt(0).toString(); + ASN1Primitive value = ((DERTaggedObject) sequence.getObjectAt(1)).getObject(); + if (value instanceof DERUTF8String) { + return new Pair<>(oid, ((DERUTF8String) value).getString()); + } else if (value instanceof DERIA5String) { + return new Pair<>(oid, ((DERIA5String) value).getString()); + } + } + } + } + return null; + } catch (IOException e) { + return null; + } + } + + private static boolean matchDomain(String needle, List<String> haystack) { + for (String entry : haystack) { + if (entry.startsWith("*.")) { + int i = needle.indexOf('.'); + Log.d(LOGTAG, "comparing " + needle.substring(i) + " and " + entry.substring(1)); + if (i != -1 && needle.substring(i).equals(entry.substring(1))) { + Log.d(LOGTAG, "domain " + needle + " matched " + entry); + return true; + } + } else { + if (entry.equals(needle)) { + Log.d(LOGTAG, "domain " + needle + " matched " + entry); + return true; + } + } + } + return false; + } +} diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java new file mode 100644 index 00000000..43a90010 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -0,0 +1,979 @@ +package eu.siacs.conversations.crypto.axolotl; + +import android.security.KeyChain; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.Log; +import android.util.Pair; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.whispersystems.libaxolotl.AxolotlAddress; +import org.whispersystems.libaxolotl.IdentityKey; +import org.whispersystems.libaxolotl.IdentityKeyPair; +import org.whispersystems.libaxolotl.InvalidKeyException; +import org.whispersystems.libaxolotl.InvalidKeyIdException; +import org.whispersystems.libaxolotl.SessionBuilder; +import org.whispersystems.libaxolotl.UntrustedIdentityException; +import org.whispersystems.libaxolotl.ecc.ECPublicKey; +import org.whispersystems.libaxolotl.state.PreKeyBundle; +import org.whispersystems.libaxolotl.state.PreKeyRecord; +import org.whispersystems.libaxolotl.state.SignedPreKeyRecord; +import org.whispersystems.libaxolotl.util.KeyHelper; + +import java.security.PrivateKey; +import java.security.Security; +import java.security.Signature; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Set; + +import eu.siacs.conversations.Config; +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.entities.Contact; +import eu.siacs.conversations.entities.Conversation; +import eu.siacs.conversations.entities.Message; +import eu.siacs.conversations.parser.IqParser; +import eu.siacs.conversations.services.XmppConnectionService; +import eu.siacs.conversations.utils.SerialSingleThreadExecutor; +import eu.siacs.conversations.xml.Element; +import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded; +import eu.siacs.conversations.xmpp.OnIqPacketReceived; +import eu.siacs.conversations.xmpp.jid.InvalidJidException; +import eu.siacs.conversations.xmpp.jid.Jid; +import eu.siacs.conversations.xmpp.stanzas.IqPacket; + +public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { + + public static final String PEP_PREFIX = "eu.siacs.conversations.axolotl"; + public static final String PEP_DEVICE_LIST = PEP_PREFIX + ".devicelist"; + public static final String PEP_BUNDLES = PEP_PREFIX + ".bundles"; + public static final String PEP_VERIFICATION = PEP_PREFIX + ".verification"; + + public static final String LOGPREFIX = "AxolotlService"; + + public static final int NUM_KEYS_TO_PUBLISH = 100; + public static final int publishTriesThreshold = 3; + + private final Account account; + private final XmppConnectionService mXmppConnectionService; + private final SQLiteAxolotlStore axolotlStore; + private final SessionMap sessions; + private final Map<Jid, Set<Integer>> deviceIds; + private final Map<String, XmppAxolotlMessage> messageCache; + private final FetchStatusMap fetchStatusMap; + private final SerialSingleThreadExecutor executor; + private int numPublishTriesOnEmptyPep = 0; + private boolean pepBroken = false; + + @Override + public void onAdvancedStreamFeaturesAvailable(Account account) { + if (account.getXmppConnection() != null && account.getXmppConnection().getFeatures().pep()) { + publishBundlesIfNeeded(true, false); + } else { + Log.d(Config.LOGTAG,account.getJid().toBareJid()+": skipping OMEMO initialization"); + } + } + + public boolean fetchMapHasErrors(Contact contact) { + Jid jid = contact.getJid().toBareJid(); + if (deviceIds.get(jid) != null) { + for (Integer foreignId : this.deviceIds.get(jid)) { + AxolotlAddress address = new AxolotlAddress(jid.toString(), foreignId); + if (fetchStatusMap.getAll(address).containsValue(FetchStatus.ERROR)) { + return true; + } + } + } + return false; + } + + private static class AxolotlAddressMap<T> { + protected Map<String, Map<Integer, T>> map; + protected final Object MAP_LOCK = new Object(); + + public AxolotlAddressMap() { + this.map = new HashMap<>(); + } + + public void put(AxolotlAddress address, T value) { + synchronized (MAP_LOCK) { + Map<Integer, T> devices = map.get(address.getName()); + if (devices == null) { + devices = new HashMap<>(); + map.put(address.getName(), devices); + } + devices.put(address.getDeviceId(), value); + } + } + + public T get(AxolotlAddress address) { + synchronized (MAP_LOCK) { + Map<Integer, T> devices = map.get(address.getName()); + if (devices == null) { + return null; + } + return devices.get(address.getDeviceId()); + } + } + + public Map<Integer, T> getAll(AxolotlAddress address) { + synchronized (MAP_LOCK) { + Map<Integer, T> devices = map.get(address.getName()); + if (devices == null) { + return new HashMap<>(); + } + return devices; + } + } + + public boolean hasAny(AxolotlAddress address) { + synchronized (MAP_LOCK) { + Map<Integer, T> devices = map.get(address.getName()); + return devices != null && !devices.isEmpty(); + } + } + + public void clear() { + map.clear(); + } + + } + + private static class SessionMap extends AxolotlAddressMap<XmppAxolotlSession> { + private final XmppConnectionService xmppConnectionService; + private final Account account; + + public SessionMap(XmppConnectionService service, SQLiteAxolotlStore store, Account account) { + super(); + this.xmppConnectionService = service; + this.account = account; + this.fillMap(store); + } + + private void putDevicesForJid(String bareJid, List<Integer> deviceIds, SQLiteAxolotlStore store) { + for (Integer deviceId : deviceIds) { + AxolotlAddress axolotlAddress = new AxolotlAddress(bareJid, deviceId); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building session for remote address: " + axolotlAddress.toString()); + IdentityKey identityKey = store.loadSession(axolotlAddress).getSessionState().getRemoteIdentityKey(); + this.put(axolotlAddress, new XmppAxolotlSession(account, store, axolotlAddress, identityKey)); + } + } + + private void fillMap(SQLiteAxolotlStore store) { + List<Integer> deviceIds = store.getSubDeviceSessions(account.getJid().toBareJid().toString()); + putDevicesForJid(account.getJid().toBareJid().toString(), deviceIds, store); + for (Contact contact : account.getRoster().getContacts()) { + Jid bareJid = contact.getJid().toBareJid(); + String address = bareJid.toString(); + deviceIds = store.getSubDeviceSessions(address); + putDevicesForJid(address, deviceIds, store); + } + + } + + @Override + public void put(AxolotlAddress address, XmppAxolotlSession value) { + super.put(address, value); + value.setNotFresh(); + xmppConnectionService.syncRosterToDisk(account); + } + + public void put(XmppAxolotlSession session) { + this.put(session.getRemoteAddress(), session); + } + } + + public enum FetchStatus { + PENDING, + SUCCESS, + SUCCESS_VERIFIED, + TIMEOUT, + ERROR + } + + private static class FetchStatusMap extends AxolotlAddressMap<FetchStatus> { + + } + + public static String getLogprefix(Account account) { + return LOGPREFIX + " (" + account.getJid().toBareJid().toString() + "): "; + } + + public AxolotlService(Account account, XmppConnectionService connectionService) { + if (Security.getProvider("BC") == null) { + Security.addProvider(new BouncyCastleProvider()); + } + this.mXmppConnectionService = connectionService; + this.account = account; + this.axolotlStore = new SQLiteAxolotlStore(this.account, this.mXmppConnectionService); + this.deviceIds = new HashMap<>(); + this.messageCache = new HashMap<>(); + this.sessions = new SessionMap(mXmppConnectionService, axolotlStore, account); + this.fetchStatusMap = new FetchStatusMap(); + this.executor = new SerialSingleThreadExecutor(); + } + + public String getOwnFingerprint() { + return axolotlStore.getIdentityKeyPair().getPublicKey().getFingerprint().replaceAll("\\s", ""); + } + + public Set<IdentityKey> getKeysWithTrust(XmppAxolotlSession.Trust trust) { + return axolotlStore.getContactKeysWithTrust(account.getJid().toBareJid().toString(), trust); + } + + public Set<IdentityKey> getKeysWithTrust(XmppAxolotlSession.Trust trust, Contact contact) { + return axolotlStore.getContactKeysWithTrust(contact.getJid().toBareJid().toString(), trust); + } + + public long getNumTrustedKeys(Contact contact) { + return axolotlStore.getContactNumTrustedKeys(contact.getJid().toBareJid().toString()); + } + + private AxolotlAddress getAddressForJid(Jid jid) { + return new AxolotlAddress(jid.toString(), 0); + } + + private Set<XmppAxolotlSession> findOwnSessions() { + AxolotlAddress ownAddress = getAddressForJid(account.getJid().toBareJid()); + return new HashSet<>(this.sessions.getAll(ownAddress).values()); + } + + private Set<XmppAxolotlSession> findSessionsforContact(Contact contact) { + AxolotlAddress contactAddress = getAddressForJid(contact.getJid()); + return new HashSet<>(this.sessions.getAll(contactAddress).values()); + } + + public Set<String> getFingerprintsForOwnSessions() { + Set<String> fingerprints = new HashSet<>(); + for (XmppAxolotlSession session : findOwnSessions()) { + fingerprints.add(session.getFingerprint()); + } + return fingerprints; + } + + public Set<String> getFingerprintsForContact(final Contact contact) { + Set<String> fingerprints = new HashSet<>(); + for (XmppAxolotlSession session : findSessionsforContact(contact)) { + fingerprints.add(session.getFingerprint()); + } + return fingerprints; + } + + private boolean hasAny(Contact contact) { + AxolotlAddress contactAddress = getAddressForJid(contact.getJid()); + return sessions.hasAny(contactAddress); + } + + public boolean isPepBroken() { + return this.pepBroken; + } + + public void regenerateKeys(boolean wipeOther) { + axolotlStore.regenerate(); + sessions.clear(); + fetchStatusMap.clear(); + publishBundlesIfNeeded(true, wipeOther); + } + + public int getOwnDeviceId() { + return axolotlStore.getLocalRegistrationId(); + } + + public Set<Integer> getOwnDeviceIds() { + return this.deviceIds.get(account.getJid().toBareJid()); + } + + private void setTrustOnSessions(final Jid jid, @NonNull final Set<Integer> deviceIds, + final XmppAxolotlSession.Trust from, + final XmppAxolotlSession.Trust to) { + for (Integer deviceId : deviceIds) { + AxolotlAddress address = new AxolotlAddress(jid.toBareJid().toString(), deviceId); + XmppAxolotlSession session = sessions.get(address); + if (session != null && session.getFingerprint() != null + && session.getTrust() == from) { + session.setTrust(to); + } + } + } + + public void registerDevices(final Jid jid, @NonNull final Set<Integer> deviceIds) { + if (jid.toBareJid().equals(account.getJid().toBareJid())) { + if (!deviceIds.isEmpty()) { + Log.d(Config.LOGTAG, getLogprefix(account) + "Received non-empty own device list. Resetting publish attemps and pepBroken status."); + pepBroken = false; + numPublishTriesOnEmptyPep = 0; + } + if (deviceIds.contains(getOwnDeviceId())) { + deviceIds.remove(getOwnDeviceId()); + } else { + publishOwnDeviceId(deviceIds); + } + for (Integer deviceId : deviceIds) { + AxolotlAddress ownDeviceAddress = new AxolotlAddress(jid.toBareJid().toString(), deviceId); + if (sessions.get(ownDeviceAddress) == null) { + buildSessionFromPEP(ownDeviceAddress); + } + } + } + Set<Integer> expiredDevices = new HashSet<>(axolotlStore.getSubDeviceSessions(jid.toBareJid().toString())); + expiredDevices.removeAll(deviceIds); + setTrustOnSessions(jid, expiredDevices, XmppAxolotlSession.Trust.TRUSTED, + XmppAxolotlSession.Trust.INACTIVE_TRUSTED); + setTrustOnSessions(jid, expiredDevices, XmppAxolotlSession.Trust.TRUSTED_X509, + XmppAxolotlSession.Trust.INACTIVE_TRUSTED_X509); + setTrustOnSessions(jid, expiredDevices, XmppAxolotlSession.Trust.UNDECIDED, + XmppAxolotlSession.Trust.INACTIVE_UNDECIDED); + setTrustOnSessions(jid, expiredDevices, XmppAxolotlSession.Trust.UNTRUSTED, + XmppAxolotlSession.Trust.INACTIVE_UNTRUSTED); + Set<Integer> newDevices = new HashSet<>(deviceIds); + setTrustOnSessions(jid, newDevices, XmppAxolotlSession.Trust.INACTIVE_TRUSTED, + XmppAxolotlSession.Trust.TRUSTED); + setTrustOnSessions(jid, newDevices, XmppAxolotlSession.Trust.INACTIVE_TRUSTED_X509, + XmppAxolotlSession.Trust.TRUSTED_X509); + setTrustOnSessions(jid, newDevices, XmppAxolotlSession.Trust.INACTIVE_UNDECIDED, + XmppAxolotlSession.Trust.UNDECIDED); + setTrustOnSessions(jid, newDevices, XmppAxolotlSession.Trust.INACTIVE_UNTRUSTED, + XmppAxolotlSession.Trust.UNTRUSTED); + this.deviceIds.put(jid, deviceIds); + mXmppConnectionService.keyStatusUpdated(null); + } + + public void wipeOtherPepDevices() { + if (pepBroken) { + Log.d(Config.LOGTAG, getLogprefix(account) + "wipeOtherPepDevices called, but PEP is broken. Ignoring... "); + return; + } + Set<Integer> deviceIds = new HashSet<>(); + deviceIds.add(getOwnDeviceId()); + IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(deviceIds); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Wiping all other devices from Pep:" + publish); + mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + // TODO: implement this! + } + }); + } + + public void purgeKey(final String fingerprint) { + axolotlStore.setFingerprintTrust(fingerprint.replaceAll("\\s", ""), XmppAxolotlSession.Trust.COMPROMISED); + } + + public void publishOwnDeviceIdIfNeeded() { + if (pepBroken) { + Log.d(Config.LOGTAG, getLogprefix(account) + "publishOwnDeviceIdIfNeeded called, but PEP is broken. Ignoring... "); + return; + } + IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveDeviceIds(account.getJid().toBareJid()); + mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + if (packet.getType() == IqPacket.TYPE.TIMEOUT) { + Log.d(Config.LOGTAG, getLogprefix(account) + "Timeout received while retrieving own Device Ids."); + } else { + Element item = mXmppConnectionService.getIqParser().getItem(packet); + Set<Integer> deviceIds = mXmppConnectionService.getIqParser().deviceIds(item); + if (!deviceIds.contains(getOwnDeviceId())) { + publishOwnDeviceId(deviceIds); + } + } + } + }); + } + + public void publishOwnDeviceId(Set<Integer> deviceIds) { + Set<Integer> deviceIdsCopy = new HashSet<>(deviceIds); + if (!deviceIdsCopy.contains(getOwnDeviceId())) { + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Own device " + getOwnDeviceId() + " not in PEP devicelist."); + if (deviceIdsCopy.isEmpty()) { + if (numPublishTriesOnEmptyPep >= publishTriesThreshold) { + Log.w(Config.LOGTAG, getLogprefix(account) + "Own device publish attempt threshold exceeded, aborting..."); + pepBroken = true; + return; + } else { + numPublishTriesOnEmptyPep++; + Log.w(Config.LOGTAG, getLogprefix(account) + "Own device list empty, attempting to publish (try " + numPublishTriesOnEmptyPep + ")"); + } + } else { + numPublishTriesOnEmptyPep = 0; + } + deviceIdsCopy.add(getOwnDeviceId()); + IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(deviceIdsCopy); + mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + if (packet.getType() != IqPacket.TYPE.RESULT) { + Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing own device id" + packet.findChild("error")); + } + } + }); + } + } + + public void publishDeviceVerificationAndBundle(final SignedPreKeyRecord signedPreKeyRecord, + final Set<PreKeyRecord> preKeyRecords, + final boolean announceAfter, + final boolean wipe) { + try { + IdentityKey axolotlPublicKey = axolotlStore.getIdentityKeyPair().getPublicKey(); + PrivateKey x509PrivateKey = KeyChain.getPrivateKey(mXmppConnectionService, account.getPrivateKeyAlias()); + X509Certificate[] chain = KeyChain.getCertificateChain(mXmppConnectionService, account.getPrivateKeyAlias()); + Signature verifier = Signature.getInstance("sha256WithRSA"); + verifier.initSign(x509PrivateKey,mXmppConnectionService.getRNG()); + verifier.update(axolotlPublicKey.serialize()); + byte[] signature = verifier.sign(); + IqPacket packet = mXmppConnectionService.getIqGenerator().publishVerification(signature, chain, getOwnDeviceId()); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ": publish verification for device "+getOwnDeviceId()); + mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe); + } + }); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void publishBundlesIfNeeded(final boolean announce, final boolean wipe) { + if (pepBroken) { + Log.d(Config.LOGTAG, getLogprefix(account) + "publishBundlesIfNeeded called, but PEP is broken. Ignoring... "); + return; + } + IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(account.getJid().toBareJid(), getOwnDeviceId()); + mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + + if (packet.getType() == IqPacket.TYPE.TIMEOUT) { + return; //ignore timeout. do nothing + } + + if (packet.getType() == IqPacket.TYPE.ERROR) { + Element error = packet.findChild("error"); + if (error == null || !error.hasChild("item-not-found")) { + pepBroken = true; + Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "request for device bundles came back with something other than item-not-found" + packet); + return; + } + } + + PreKeyBundle bundle = mXmppConnectionService.getIqParser().bundle(packet); + Map<Integer, ECPublicKey> keys = mXmppConnectionService.getIqParser().preKeyPublics(packet); + boolean flush = false; + if (bundle == null) { + Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received invalid bundle:" + packet); + bundle = new PreKeyBundle(-1, -1, -1, null, -1, null, null, null); + flush = true; + } + if (keys == null) { + Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received invalid prekeys:" + packet); + } + try { + boolean changed = false; + // Validate IdentityKey + IdentityKeyPair identityKeyPair = axolotlStore.getIdentityKeyPair(); + if (flush || !identityKeyPair.getPublicKey().equals(bundle.getIdentityKey())) { + Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding own IdentityKey " + identityKeyPair.getPublicKey() + " to PEP."); + changed = true; + } + + // Validate signedPreKeyRecord + ID + SignedPreKeyRecord signedPreKeyRecord; + int numSignedPreKeys = axolotlStore.loadSignedPreKeys().size(); + try { + signedPreKeyRecord = axolotlStore.loadSignedPreKey(bundle.getSignedPreKeyId()); + if (flush + || !bundle.getSignedPreKey().equals(signedPreKeyRecord.getKeyPair().getPublicKey()) + || !Arrays.equals(bundle.getSignedPreKeySignature(), signedPreKeyRecord.getSignature())) { + Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP."); + signedPreKeyRecord = KeyHelper.generateSignedPreKey(identityKeyPair, numSignedPreKeys + 1); + axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord); + changed = true; + } + } catch (InvalidKeyIdException e) { + Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP."); + signedPreKeyRecord = KeyHelper.generateSignedPreKey(identityKeyPair, numSignedPreKeys + 1); + axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord); + changed = true; + } + + // Validate PreKeys + Set<PreKeyRecord> preKeyRecords = new HashSet<>(); + if (keys != null) { + for (Integer id : keys.keySet()) { + try { + PreKeyRecord preKeyRecord = axolotlStore.loadPreKey(id); + if (preKeyRecord.getKeyPair().getPublicKey().equals(keys.get(id))) { + preKeyRecords.add(preKeyRecord); + } + } catch (InvalidKeyIdException ignored) { + } + } + } + int newKeys = NUM_KEYS_TO_PUBLISH - preKeyRecords.size(); + if (newKeys > 0) { + List<PreKeyRecord> newRecords = KeyHelper.generatePreKeys( + axolotlStore.getCurrentPreKeyId() + 1, newKeys); + preKeyRecords.addAll(newRecords); + for (PreKeyRecord record : newRecords) { + axolotlStore.storePreKey(record.getId(), record); + } + changed = true; + Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding " + newKeys + " new preKeys to PEP."); + } + + + if (changed) { + if (account.getPrivateKeyAlias() != null && Config.X509_VERIFICATION) { + mXmppConnectionService.publishDisplayName(account); + publishDeviceVerificationAndBundle(signedPreKeyRecord, preKeyRecords, announce, wipe); + } else { + publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announce, wipe); + } + } else { + Log.d(Config.LOGTAG, getLogprefix(account) + "Bundle " + getOwnDeviceId() + " in PEP was current"); + if (wipe) { + wipeOtherPepDevices(); + } else if (announce) { + Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId()); + publishOwnDeviceIdIfNeeded(); + } + } + } catch (InvalidKeyException e) { + Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Failed to publish bundle " + getOwnDeviceId() + ", reason: " + e.getMessage()); + } + } + }); + } + + private void publishDeviceBundle(SignedPreKeyRecord signedPreKeyRecord, + Set<PreKeyRecord> preKeyRecords, + final boolean announceAfter, + final boolean wipe) { + IqPacket publish = mXmppConnectionService.getIqGenerator().publishBundles( + signedPreKeyRecord, axolotlStore.getIdentityKeyPair().getPublicKey(), + preKeyRecords, getOwnDeviceId()); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ": Bundle " + getOwnDeviceId() + " in PEP not current. Publishing: " + publish); + mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + if (packet.getType() == IqPacket.TYPE.RESULT) { + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Successfully published bundle. "); + if (wipe) { + wipeOtherPepDevices(); + } else if (announceAfter) { + Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId()); + publishOwnDeviceIdIfNeeded(); + } + } else { + Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing bundle: " + packet.findChild("error")); + } + } + }); + } + + public boolean isContactAxolotlCapable(Contact contact) { + Jid jid = contact.getJid().toBareJid(); + return hasAny(contact) || + (deviceIds.containsKey(jid) && !deviceIds.get(jid).isEmpty()); + } + + public XmppAxolotlSession.Trust getFingerprintTrust(String fingerprint) { + return axolotlStore.getFingerprintTrust(fingerprint); + } + + public X509Certificate getFingerprintCertificate(String fingerprint) { + return axolotlStore.getFingerprintCertificate(fingerprint); + } + + public void setFingerprintTrust(String fingerprint, XmppAxolotlSession.Trust trust) { + axolotlStore.setFingerprintTrust(fingerprint, trust); + } + + private void verifySessionWithPEP(final XmppAxolotlSession session) { + Log.d(Config.LOGTAG, "trying to verify fresh session (" + session.getRemoteAddress().getName() + ") with pep"); + final AxolotlAddress address = session.getRemoteAddress(); + final IdentityKey identityKey = session.getIdentityKey(); + try { + IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveVerificationForDevice(Jid.fromString(address.getName()), address.getDeviceId()); + mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + Pair<X509Certificate[],byte[]> verification = mXmppConnectionService.getIqParser().verification(packet); + if (verification != null) { + try { + Signature verifier = Signature.getInstance("sha256WithRSA"); + verifier.initVerify(verification.first[0]); + verifier.update(identityKey.serialize()); + if (verifier.verify(verification.second)) { + try { + mXmppConnectionService.getMemorizingTrustManager().getNonInteractive().checkClientTrusted(verification.first, "RSA"); + String fingerprint = session.getFingerprint(); + Log.d(Config.LOGTAG, "verified session with x.509 signature. fingerprint was: "+fingerprint); + setFingerprintTrust(fingerprint, XmppAxolotlSession.Trust.TRUSTED_X509); + axolotlStore.setFingerprintCertificate(fingerprint, verification.first[0]); + fetchStatusMap.put(address, FetchStatus.SUCCESS_VERIFIED); + finishBuildingSessionsFromPEP(address); + return; + } catch (Exception e) { + Log.d(Config.LOGTAG,"could not verify certificate"); + } + } + } catch (Exception e) { + Log.d(Config.LOGTAG, "error during verification " + e.getMessage()); + } + } else { + Log.d(Config.LOGTAG,"no verification found"); + } + fetchStatusMap.put(address, FetchStatus.SUCCESS); + finishBuildingSessionsFromPEP(address); + } + }); + } catch (InvalidJidException e) { + fetchStatusMap.put(address, FetchStatus.SUCCESS); + finishBuildingSessionsFromPEP(address); + } + } + + private void finishBuildingSessionsFromPEP(final AxolotlAddress address) { + AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toString(), 0); + if (!fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.PENDING) + && !fetchStatusMap.getAll(address).containsValue(FetchStatus.PENDING)) { + FetchStatus report = null; + if (fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.SUCCESS_VERIFIED) + | fetchStatusMap.getAll(address).containsValue(FetchStatus.SUCCESS_VERIFIED)) { + report = FetchStatus.SUCCESS_VERIFIED; + } else if (fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.ERROR) + || fetchStatusMap.getAll(address).containsValue(FetchStatus.ERROR)) { + report = FetchStatus.ERROR; + } + mXmppConnectionService.keyStatusUpdated(report); + } + } + + private void buildSessionFromPEP(final AxolotlAddress address) { + Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building new sesstion for " + address.toString()); + if (address.getDeviceId() == getOwnDeviceId()) { + throw new AssertionError("We should NEVER build a session with ourselves. What happened here?!"); + } + + try { + IqPacket bundlesPacket = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice( + Jid.fromString(address.getName()), address.getDeviceId()); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Retrieving bundle: " + bundlesPacket); + mXmppConnectionService.sendIqPacket(account, bundlesPacket, new OnIqPacketReceived() { + + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + if (packet.getType() == IqPacket.TYPE.TIMEOUT) { + fetchStatusMap.put(address, FetchStatus.TIMEOUT); + } else if (packet.getType() == IqPacket.TYPE.RESULT) { + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received preKey IQ packet, processing..."); + final IqParser parser = mXmppConnectionService.getIqParser(); + final List<PreKeyBundle> preKeyBundleList = parser.preKeys(packet); + final PreKeyBundle bundle = parser.bundle(packet); + if (preKeyBundleList.isEmpty() || bundle == null) { + Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "preKey IQ packet invalid: " + packet); + fetchStatusMap.put(address, FetchStatus.ERROR); + finishBuildingSessionsFromPEP(address); + return; + } + Random random = new Random(); + final PreKeyBundle preKey = preKeyBundleList.get(random.nextInt(preKeyBundleList.size())); + if (preKey == null) { + //should never happen + fetchStatusMap.put(address, FetchStatus.ERROR); + finishBuildingSessionsFromPEP(address); + return; + } + + final PreKeyBundle preKeyBundle = new PreKeyBundle(0, address.getDeviceId(), + preKey.getPreKeyId(), preKey.getPreKey(), + bundle.getSignedPreKeyId(), bundle.getSignedPreKey(), + bundle.getSignedPreKeySignature(), bundle.getIdentityKey()); + + try { + SessionBuilder builder = new SessionBuilder(axolotlStore, address); + builder.process(preKeyBundle); + XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, bundle.getIdentityKey()); + sessions.put(address, session); + if (Config.X509_VERIFICATION) { + verifySessionWithPEP(session); + } else { + fetchStatusMap.put(address, FetchStatus.SUCCESS); + finishBuildingSessionsFromPEP(address); + } + } catch (UntrustedIdentityException | InvalidKeyException e) { + Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error building session for " + address + ": " + + e.getClass().getName() + ", " + e.getMessage()); + fetchStatusMap.put(address, FetchStatus.ERROR); + finishBuildingSessionsFromPEP(address); + } + } else { + fetchStatusMap.put(address, FetchStatus.ERROR); + Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while building session:" + packet.findChild("error")); + finishBuildingSessionsFromPEP(address); + } + } + }); + } catch (InvalidJidException e) { + Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Got address with invalid jid: " + address.getName()); + } + } + + public Set<AxolotlAddress> findDevicesWithoutSession(final Conversation conversation) { + return findDevicesWithoutSession(conversation.getContact().getJid().toBareJid()); + } + + public Set<AxolotlAddress> findDevicesWithoutSession(final Jid contactJid) { + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Finding devices without session for " + contactJid); + Set<AxolotlAddress> addresses = new HashSet<>(); + if (deviceIds.get(contactJid) != null) { + for (Integer foreignId : this.deviceIds.get(contactJid)) { + AxolotlAddress address = new AxolotlAddress(contactJid.toString(), foreignId); + if (sessions.get(address) == null) { + IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey(); + if (identityKey != null) { + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already have session for " + address.toString() + ", adding to cache..."); + XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey); + sessions.put(address, session); + } else { + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Found device " + contactJid + ":" + foreignId); + if (fetchStatusMap.get(address) != FetchStatus.ERROR) { + addresses.add(address); + } else { + Log.d(Config.LOGTAG,getLogprefix(account)+"skipping over "+address+" because it's broken"); + } + } + } + } + } else { + Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Have no target devices in PEP!"); + } + if (deviceIds.get(account.getJid().toBareJid()) != null) { + for (Integer ownId : this.deviceIds.get(account.getJid().toBareJid())) { + AxolotlAddress address = new AxolotlAddress(account.getJid().toBareJid().toString(), ownId); + if (sessions.get(address) == null) { + IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey(); + if (identityKey != null) { + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already have session for " + address.toString() + ", adding to cache..."); + XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey); + sessions.put(address, session); + } else { + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Found device " + account.getJid().toBareJid() + ":" + ownId); + if (fetchStatusMap.get(address) != FetchStatus.ERROR) { + addresses.add(address); + } else { + Log.d(Config.LOGTAG,getLogprefix(account)+"skipping over "+address+" because it's broken"); + } + } + } + } + } + + return addresses; + } + + public boolean createSessionsIfNeeded(final Conversation conversation) { + Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Creating axolotl sessions if needed..."); + boolean newSessions = false; + Set<AxolotlAddress> addresses = findDevicesWithoutSession(conversation); + for (AxolotlAddress address : addresses) { + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Processing device: " + address.toString()); + FetchStatus status = fetchStatusMap.get(address); + if (status == null || status == FetchStatus.TIMEOUT) { + fetchStatusMap.put(address, FetchStatus.PENDING); + this.buildSessionFromPEP(address); + newSessions = true; + } else if (status == FetchStatus.PENDING) { + newSessions = true; + } else { + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already fetching bundle for " + address.toString()); + } + } + + return newSessions; + } + + public boolean trustedSessionVerified(final Conversation conversation) { + Set<XmppAxolotlSession> sessions = findSessionsforContact(conversation.getContact()); + sessions.addAll(findOwnSessions()); + boolean verified = false; + for(XmppAxolotlSession session : sessions) { + if (session.getTrust().trusted()) { + if (session.getTrust() == XmppAxolotlSession.Trust.TRUSTED_X509) { + verified = true; + } else { + return false; + } + } + } + return verified; + } + + public boolean hasPendingKeyFetches(Account account, Contact contact) { + AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toString(), 0); + AxolotlAddress foreignAddress = new AxolotlAddress(contact.getJid().toBareJid().toString(), 0); + return fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.PENDING) + || fetchStatusMap.getAll(foreignAddress).containsValue(FetchStatus.PENDING); + + } + + @Nullable + private XmppAxolotlMessage buildHeader(Contact contact) { + final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage( + contact.getJid().toBareJid(), getOwnDeviceId()); + + Set<XmppAxolotlSession> contactSessions = findSessionsforContact(contact); + Set<XmppAxolotlSession> ownSessions = findOwnSessions(); + if (contactSessions.isEmpty()) { + return null; + } + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building axolotl foreign keyElements..."); + for (XmppAxolotlSession session : contactSessions) { + Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account) + session.getRemoteAddress().toString()); + axolotlMessage.addDevice(session); + } + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building axolotl own keyElements..."); + for (XmppAxolotlSession session : ownSessions) { + Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account) + session.getRemoteAddress().toString()); + axolotlMessage.addDevice(session); + } + + return axolotlMessage; + } + + @Nullable + public XmppAxolotlMessage encrypt(Message message) { + XmppAxolotlMessage axolotlMessage = buildHeader(message.getContact()); + + if (axolotlMessage != null) { + final String content; + if (message.hasFileOnRemoteHost()) { + content = message.getFileParams().url.toString(); + } else { + content = message.getBody(); + } + try { + axolotlMessage.encrypt(content); + } catch (CryptoFailedException e) { + Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to encrypt message: " + e.getMessage()); + return null; + } + } + + return axolotlMessage; + } + + public void preparePayloadMessage(final Message message, final boolean delay) { + executor.execute(new Runnable() { + @Override + public void run() { + XmppAxolotlMessage axolotlMessage = encrypt(message); + if (axolotlMessage == null) { + mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED); + //mXmppConnectionService.updateConversationUi(); + } else { + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Generated message, caching: " + message.getUuid()); + messageCache.put(message.getUuid(), axolotlMessage); + mXmppConnectionService.resendMessage(message, delay); + } + } + }); + } + + public void prepareKeyTransportMessage(final Contact contact, final OnMessageCreatedCallback onMessageCreatedCallback) { + executor.execute(new Runnable() { + @Override + public void run() { + XmppAxolotlMessage axolotlMessage = buildHeader(contact); + onMessageCreatedCallback.run(axolotlMessage); + } + }); + } + + public XmppAxolotlMessage fetchAxolotlMessageFromCache(Message message) { + XmppAxolotlMessage axolotlMessage = messageCache.get(message.getUuid()); + if (axolotlMessage != null) { + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Cache hit: " + message.getUuid()); + messageCache.remove(message.getUuid()); + } else { + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Cache miss: " + message.getUuid()); + } + return axolotlMessage; + } + + private XmppAxolotlSession recreateUncachedSession(AxolotlAddress address) { + IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey(); + return (identityKey != null) + ? new XmppAxolotlSession(account, axolotlStore, address, identityKey) + : null; + } + + private XmppAxolotlSession getReceivingSession(XmppAxolotlMessage message) { + AxolotlAddress senderAddress = new AxolotlAddress(message.getFrom().toString(), + message.getSenderDeviceId()); + XmppAxolotlSession session = sessions.get(senderAddress); + if (session == null) { + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Account: " + account.getJid() + " No axolotl session found while parsing received message " + message); + session = recreateUncachedSession(senderAddress); + if (session == null) { + session = new XmppAxolotlSession(account, axolotlStore, senderAddress); + } + } + return session; + } + + public XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceivingPayloadMessage(XmppAxolotlMessage message) { + XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = null; + + XmppAxolotlSession session = getReceivingSession(message); + try { + plaintextMessage = message.decrypt(session, getOwnDeviceId()); + Integer preKeyId = session.getPreKeyId(); + if (preKeyId != null) { + publishBundlesIfNeeded(false, false); + session.resetPreKeyId(); + } + } catch (CryptoFailedException e) { + Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to decrypt message: " + e.getMessage()); + } + + if (session.isFresh() && plaintextMessage != null) { + putFreshSession(session); + } + + return plaintextMessage; + } + + public XmppAxolotlMessage.XmppAxolotlKeyTransportMessage processReceivingKeyTransportMessage(XmppAxolotlMessage message) { + XmppAxolotlMessage.XmppAxolotlKeyTransportMessage keyTransportMessage; + + XmppAxolotlSession session = getReceivingSession(message); + keyTransportMessage = message.getParameters(session, getOwnDeviceId()); + + if (session.isFresh() && keyTransportMessage != null) { + putFreshSession(session); + } + + return keyTransportMessage; + } + + private void putFreshSession(XmppAxolotlSession session) { + Log.d(Config.LOGTAG,"put fresh session"); + sessions.put(session); + if (Config.X509_VERIFICATION) { + if (session.getIdentityKey() != null) { + verifySessionWithPEP(session); + } else { + Log.e(Config.LOGTAG,account.getJid().toBareJid()+": identity key was empty after reloading for x509 verification"); + } + } + } +} diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/CryptoFailedException.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/CryptoFailedException.java new file mode 100644 index 00000000..5796ef30 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/CryptoFailedException.java @@ -0,0 +1,7 @@ +package eu.siacs.conversations.crypto.axolotl; + +public class CryptoFailedException extends Exception { + public CryptoFailedException(Exception e){ + super(e); + } +} diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/NoSessionsCreatedException.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/NoSessionsCreatedException.java new file mode 100644 index 00000000..663b42b5 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/NoSessionsCreatedException.java @@ -0,0 +1,4 @@ +package eu.siacs.conversations.crypto.axolotl; + +public class NoSessionsCreatedException extends Throwable{ +} diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/OnMessageCreatedCallback.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/OnMessageCreatedCallback.java new file mode 100644 index 00000000..3d40a408 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/OnMessageCreatedCallback.java @@ -0,0 +1,5 @@ +package eu.siacs.conversations.crypto.axolotl; + +public interface OnMessageCreatedCallback { + void run(XmppAxolotlMessage message); +} diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/SQLiteAxolotlStore.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/SQLiteAxolotlStore.java new file mode 100644 index 00000000..3c8cb3c1 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/SQLiteAxolotlStore.java @@ -0,0 +1,429 @@ +package eu.siacs.conversations.crypto.axolotl; + +import android.util.Log; +import android.util.LruCache; + +import org.whispersystems.libaxolotl.AxolotlAddress; +import org.whispersystems.libaxolotl.IdentityKey; +import org.whispersystems.libaxolotl.IdentityKeyPair; +import org.whispersystems.libaxolotl.InvalidKeyIdException; +import org.whispersystems.libaxolotl.ecc.Curve; +import org.whispersystems.libaxolotl.ecc.ECKeyPair; +import org.whispersystems.libaxolotl.state.AxolotlStore; +import org.whispersystems.libaxolotl.state.PreKeyRecord; +import org.whispersystems.libaxolotl.state.SessionRecord; +import org.whispersystems.libaxolotl.state.SignedPreKeyRecord; +import org.whispersystems.libaxolotl.util.KeyHelper; + +import java.security.cert.X509Certificate; +import java.util.List; +import java.util.Set; + +import eu.siacs.conversations.Config; +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.services.XmppConnectionService; + +public class SQLiteAxolotlStore implements AxolotlStore { + + public static final String PREKEY_TABLENAME = "prekeys"; + public static final String SIGNED_PREKEY_TABLENAME = "signed_prekeys"; + public static final String SESSION_TABLENAME = "sessions"; + public static final String IDENTITIES_TABLENAME = "identities"; + public static final String ACCOUNT = "account"; + public static final String DEVICE_ID = "device_id"; + public static final String ID = "id"; + public static final String KEY = "key"; + public static final String FINGERPRINT = "fingerprint"; + public static final String NAME = "name"; + public static final String TRUSTED = "trusted"; + public static final String OWN = "ownkey"; + public static final String CERTIFICATE = "certificate"; + + public static final String JSONKEY_REGISTRATION_ID = "axolotl_reg_id"; + public static final String JSONKEY_CURRENT_PREKEY_ID = "axolotl_cur_prekey_id"; + + private static final int NUM_TRUSTS_TO_CACHE = 100; + + private final Account account; + private final XmppConnectionService mXmppConnectionService; + + private IdentityKeyPair identityKeyPair; + private int localRegistrationId; + private int currentPreKeyId = 0; + + private final LruCache<String, XmppAxolotlSession.Trust> trustCache = + new LruCache<String, XmppAxolotlSession.Trust>(NUM_TRUSTS_TO_CACHE) { + @Override + protected XmppAxolotlSession.Trust create(String fingerprint) { + return mXmppConnectionService.databaseBackend.isIdentityKeyTrusted(account, fingerprint); + } + }; + + private static IdentityKeyPair generateIdentityKeyPair() { + Log.i(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Generating axolotl IdentityKeyPair..."); + ECKeyPair identityKeyPairKeys = Curve.generateKeyPair(); + return new IdentityKeyPair(new IdentityKey(identityKeyPairKeys.getPublicKey()), + identityKeyPairKeys.getPrivateKey()); + } + + private static int generateRegistrationId() { + Log.i(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Generating axolotl registration ID..."); + return KeyHelper.generateRegistrationId(true); + } + + public SQLiteAxolotlStore(Account account, XmppConnectionService service) { + this.account = account; + this.mXmppConnectionService = service; + this.localRegistrationId = loadRegistrationId(); + this.currentPreKeyId = loadCurrentPreKeyId(); + for (SignedPreKeyRecord record : loadSignedPreKeys()) { + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Got Axolotl signed prekey record:" + record.getId()); + } + } + + public int getCurrentPreKeyId() { + return currentPreKeyId; + } + + // -------------------------------------- + // IdentityKeyStore + // -------------------------------------- + + private IdentityKeyPair loadIdentityKeyPair() { + IdentityKeyPair ownKey = mXmppConnectionService.databaseBackend.loadOwnIdentityKeyPair(account); + + if (ownKey != null) { + return ownKey; + } else { + Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Could not retrieve own IdentityKeyPair"); + ownKey = generateIdentityKeyPair(); + mXmppConnectionService.databaseBackend.storeOwnIdentityKeyPair(account, ownKey); + } + return ownKey; + } + + private int loadRegistrationId() { + return loadRegistrationId(false); + } + + private int loadRegistrationId(boolean regenerate) { + String regIdString = this.account.getKey(JSONKEY_REGISTRATION_ID); + int reg_id; + if (!regenerate && regIdString != null) { + reg_id = Integer.valueOf(regIdString); + } else { + Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Could not retrieve axolotl registration id for account " + account.getJid()); + reg_id = generateRegistrationId(); + boolean success = this.account.setKey(JSONKEY_REGISTRATION_ID, Integer.toString(reg_id)); + if (success) { + mXmppConnectionService.databaseBackend.updateAccount(account); + } else { + Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Failed to write new key to the database!"); + } + } + return reg_id; + } + + private int loadCurrentPreKeyId() { + String regIdString = this.account.getKey(JSONKEY_CURRENT_PREKEY_ID); + int reg_id; + if (regIdString != null) { + reg_id = Integer.valueOf(regIdString); + } else { + Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Could not retrieve current prekey id for account " + account.getJid()); + reg_id = 0; + } + return reg_id; + } + + public void regenerate() { + mXmppConnectionService.databaseBackend.wipeAxolotlDb(account); + trustCache.evictAll(); + account.setKey(JSONKEY_CURRENT_PREKEY_ID, Integer.toString(0)); + identityKeyPair = loadIdentityKeyPair(); + localRegistrationId = loadRegistrationId(true); + currentPreKeyId = 0; + mXmppConnectionService.updateAccountUi(); + } + + /** + * Get the local client's identity key pair. + * + * @return The local client's persistent identity key pair. + */ + @Override + public IdentityKeyPair getIdentityKeyPair() { + if (identityKeyPair == null) { + identityKeyPair = loadIdentityKeyPair(); + } + return identityKeyPair; + } + + /** + * Return the local client's registration ID. + * <p/> + * Clients should maintain a registration ID, a random number + * between 1 and 16380 that's generated once at install time. + * + * @return the local client's registration ID. + */ + @Override + public int getLocalRegistrationId() { + return localRegistrationId; + } + + /** + * Save a remote client's identity key + * <p/> + * Store a remote client's identity key as trusted. + * + * @param name The name of the remote client. + * @param identityKey The remote client's identity key. + */ + @Override + public void saveIdentity(String name, IdentityKey identityKey) { + if (!mXmppConnectionService.databaseBackend.loadIdentityKeys(account, name).contains(identityKey)) { + mXmppConnectionService.databaseBackend.storeIdentityKey(account, name, identityKey); + } + } + + /** + * Verify a remote client's identity key. + * <p/> + * Determine whether a remote client's identity is trusted. Convention is + * that the TextSecure protocol is 'trust on first use.' This means that + * an identity key is considered 'trusted' if there is no entry for the recipient + * in the local store, or if it matches the saved key for a recipient in the local + * store. Only if it mismatches an entry in the local store is it considered + * 'untrusted.' + * + * @param name The name of the remote client. + * @param identityKey The identity key to verify. + * @return true if trusted, false if untrusted. + */ + @Override + public boolean isTrustedIdentity(String name, IdentityKey identityKey) { + return true; + } + + public XmppAxolotlSession.Trust getFingerprintTrust(String fingerprint) { + return (fingerprint == null)? null : trustCache.get(fingerprint); + } + + public void setFingerprintTrust(String fingerprint, XmppAxolotlSession.Trust trust) { + mXmppConnectionService.databaseBackend.setIdentityKeyTrust(account, fingerprint, trust); + trustCache.remove(fingerprint); + } + + public void setFingerprintCertificate(String fingerprint, X509Certificate x509Certificate) { + mXmppConnectionService.databaseBackend.setIdentityKeyCertificate(account, fingerprint, x509Certificate); + } + + public X509Certificate getFingerprintCertificate(String fingerprint) { + return mXmppConnectionService.databaseBackend.getIdentityKeyCertifcate(account, fingerprint); + } + + public Set<IdentityKey> getContactKeysWithTrust(String bareJid, XmppAxolotlSession.Trust trust) { + return mXmppConnectionService.databaseBackend.loadIdentityKeys(account, bareJid, trust); + } + + public long getContactNumTrustedKeys(String bareJid) { + return mXmppConnectionService.databaseBackend.numTrustedKeys(account, bareJid); + } + + // -------------------------------------- + // SessionStore + // -------------------------------------- + + /** + * Returns a copy of the {@link SessionRecord} corresponding to the recipientId + deviceId tuple, + * or a new SessionRecord if one does not currently exist. + * <p/> + * It is important that implementations return a copy of the current durable information. The + * returned SessionRecord may be modified, but those changes should not have an effect on the + * durable session state (what is returned by subsequent calls to this method) without the + * store method being called here first. + * + * @param address The name and device ID of the remote client. + * @return a copy of the SessionRecord corresponding to the recipientId + deviceId tuple, or + * a new SessionRecord if one does not currently exist. + */ + @Override + public SessionRecord loadSession(AxolotlAddress address) { + SessionRecord session = mXmppConnectionService.databaseBackend.loadSession(this.account, address); + return (session != null) ? session : new SessionRecord(); + } + + /** + * Returns all known devices with active sessions for a recipient + * + * @param name the name of the client. + * @return all known sub-devices with active sessions. + */ + @Override + public List<Integer> getSubDeviceSessions(String name) { + return mXmppConnectionService.databaseBackend.getSubDeviceSessions(account, + new AxolotlAddress(name, 0)); + } + + /** + * Commit to storage the {@link SessionRecord} for a given recipientId + deviceId tuple. + * + * @param address the address of the remote client. + * @param record the current SessionRecord for the remote client. + */ + @Override + public void storeSession(AxolotlAddress address, SessionRecord record) { + mXmppConnectionService.databaseBackend.storeSession(account, address, record); + } + + /** + * Determine whether there is a committed {@link SessionRecord} for a recipientId + deviceId tuple. + * + * @param address the address of the remote client. + * @return true if a {@link SessionRecord} exists, false otherwise. + */ + @Override + public boolean containsSession(AxolotlAddress address) { + return mXmppConnectionService.databaseBackend.containsSession(account, address); + } + + /** + * Remove a {@link SessionRecord} for a recipientId + deviceId tuple. + * + * @param address the address of the remote client. + */ + @Override + public void deleteSession(AxolotlAddress address) { + mXmppConnectionService.databaseBackend.deleteSession(account, address); + } + + /** + * Remove the {@link SessionRecord}s corresponding to all devices of a recipientId. + * + * @param name the name of the remote client. + */ + @Override + public void deleteAllSessions(String name) { + AxolotlAddress address = new AxolotlAddress(name, 0); + mXmppConnectionService.databaseBackend.deleteAllSessions(account, + address); + } + + // -------------------------------------- + // PreKeyStore + // -------------------------------------- + + /** + * Load a local PreKeyRecord. + * + * @param preKeyId the ID of the local PreKeyRecord. + * @return the corresponding PreKeyRecord. + * @throws InvalidKeyIdException when there is no corresponding PreKeyRecord. + */ + @Override + public PreKeyRecord loadPreKey(int preKeyId) throws InvalidKeyIdException { + PreKeyRecord record = mXmppConnectionService.databaseBackend.loadPreKey(account, preKeyId); + if (record == null) { + throw new InvalidKeyIdException("No such PreKeyRecord: " + preKeyId); + } + return record; + } + + /** + * Store a local PreKeyRecord. + * + * @param preKeyId the ID of the PreKeyRecord to store. + * @param record the PreKeyRecord. + */ + @Override + public void storePreKey(int preKeyId, PreKeyRecord record) { + mXmppConnectionService.databaseBackend.storePreKey(account, record); + currentPreKeyId = preKeyId; + boolean success = this.account.setKey(JSONKEY_CURRENT_PREKEY_ID, Integer.toString(preKeyId)); + if (success) { + mXmppConnectionService.databaseBackend.updateAccount(account); + } else { + Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Failed to write new prekey id to the database!"); + } + } + + /** + * @param preKeyId A PreKeyRecord ID. + * @return true if the store has a record for the preKeyId, otherwise false. + */ + @Override + public boolean containsPreKey(int preKeyId) { + return mXmppConnectionService.databaseBackend.containsPreKey(account, preKeyId); + } + + /** + * Delete a PreKeyRecord from local storage. + * + * @param preKeyId The ID of the PreKeyRecord to remove. + */ + @Override + public void removePreKey(int preKeyId) { + mXmppConnectionService.databaseBackend.deletePreKey(account, preKeyId); + } + + // -------------------------------------- + // SignedPreKeyStore + // -------------------------------------- + + /** + * Load a local SignedPreKeyRecord. + * + * @param signedPreKeyId the ID of the local SignedPreKeyRecord. + * @return the corresponding SignedPreKeyRecord. + * @throws InvalidKeyIdException when there is no corresponding SignedPreKeyRecord. + */ + @Override + public SignedPreKeyRecord loadSignedPreKey(int signedPreKeyId) throws InvalidKeyIdException { + SignedPreKeyRecord record = mXmppConnectionService.databaseBackend.loadSignedPreKey(account, signedPreKeyId); + if (record == null) { + throw new InvalidKeyIdException("No such SignedPreKeyRecord: " + signedPreKeyId); + } + return record; + } + + /** + * Load all local SignedPreKeyRecords. + * + * @return All stored SignedPreKeyRecords. + */ + @Override + public List<SignedPreKeyRecord> loadSignedPreKeys() { + return mXmppConnectionService.databaseBackend.loadSignedPreKeys(account); + } + + /** + * Store a local SignedPreKeyRecord. + * + * @param signedPreKeyId the ID of the SignedPreKeyRecord to store. + * @param record the SignedPreKeyRecord. + */ + @Override + public void storeSignedPreKey(int signedPreKeyId, SignedPreKeyRecord record) { + mXmppConnectionService.databaseBackend.storeSignedPreKey(account, record); + } + + /** + * @param signedPreKeyId A SignedPreKeyRecord ID. + * @return true if the store has a record for the signedPreKeyId, otherwise false. + */ + @Override + public boolean containsSignedPreKey(int signedPreKeyId) { + return mXmppConnectionService.databaseBackend.containsSignedPreKey(account, signedPreKeyId); + } + + /** + * Delete a SignedPreKeyRecord from local storage. + * + * @param signedPreKeyId The ID of the SignedPreKeyRecord to remove. + */ + @Override + public void removeSignedPreKey(int signedPreKeyId) { + mXmppConnectionService.databaseBackend.deleteSignedPreKey(account, signedPreKeyId); + } +} diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java new file mode 100644 index 00000000..cf950d6d --- /dev/null +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java @@ -0,0 +1,249 @@ +package eu.siacs.conversations.crypto.axolotl; + +import android.util.Base64; +import android.util.Log; + +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.SecureRandom; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.KeyGenerator; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +import eu.siacs.conversations.Config; +import eu.siacs.conversations.xml.Element; +import eu.siacs.conversations.xmpp.jid.Jid; + +public class XmppAxolotlMessage { + public static final String CONTAINERTAG = "encrypted"; + public static final String HEADER = "header"; + public static final String SOURCEID = "sid"; + public static final String KEYTAG = "key"; + public static final String REMOTEID = "rid"; + public static final String IVTAG = "iv"; + public static final String PAYLOAD = "payload"; + + private static final String KEYTYPE = "AES"; + private static final String CIPHERMODE = "AES/GCM/NoPadding"; + private static final String PROVIDER = "BC"; + + private byte[] innerKey; + private byte[] ciphertext = null; + private byte[] iv = null; + private final Map<Integer, byte[]> keys; + private final Jid from; + private final int sourceDeviceId; + + public static class XmppAxolotlPlaintextMessage { + private final String plaintext; + private final String fingerprint; + + public XmppAxolotlPlaintextMessage(String plaintext, String fingerprint) { + this.plaintext = plaintext; + this.fingerprint = fingerprint; + } + + public String getPlaintext() { + return plaintext; + } + + + public String getFingerprint() { + return fingerprint; + } + } + + public static class XmppAxolotlKeyTransportMessage { + private final String fingerprint; + private final byte[] key; + private final byte[] iv; + + public XmppAxolotlKeyTransportMessage(String fingerprint, byte[] key, byte[] iv) { + this.fingerprint = fingerprint; + this.key = key; + this.iv = iv; + } + + public String getFingerprint() { + return fingerprint; + } + + public byte[] getKey() { + return key; + } + + public byte[] getIv() { + return iv; + } + } + + private XmppAxolotlMessage(final Element axolotlMessage, final Jid from) throws IllegalArgumentException { + this.from = from; + Element header = axolotlMessage.findChild(HEADER); + this.sourceDeviceId = Integer.parseInt(header.getAttribute(SOURCEID)); + List<Element> keyElements = header.getChildren(); + this.keys = new HashMap<>(keyElements.size()); + for (Element keyElement : keyElements) { + switch (keyElement.getName()) { + case KEYTAG: + try { + Integer recipientId = Integer.parseInt(keyElement.getAttribute(REMOTEID)); + byte[] key = Base64.decode(keyElement.getContent(), Base64.DEFAULT); + this.keys.put(recipientId, key); + } catch (NumberFormatException e) { + throw new IllegalArgumentException(e); + } + break; + case IVTAG: + if (this.iv != null) { + throw new IllegalArgumentException("Duplicate iv entry"); + } + iv = Base64.decode(keyElement.getContent(), Base64.DEFAULT); + break; + default: + Log.w(Config.LOGTAG, "Unexpected element in header: " + keyElement.toString()); + break; + } + } + Element payloadElement = axolotlMessage.findChild(PAYLOAD); + if (payloadElement != null) { + ciphertext = Base64.decode(payloadElement.getContent(), Base64.DEFAULT); + } + } + + public XmppAxolotlMessage(Jid from, int sourceDeviceId) { + this.from = from; + this.sourceDeviceId = sourceDeviceId; + this.keys = new HashMap<>(); + this.iv = generateIv(); + this.innerKey = generateKey(); + } + + public static XmppAxolotlMessage fromElement(Element element, Jid from) { + return new XmppAxolotlMessage(element, from); + } + + private static byte[] generateKey() { + try { + KeyGenerator generator = KeyGenerator.getInstance(KEYTYPE); + generator.init(128); + return generator.generateKey().getEncoded(); + } catch (NoSuchAlgorithmException e) { + Log.e(Config.LOGTAG, e.getMessage()); + return null; + } + } + + private static byte[] generateIv() { + SecureRandom random = new SecureRandom(); + byte[] iv = new byte[16]; + random.nextBytes(iv); + return iv; + } + + public void encrypt(String plaintext) throws CryptoFailedException { + try { + SecretKey secretKey = new SecretKeySpec(innerKey, KEYTYPE); + IvParameterSpec ivSpec = new IvParameterSpec(iv); + Cipher cipher = Cipher.getInstance(CIPHERMODE, PROVIDER); + cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec); + this.innerKey = secretKey.getEncoded(); + this.ciphertext = cipher.doFinal(plaintext.getBytes()); + } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException + | IllegalBlockSizeException | BadPaddingException | NoSuchProviderException + | InvalidAlgorithmParameterException e) { + throw new CryptoFailedException(e); + } + } + + public Jid getFrom() { + return this.from; + } + + public int getSenderDeviceId() { + return sourceDeviceId; + } + + public byte[] getCiphertext() { + return ciphertext; + } + + public void addDevice(XmppAxolotlSession session) { + byte[] key = session.processSending(innerKey); + if (key != null) { + keys.put(session.getRemoteAddress().getDeviceId(), key); + } + } + + public byte[] getInnerKey() { + return innerKey; + } + + public byte[] getIV() { + return this.iv; + } + + public Element toElement() { + Element encryptionElement = new Element(CONTAINERTAG, AxolotlService.PEP_PREFIX); + Element headerElement = encryptionElement.addChild(HEADER); + headerElement.setAttribute(SOURCEID, sourceDeviceId); + for (Map.Entry<Integer, byte[]> keyEntry : keys.entrySet()) { + Element keyElement = new Element(KEYTAG); + keyElement.setAttribute(REMOTEID, keyEntry.getKey()); + keyElement.setContent(Base64.encodeToString(keyEntry.getValue(), Base64.DEFAULT)); + headerElement.addChild(keyElement); + } + headerElement.addChild(IVTAG).setContent(Base64.encodeToString(iv, Base64.DEFAULT)); + if (ciphertext != null) { + Element payload = encryptionElement.addChild(PAYLOAD); + payload.setContent(Base64.encodeToString(ciphertext, Base64.DEFAULT)); + } + return encryptionElement; + } + + private byte[] unpackKey(XmppAxolotlSession session, Integer sourceDeviceId) { + byte[] encryptedKey = keys.get(sourceDeviceId); + return (encryptedKey != null) ? session.processReceiving(encryptedKey) : null; + } + + public XmppAxolotlKeyTransportMessage getParameters(XmppAxolotlSession session, Integer sourceDeviceId) { + byte[] key = unpackKey(session, sourceDeviceId); + return (key != null) + ? new XmppAxolotlKeyTransportMessage(session.getFingerprint(), key, getIV()) + : null; + } + + public XmppAxolotlPlaintextMessage decrypt(XmppAxolotlSession session, Integer sourceDeviceId) throws CryptoFailedException { + XmppAxolotlPlaintextMessage plaintextMessage = null; + byte[] key = unpackKey(session, sourceDeviceId); + if (key != null) { + try { + Cipher cipher = Cipher.getInstance(CIPHERMODE, PROVIDER); + SecretKeySpec keySpec = new SecretKeySpec(key, KEYTYPE); + IvParameterSpec ivSpec = new IvParameterSpec(iv); + + cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec); + + String plaintext = new String(cipher.doFinal(ciphertext)); + plaintextMessage = new XmppAxolotlPlaintextMessage(plaintext, session.getFingerprint()); + + } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException + | InvalidAlgorithmParameterException | IllegalBlockSizeException + | BadPaddingException | NoSuchProviderException e) { + throw new CryptoFailedException(e); + } + } + return plaintextMessage; + } +} diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java new file mode 100644 index 00000000..b713eb5f --- /dev/null +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java @@ -0,0 +1,221 @@ +package eu.siacs.conversations.crypto.axolotl; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.Log; + +import org.whispersystems.libaxolotl.AxolotlAddress; +import org.whispersystems.libaxolotl.DuplicateMessageException; +import org.whispersystems.libaxolotl.IdentityKey; +import org.whispersystems.libaxolotl.InvalidKeyException; +import org.whispersystems.libaxolotl.InvalidKeyIdException; +import org.whispersystems.libaxolotl.InvalidMessageException; +import org.whispersystems.libaxolotl.InvalidVersionException; +import org.whispersystems.libaxolotl.LegacyMessageException; +import org.whispersystems.libaxolotl.NoSessionException; +import org.whispersystems.libaxolotl.SessionCipher; +import org.whispersystems.libaxolotl.UntrustedIdentityException; +import org.whispersystems.libaxolotl.protocol.CiphertextMessage; +import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage; +import org.whispersystems.libaxolotl.protocol.WhisperMessage; + +import java.util.HashMap; +import java.util.Map; + +import eu.siacs.conversations.Config; +import eu.siacs.conversations.entities.Account; + +public class XmppAxolotlSession { + private final SessionCipher cipher; + private final SQLiteAxolotlStore sqLiteAxolotlStore; + private final AxolotlAddress remoteAddress; + private final Account account; + private IdentityKey identityKey; + private Integer preKeyId = null; + private boolean fresh = true; + + public enum Trust { + UNDECIDED(0), + TRUSTED(1), + UNTRUSTED(2), + COMPROMISED(3), + INACTIVE_TRUSTED(4), + INACTIVE_UNDECIDED(5), + INACTIVE_UNTRUSTED(6), + TRUSTED_X509(7), + INACTIVE_TRUSTED_X509(8); + + private static final Map<Integer, Trust> trustsByValue = new HashMap<>(); + + static { + for (Trust trust : Trust.values()) { + trustsByValue.put(trust.getCode(), trust); + } + } + + private final int code; + + Trust(int code) { + this.code = code; + } + + public int getCode() { + return this.code; + } + + public String toString() { + switch (this) { + case UNDECIDED: + return "Trust undecided " + getCode(); + case TRUSTED: + return "Trusted " + getCode(); + case COMPROMISED: + return "Compromised " + getCode(); + case INACTIVE_TRUSTED: + return "Inactive (Trusted)" + getCode(); + case INACTIVE_UNDECIDED: + return "Inactive (Undecided)" + getCode(); + case INACTIVE_UNTRUSTED: + return "Inactive (Untrusted)" + getCode(); + case TRUSTED_X509: + return "Trusted (X509) " + getCode(); + case INACTIVE_TRUSTED_X509: + return "Inactive (Trusted (X509)) " + getCode(); + case UNTRUSTED: + default: + return "Untrusted " + getCode(); + } + } + + public static Trust fromBoolean(Boolean trusted) { + return trusted ? TRUSTED : UNTRUSTED; + } + + public static Trust fromCode(int code) { + return trustsByValue.get(code); + } + + public boolean trusted() { + return this == TRUSTED_X509 || this == TRUSTED; + } + + public boolean trustedInactive() { + return this == INACTIVE_TRUSTED_X509 || this == INACTIVE_TRUSTED; + } + } + + public XmppAxolotlSession(Account account, SQLiteAxolotlStore store, AxolotlAddress remoteAddress, IdentityKey identityKey) { + this(account, store, remoteAddress); + this.identityKey = identityKey; + } + + public XmppAxolotlSession(Account account, SQLiteAxolotlStore store, AxolotlAddress remoteAddress) { + this.cipher = new SessionCipher(store, remoteAddress); + this.remoteAddress = remoteAddress; + this.sqLiteAxolotlStore = store; + this.account = account; + } + + public Integer getPreKeyId() { + return preKeyId; + } + + public void resetPreKeyId() { + + preKeyId = null; + } + + public String getFingerprint() { + return identityKey == null ? null : identityKey.getFingerprint().replaceAll("\\s", ""); + } + + public IdentityKey getIdentityKey() { + return identityKey; + } + + public AxolotlAddress getRemoteAddress() { + return remoteAddress; + } + + public boolean isFresh() { + return fresh; + } + + public void setNotFresh() { + this.fresh = false; + } + + protected void setTrust(Trust trust) { + sqLiteAxolotlStore.setFingerprintTrust(getFingerprint(), trust); + } + + protected Trust getTrust() { + Trust trust = sqLiteAxolotlStore.getFingerprintTrust(getFingerprint()); + return (trust == null) ? Trust.UNDECIDED : trust; + } + + @Nullable + public byte[] processReceiving(byte[] encryptedKey) { + byte[] plaintext = null; + Trust trust = getTrust(); + switch (trust) { + case INACTIVE_TRUSTED: + case UNDECIDED: + case UNTRUSTED: + case TRUSTED: + case INACTIVE_TRUSTED_X509: + case TRUSTED_X509: + try { + try { + PreKeyWhisperMessage message = new PreKeyWhisperMessage(encryptedKey); + Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "PreKeyWhisperMessage received, new session ID:" + message.getSignedPreKeyId() + "/" + message.getPreKeyId()); + IdentityKey msgIdentityKey = message.getIdentityKey(); + if (this.identityKey != null && !this.identityKey.equals(msgIdentityKey)) { + Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Had session with fingerprint " + this.getFingerprint() + ", received message with fingerprint " + msgIdentityKey.getFingerprint()); + } else { + this.identityKey = msgIdentityKey; + plaintext = cipher.decrypt(message); + if (message.getPreKeyId().isPresent()) { + preKeyId = message.getPreKeyId().get(); + } + } + } catch (InvalidMessageException | InvalidVersionException e) { + Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "WhisperMessage received"); + WhisperMessage message = new WhisperMessage(encryptedKey); + plaintext = cipher.decrypt(message); + } catch (InvalidKeyException | InvalidKeyIdException | UntrustedIdentityException e) { + Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error decrypting axolotl header, " + e.getClass().getName() + ": " + e.getMessage()); + } + } catch (LegacyMessageException | InvalidMessageException | DuplicateMessageException | NoSessionException e) { + Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error decrypting axolotl header, " + e.getClass().getName() + ": " + e.getMessage()); + } + + if (plaintext != null) { + if (trust == Trust.INACTIVE_TRUSTED) { + setTrust(Trust.TRUSTED); + } else if (trust == Trust.INACTIVE_TRUSTED_X509) { + setTrust(Trust.TRUSTED_X509); + } + } + + break; + + case COMPROMISED: + default: + // ignore + break; + } + return plaintext; + } + + @Nullable + public byte[] processSending(@NonNull byte[] outgoingMessage) { + Trust trust = getTrust(); + if (trust.trusted()) { + CiphertextMessage ciphertextMessage = cipher.encrypt(outgoingMessage); + return ciphertextMessage.serialize(); + } else { + return null; + } + } +} diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/External.java b/src/main/java/eu/siacs/conversations/crypto/sasl/External.java new file mode 100644 index 00000000..df92898c --- /dev/null +++ b/src/main/java/eu/siacs/conversations/crypto/sasl/External.java @@ -0,0 +1,29 @@ +package eu.siacs.conversations.crypto.sasl; + +import android.util.Base64; +import java.security.SecureRandom; + +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.xml.TagWriter; + +public class External extends SaslMechanism { + + public External(TagWriter tagWriter, Account account, SecureRandom rng) { + super(tagWriter, account, rng); + } + + @Override + public int getPriority() { + return 25; + } + + @Override + public String getMechanism() { + return "EXTERNAL"; + } + + @Override + public String getClientFirstMessage() { + return Base64.encodeToString(account.getJid().toBareJid().toString().getBytes(),Base64.NO_WRAP); + } +} diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/SaslMechanism.java b/src/main/java/eu/siacs/conversations/crypto/sasl/SaslMechanism.java index 14d8b944..5b4b99ef 100644 --- a/src/main/java/eu/siacs/conversations/crypto/sasl/SaslMechanism.java +++ b/src/main/java/eu/siacs/conversations/crypto/sasl/SaslMechanism.java @@ -11,7 +11,7 @@ public abstract class SaslMechanism { final protected Account account; final protected SecureRandom rng; - protected static enum State { + protected enum State { INITIAL, AUTH_TEXT_SENT, RESPONSE_SENT, diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha1.java b/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha1.java index c95a62df..3a05446c 100644 --- a/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha1.java +++ b/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha1.java @@ -21,7 +21,6 @@ public class ScramSha1 extends SaslMechanism { // TODO: When channel binding (SCRAM-SHA1-PLUS) is supported in future, generalize this to indicate support and/or usage. final private static String GS2_HEADER = "n,,"; private String clientFirstMessageBare; - private byte[] serverFirstMessage; final private String clientNonce; private byte[] serverSignature = null; private static HMac HMAC; @@ -101,7 +100,10 @@ public class ScramSha1 extends SaslMechanism { public String getResponse(final String challenge) throws AuthenticationException { switch (state) { case AUTH_TEXT_SENT: - serverFirstMessage = Base64.decode(challenge, Base64.DEFAULT); + if (challenge == null) { + throw new AuthenticationException("challenge can not be null"); + } + byte[] serverFirstMessage = Base64.decode(challenge, Base64.DEFAULT); final Tokenizer tokenizer = new Tokenizer(serverFirstMessage); String nonce = ""; int iterationCount = -1; diff --git a/src/main/java/eu/siacs/conversations/entities/Account.java b/src/main/java/eu/siacs/conversations/entities/Account.java index 90c10199..0f37ea61 100644 --- a/src/main/java/eu/siacs/conversations/entities/Account.java +++ b/src/main/java/eu/siacs/conversations/entities/Account.java @@ -4,6 +4,7 @@ import android.content.ContentValues; import android.database.Cursor; import android.os.SystemClock; +import eu.siacs.conversations.crypto.PgpDecryptionService; import net.java.otr4j.crypto.OtrCryptoEngineImpl; import net.java.otr4j.crypto.OtrCryptoException; @@ -20,6 +21,7 @@ import java.util.concurrent.CopyOnWriteArraySet; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.OtrService; +import eu.siacs.conversations.crypto.axolotl.AxolotlService; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.xmpp.XmppConnection; import eu.siacs.conversations.xmpp.jid.InvalidJidException; @@ -36,6 +38,9 @@ public class Account extends AbstractEntity { public static final String ROSTERVERSION = "rosterversion"; public static final String KEYS = "keys"; public static final String AVATAR = "avatar"; + public static final String DISPLAY_NAME = "display_name"; + public static final String HOSTNAME = "hostname"; + public static final String PORT = "port"; public static final String PINNED_MECHANISM_KEY = "pinned_mechanism"; @@ -48,7 +53,23 @@ public class Account extends AbstractEntity { return xmppConnection != null && xmppConnection.getFeatures().httpUpload(); } - public static enum State { + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + public String getDisplayName() { + return displayName; + } + + public XmppConnection.Identity getServerIdentity() { + if (xmppConnection == null) { + return XmppConnection.Identity.UNKNOWN; + } else { + return xmppConnection.getServerIdentity(); + } + } + + public enum State { DISABLED, OFFLINE, CONNECTING, @@ -61,7 +82,8 @@ public class Account extends AbstractEntity { REGISTRATION_SUCCESSFUL, REGISTRATION_NOT_SUPPORTED(true), SECURITY_ERROR(true), - INCOMPATIBLE_SERVER(true); + INCOMPATIBLE_SERVER(true), + TOR_NOT_AVAILABLE(true); private final boolean isError; @@ -105,6 +127,8 @@ public class Account extends AbstractEntity { return R.string.account_status_security_error; case INCOMPATIBLE_SERVER: return R.string.account_status_incompatible_server; + case TOR_NOT_AVAILABLE: + return R.string.account_status_tor_unavailable; default: return R.string.account_status_unknown; } @@ -113,6 +137,10 @@ public class Account extends AbstractEntity { public List<Conversation> pendingConferenceJoins = new CopyOnWriteArrayList<>(); public List<Conversation> pendingConferenceLeaves = new CopyOnWriteArrayList<>(); + + private static final String KEY_PGP_SIGNATURE = "pgp_signature"; + private static final String KEY_PGP_ID = "pgp_id"; + protected Jid jid; protected String password; protected int options = 0; @@ -120,15 +148,19 @@ public class Account extends AbstractEntity { protected State status = State.OFFLINE; protected JSONObject keys = new JSONObject(); protected String avatar; + protected String displayName = null; + protected String hostname = null; + protected int port = 5222; protected boolean online = false; private OtrService mOtrService = null; + private AxolotlService axolotlService = null; + private PgpDecryptionService pgpDecryptionService = null; private XmppConnection xmppConnection = null; private long mEndGracePeriod = 0L; private String otrFingerprint; private final Roster roster = new Roster(this); private List<Bookmark> bookmarks = new CopyOnWriteArrayList<>(); private final Collection<Jid> blocklist = new CopyOnWriteArraySet<>(); - private XmppConnectionService mXmppConnectionService; public Account() { this.uuid = "0"; @@ -136,12 +168,12 @@ public class Account extends AbstractEntity { public Account(final Jid jid, final String password) { this(java.util.UUID.randomUUID().toString(), jid, - password, 0, null, "", null); + password, 0, null, "", null, null, null, 5222); } - public Account(final String uuid, final Jid jid, + private Account(final String uuid, final Jid jid, final String password, final int options, final String rosterVersion, final String keys, - final String avatar) { + final String avatar, String displayName, String hostname, int port) { this.uuid = uuid; this.jid = jid; if (jid.isBareJid()) { @@ -156,6 +188,9 @@ public class Account extends AbstractEntity { this.keys = new JSONObject(); } this.avatar = avatar; + this.displayName = displayName; + this.hostname = hostname; + this.port = port; } public static Account fromCursor(final Cursor cursor) { @@ -171,7 +206,10 @@ public class Account extends AbstractEntity { cursor.getInt(cursor.getColumnIndex(OPTIONS)), cursor.getString(cursor.getColumnIndex(ROSTERVERSION)), cursor.getString(cursor.getColumnIndex(KEYS)), - cursor.getString(cursor.getColumnIndex(AVATAR))); + cursor.getString(cursor.getColumnIndex(AVATAR)), + cursor.getString(cursor.getColumnIndex(DISPLAY_NAME)), + cursor.getString(cursor.getColumnIndex(HOSTNAME)), + cursor.getInt(cursor.getColumnIndex(PORT))); } public boolean isOptionSet(final int option) { @@ -190,18 +228,14 @@ public class Account extends AbstractEntity { return jid.getLocalpart(); } - public void setUsername(final String username) throws InvalidJidException { - jid = Jid.fromParts(username, jid.getDomainpart(), jid.getResourcepart()); + public void setJid(final Jid jid) { + this.jid = jid; } public Jid getServer() { return jid.toDomainJid(); } - public void setServer(final String server) throws InvalidJidException { - jid = Jid.fromParts(jid.getLocalpart(), server, jid.getResourcepart()); - } - public String getPassword() { return password; } @@ -210,6 +244,26 @@ public class Account extends AbstractEntity { this.password = password; } + public void setHostname(String hostname) { + this.hostname = hostname; + } + + public String getHostname() { + return this.hostname == null ? "" : this.hostname; + } + + public boolean isOnion() { + return getServer().toString().toLowerCase().endsWith(".onion"); + } + + public void setPort(int port) { + this.port = port; + } + + public int getPort() { + return this.port; + } + public State getStatus() { if (isOptionSet(OPTION_DISABLED)) { return State.DISABLED; @@ -227,7 +281,7 @@ public class Account extends AbstractEntity { } public boolean hasErrorStatus() { - return getXmppConnection() != null && getStatus().isError() && getXmppConnection().getAttempt() >= 2; + return getXmppConnection() != null && getStatus().isError() && getXmppConnection().getAttempt() >= 3; } public String getResource() { @@ -255,6 +309,10 @@ public class Account extends AbstractEntity { return keys; } + public String getKey(final String name) { + return this.keys.optString(name, null); + } + public boolean setKey(final String keyName, final String keyValue) { try { this.keys.put(keyName, keyValue); @@ -264,6 +322,14 @@ public class Account extends AbstractEntity { } } + public boolean setPrivateKeyAlias(String alias) { + return setKey("private_key_alias", alias); + } + + public String getPrivateKeyAlias() { + return getKey("private_key_alias"); + } + @Override public ContentValues getContentValues() { final ContentValues values = new ContentValues(); @@ -275,18 +341,33 @@ public class Account extends AbstractEntity { values.put(KEYS, this.keys.toString()); values.put(ROSTERVERSION, rosterVersion); values.put(AVATAR, avatar); + values.put(DISPLAY_NAME, displayName); + values.put(HOSTNAME, hostname); + values.put(PORT, port); return values; } + public AxolotlService getAxolotlService() { + return axolotlService; + } + public void initAccountServices(final XmppConnectionService context) { - this.mXmppConnectionService = context; this.mOtrService = new OtrService(context, this); + this.axolotlService = new AxolotlService(this, context); + if (xmppConnection != null) { + xmppConnection.addOnAdvancedStreamFeaturesAvailableListener(axolotlService); + } + this.pgpDecryptionService = new PgpDecryptionService(context); } public OtrService getOtrService() { return this.mOtrService; } + public PgpDecryptionService getPgpDecryptionService() { + return pgpDecryptionService; + } + public XmppConnection getXmppConnection() { return this.xmppConnection; } @@ -332,17 +413,56 @@ public class Account extends AbstractEntity { } public String getPgpSignature() { - if (keys.has("pgp_signature")) { - try { - return keys.getString("pgp_signature"); - } catch (final JSONException e) { + try { + if (keys.has(KEY_PGP_SIGNATURE) && !"null".equals(keys.getString(KEY_PGP_SIGNATURE))) { + return keys.getString(KEY_PGP_SIGNATURE); + } else { return null; } - } else { + } catch (final JSONException e) { return null; } } + public boolean setPgpSignature(String signature) { + try { + keys.put(KEY_PGP_SIGNATURE, signature); + } catch (JSONException e) { + return false; + } + return true; + } + + public boolean unsetPgpSignature() { + try { + keys.put(KEY_PGP_SIGNATURE, JSONObject.NULL); + } catch (JSONException e) { + return false; + } + return true; + } + + public long getPgpId() { + if (keys.has(KEY_PGP_ID)) { + try { + return keys.getLong(KEY_PGP_ID); + } catch (JSONException e) { + return -1; + } + } else { + return -1; + } + } + + public boolean setPgpSignId(long pgpID) { + try { + keys.put(KEY_PGP_ID, pgpID); + } catch (JSONException e) { + return false; + } + return true; + } + public Roster getRoster() { return this.roster; } @@ -420,8 +540,4 @@ public class Account extends AbstractEntity { public boolean isOnlineAndConnected() { return this.getStatus() == State.ONLINE && this.getXmppConnection() != null; } - - public XmppConnectionService getXmppConnectionService() { - return mXmppConnectionService; - } } diff --git a/src/main/java/eu/siacs/conversations/entities/Bookmark.java b/src/main/java/eu/siacs/conversations/entities/Bookmark.java index 06c46c1c..d0a951b3 100644 --- a/src/main/java/eu/siacs/conversations/entities/Bookmark.java +++ b/src/main/java/eu/siacs/conversations/entities/Bookmark.java @@ -52,8 +52,8 @@ public class Bookmark extends Element implements ListItem { if (this.mJoinedConversation != null && (this.mJoinedConversation.getMucOptions().getSubject() != null)) { return this.mJoinedConversation.getMucOptions().getSubject(); - } else if (getName() != null) { - return getName(); + } else if (getBookmarkName() != null) { + return getBookmarkName(); } else { return this.getJid().getLocalpart(); } @@ -141,12 +141,18 @@ public class Bookmark extends Element implements ListItem { this.mJoinedConversation = conversation; } - public String getName() { + public String getBookmarkName() { return this.getAttribute("name"); } - public void setName(String name) { - this.name = name; + public boolean setBookmarkName(String name) { + String before = getBookmarkName(); + if (name != null && !name.equals(before)) { + this.setAttribute("name", name); + return true; + } else { + return false; + } } public void unregisterConversation() { diff --git a/src/main/java/eu/siacs/conversations/entities/Contact.java b/src/main/java/eu/siacs/conversations/entities/Contact.java index 81f6568b..6d252376 100644 --- a/src/main/java/eu/siacs/conversations/entities/Contact.java +++ b/src/main/java/eu/siacs/conversations/entities/Contact.java @@ -12,6 +12,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Locale; +import eu.siacs.conversations.Config; import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xmpp.jid.InvalidJidException; @@ -105,7 +106,9 @@ public class Contact implements ListItem, Blockable { } public String getDisplayName() { - if (this.systemName != null) { + if (this.presenceName != null && Config.X509_VERIFICATION) { + return this.presenceName; + } else if (this.systemName != null) { return this.systemName; } else if (this.serverName != null) { return this.serverName; @@ -189,20 +192,22 @@ public class Contact implements ListItem, Blockable { } public ContentValues getContentValues() { - final ContentValues values = new ContentValues(); - values.put(ACCOUNT, accountUuid); - values.put(SYSTEMNAME, systemName); - values.put(SERVERNAME, serverName); - values.put(JID, jid.toString()); - values.put(OPTIONS, subscription); - values.put(SYSTEMACCOUNT, systemAccount); - values.put(PHOTOURI, photoUri); - values.put(KEYS, keys.toString()); - values.put(AVATAR, avatar == null ? null : avatar.getFilename()); - values.put(LAST_PRESENCE, lastseen.presence); - values.put(LAST_TIME, lastseen.time); - values.put(GROUPS, groups.toString()); - return values; + synchronized (this.keys) { + final ContentValues values = new ContentValues(); + values.put(ACCOUNT, accountUuid); + values.put(SYSTEMNAME, systemName); + values.put(SERVERNAME, serverName); + values.put(JID, jid.toString()); + values.put(OPTIONS, subscription); + values.put(SYSTEMACCOUNT, systemAccount); + values.put(PHOTOURI, photoUri); + values.put(KEYS, keys.toString()); + values.put(AVATAR, avatar == null ? null : avatar.getFilename()); + values.put(LAST_PRESENCE, lastseen.presence); + values.put(LAST_TIME, lastseen.time); + values.put(GROUPS, groups.toString()); + return values; + } } public int getSubscription() { @@ -287,60 +292,65 @@ public class Contact implements ListItem, Blockable { } public ArrayList<String> getOtrFingerprints() { - final ArrayList<String> fingerprints = new ArrayList<String>(); - try { - if (this.keys.has("otr_fingerprints")) { - final JSONArray prints = this.keys.getJSONArray("otr_fingerprints"); - for (int i = 0; i < prints.length(); ++i) { - final String print = prints.isNull(i) ? null : prints.getString(i); - if (print != null && !print.isEmpty()) { - fingerprints.add(prints.getString(i)); + synchronized (this.keys) { + final ArrayList<String> fingerprints = new ArrayList<String>(); + try { + if (this.keys.has("otr_fingerprints")) { + final JSONArray prints = this.keys.getJSONArray("otr_fingerprints"); + for (int i = 0; i < prints.length(); ++i) { + final String print = prints.isNull(i) ? null : prints.getString(i); + if (print != null && !print.isEmpty()) { + fingerprints.add(prints.getString(i)); + } } } - } - } catch (final JSONException ignored) { + } catch (final JSONException ignored) { + } + return fingerprints; } - return fingerprints; } - public boolean addOtrFingerprint(String print) { - if (getOtrFingerprints().contains(print)) { - return false; - } - try { - JSONArray fingerprints; - if (!this.keys.has("otr_fingerprints")) { - fingerprints = new JSONArray(); - - } else { - fingerprints = this.keys.getJSONArray("otr_fingerprints"); + synchronized (this.keys) { + if (getOtrFingerprints().contains(print)) { + return false; + } + try { + JSONArray fingerprints; + if (!this.keys.has("otr_fingerprints")) { + fingerprints = new JSONArray(); + } else { + fingerprints = this.keys.getJSONArray("otr_fingerprints"); + } + fingerprints.put(print); + this.keys.put("otr_fingerprints", fingerprints); + return true; + } catch (final JSONException ignored) { + return false; } - fingerprints.put(print); - this.keys.put("otr_fingerprints", fingerprints); - return true; - } catch (final JSONException ignored) { - return false; } } public long getPgpKeyId() { - if (this.keys.has("pgp_keyid")) { - try { - return this.keys.getLong("pgp_keyid"); - } catch (JSONException e) { + synchronized (this.keys) { + if (this.keys.has("pgp_keyid")) { + try { + return this.keys.getLong("pgp_keyid"); + } catch (JSONException e) { + return 0; + } + } else { return 0; } - } else { - return 0; } } public void setPgpKeyId(long keyId) { - try { - this.keys.put("pgp_keyid", keyId); - } catch (final JSONException ignored) { - + synchronized (this.keys) { + try { + this.keys.put("pgp_keyid", keyId); + } catch (final JSONException ignored) { + } } } @@ -447,24 +457,26 @@ public class Contact implements ListItem, Blockable { } public boolean deleteOtrFingerprint(String fingerprint) { - boolean success = false; - try { - if (this.keys.has("otr_fingerprints")) { - JSONArray newPrints = new JSONArray(); - JSONArray oldPrints = this.keys - .getJSONArray("otr_fingerprints"); - for (int i = 0; i < oldPrints.length(); ++i) { - if (!oldPrints.getString(i).equals(fingerprint)) { - newPrints.put(oldPrints.getString(i)); - } else { - success = true; + synchronized (this.keys) { + boolean success = false; + try { + if (this.keys.has("otr_fingerprints")) { + JSONArray newPrints = new JSONArray(); + JSONArray oldPrints = this.keys + .getJSONArray("otr_fingerprints"); + for (int i = 0; i < oldPrints.length(); ++i) { + if (!oldPrints.getString(i).equals(fingerprint)) { + newPrints.put(oldPrints.getString(i)); + } else { + success = true; + } } + this.keys.put("otr_fingerprints", newPrints); } - this.keys.put("otr_fingerprints", newPrints); + return success; + } catch (JSONException e) { + return false; } - return success; - } catch (JSONException e) { - return false; } } diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java index 1f3d35e7..38791d28 100644 --- a/src/main/java/eu/siacs/conversations/entities/Conversation.java +++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java @@ -21,6 +21,7 @@ import java.util.List; import eu.siacs.conversations.Config; import de.thedevstack.conversationsplus.ConversationsPlusPreferences; +import eu.siacs.conversations.crypto.axolotl.AxolotlService; import eu.siacs.conversations.xmpp.chatstate.ChatState; import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; @@ -47,7 +48,7 @@ public class Conversation extends AbstractEntity implements Blockable { public static final String ATTRIBUTE_NEXT_ENCRYPTION = "next_encryption"; public static final String ATTRIBUTE_MUC_PASSWORD = "muc_password"; public static final String ATTRIBUTE_MUTED_TILL = "muted_till"; - public static final String ATTRIBUTE_LAST_MESSAGE_TRANSMITTED = "last_message_transmitted"; + public static final String ATTRIBUTE_ALWAYS_NOTIFY = "always_notify"; private String name; private String contactUuid; @@ -112,6 +113,16 @@ public class Conversation extends AbstractEntity implements Blockable { } } + public void findUnreadMessages(OnMessageFound onMessageFound) { + synchronized (this.messages) { + for(Message message : this.messages) { + if (!message.isRead()) { + onMessageFound.onMessageFound(message); + } + } + } + } + public void findMessagesWithFiles(final OnMessageFound onMessageFound) { synchronized (this.messages) { for (final Message message : this.messages) { @@ -180,13 +191,13 @@ public class Conversation extends AbstractEntity implements Blockable { } } - public void findUnsentMessagesWithOtrEncryption(OnMessageFound onMessageFound) { + public void findUnsentMessagesWithEncryption(int encryptionType, OnMessageFound onMessageFound) { synchronized (this.messages) { for (Message message : this.messages) { if ((message.getStatus() == Message.STATUS_UNSEND || message.getStatus() == Message.STATUS_WAITING) - && (message.getEncryption() == Message.ENCRYPTION_OTR)) { + && (message.getEncryption() == encryptionType)) { onMessageFound.onMessageFound(message); - } + } } } } @@ -202,14 +213,25 @@ public class Conversation extends AbstractEntity implements Blockable { } } - public Message findSentMessageWithUuid(String uuid) { + public Message findSentMessageWithUuidOrRemoteId(String id) { synchronized (this.messages) { for (Message message : this.messages) { - if (uuid.equals(message.getUuid()) - || (message.getStatus() >= Message.STATUS_SEND && uuid - .equals(message.getRemoteMsgId()))) { + if (id.equals(message.getUuid()) + || (message.getStatus() >= Message.STATUS_SEND + && id.equals(message.getRemoteMsgId()))) { return message; - } + } + } + } + return null; + } + + public Message findSentMessageWithUuid(String id) { + synchronized (this.messages) { + for (Message message : this.messages) { + if (id.equals(message.getUuid())) { + return message; + } } } return null; @@ -256,9 +278,8 @@ public class Conversation extends AbstractEntity implements Blockable { } } - public interface OnMessageFound { - public void onMessageFound(final Message message); + void onMessageFound(final Message message); } public Conversation(final String name, final Account account, final Jid contactJid, @@ -291,13 +312,17 @@ public class Conversation extends AbstractEntity implements Blockable { return (this.messages.size() == 0) || this.messages.get(this.messages.size() - 1).isRead(); } - public void markRead() { - for (int i = this.messages.size() - 1; i >= 0; --i) { - if (messages.get(i).isRead()) { - break; + public List<Message> markRead() { + final List<Message> unread = new ArrayList<>(); + synchronized (this.messages) { + for(Message message : this.messages) { + if (!message.isRead()) { + message.markRead(); + unread.add(message); + } } - this.messages.get(i).markRead(); } + return unread; } public Message getLatestMarkableMessage() { @@ -330,8 +355,8 @@ public class Conversation extends AbstractEntity implements Blockable { if (getMode() == MODE_MULTI) { if (getMucOptions().getSubject() != null) { return getMucOptions().getSubject(); - } else if (bookmark != null && bookmark.getName() != null) { - return bookmark.getName(); + } else if (bookmark != null && bookmark.getBookmarkName() != null) { + return bookmark.getBookmarkName(); } else { String generatedName = getMucOptions().createNameFromParticipants(); if (generatedName != null) { @@ -520,6 +545,13 @@ public class Conversation extends AbstractEntity implements Blockable { return getContact().getOtrFingerprints().contains(getOtrFingerprint()); } + /** + * short for is Private and Non-anonymous + */ + private boolean isPnNA() { + return mode == MODE_SINGLE || (getMucOptions().membersOnly() && getMucOptions().nonanonymous()); + } + public synchronized MucOptions getMucOptions() { if (this.mucOptions == null) { this.mucOptions = new MucOptions(this); @@ -543,42 +575,66 @@ public class Conversation extends AbstractEntity implements Blockable { return this.nextCounterpart; } - public int getLatestEncryption() { - int latestEncryption = this.getLatestMessage().getEncryption(); - if ((latestEncryption == Message.ENCRYPTION_DECRYPTED) - || (latestEncryption == Message.ENCRYPTION_DECRYPTION_FAILED)) { - return Message.ENCRYPTION_PGP; - } else { - return latestEncryption; + private int getMostRecentlyUsedOutgoingEncryption() { + synchronized (this.messages) { + for(int i = this.messages.size() -1; i >= 0; --i) { + final Message m = this.messages.get(i); + if (!m.isCarbon() && m.getStatus() != Message.STATUS_RECEIVED) { + final int e = m.getEncryption(); + if (e == Message.ENCRYPTION_DECRYPTED || e == Message.ENCRYPTION_DECRYPTION_FAILED) { + return Message.ENCRYPTION_PGP; + } else { + return e; + } + } + } } + return Message.ENCRYPTION_NONE; } - public int getNextEncryption(boolean force) { - int next = this.getIntAttribute(ATTRIBUTE_NEXT_ENCRYPTION, -1); - if (next == -1) { - int latest = this.getLatestEncryption(); - if (latest == Message.ENCRYPTION_NONE) { - if (force && getMode() == MODE_SINGLE) { - return Message.ENCRYPTION_OTR; - } else if (getContact().getPresences().size() == 1) { - if (getContact().getOtrFingerprints().size() >= 1) { - return Message.ENCRYPTION_OTR; + private int getMostRecentlyUsedIncomingEncryption() { + synchronized (this.messages) { + for(int i = this.messages.size() -1; i >= 0; --i) { + final Message m = this.messages.get(i); + if (m.getStatus() == Message.STATUS_RECEIVED) { + final int e = m.getEncryption(); + if (e == Message.ENCRYPTION_DECRYPTED || e == Message.ENCRYPTION_DECRYPTION_FAILED) { + return Message.ENCRYPTION_PGP; } else { - return latest; + return e; } + } + } + } + return Message.ENCRYPTION_NONE; + } + + public int getNextEncryption() { + final AxolotlService axolotlService = getAccount().getAxolotlService(); + int next = this.getIntAttribute(ATTRIBUTE_NEXT_ENCRYPTION, -1); + if (next == -1) { + if (Config.X509_VERIFICATION && mode == MODE_SINGLE) { + if (axolotlService != null && axolotlService.isContactAxolotlCapable(getContact())) { + return Message.ENCRYPTION_AXOLOTL; } else { - return latest; + return Message.ENCRYPTION_NONE; } + } + int outgoing = this.getMostRecentlyUsedOutgoingEncryption(); + if (outgoing == Message.ENCRYPTION_NONE) { + next = this.getMostRecentlyUsedIncomingEncryption(); } else { - return latest; + next = outgoing; } } - if (next == Message.ENCRYPTION_NONE && force - && getMode() == MODE_SINGLE) { - return Message.ENCRYPTION_OTR; - } else { - return next; + if (Config.FORCE_E2E_ENCRYPTION && mode == MODE_SINGLE && next <= 0) { + if (axolotlService != null && axolotlService.isContactAxolotlCapable(getContact())) { + return Message.ENCRYPTION_AXOLOTL; + } else { + return Message.ENCRYPTION_OTR; + } } + return next; } public void setNextEncryption(int encryption) { @@ -639,37 +695,32 @@ public class Conversation extends AbstractEntity implements Blockable { synchronized (this.messages) { for (int i = this.messages.size() - 1; i >= 0; --i) { Message message = this.messages.get(i); - if ((message.getStatus() == Message.STATUS_UNSEND || message.getStatus() == Message.STATUS_SEND) && message.getBody() != null && message.getBody().equals(body)) { - return message; + if (message.getStatus() == Message.STATUS_UNSEND || message.getStatus() == Message.STATUS_SEND) { + String otherBody; + if (message.hasFileOnRemoteHost()) { + otherBody = message.getFileParams().url.toString(); + } else { + otherBody = message.body; + } + if (otherBody != null && otherBody.equals(body)) { + return message; + } } } return null; } } - public boolean setLastMessageTransmitted(long value) { - long before = getLastMessageTransmitted(); - if (value - before > 1000) { - this.setAttribute(ATTRIBUTE_LAST_MESSAGE_TRANSMITTED, String.valueOf(value)); - return true; - } else { - return false; - } - } - public long getLastMessageTransmitted() { - long timestamp = getLongAttribute(ATTRIBUTE_LAST_MESSAGE_TRANSMITTED,0); - if (timestamp == 0) { - synchronized (this.messages) { - for(int i = this.messages.size() - 1; i >= 0; --i) { - Message message = this.messages.get(i); - if (message.getStatus() == Message.STATUS_RECEIVED) { - return message.getTimeSent(); - } + synchronized (this.messages) { + for(int i = this.messages.size() - 1; i >= 0; --i) { + Message message = this.messages.get(i); + if (message.getStatus() == Message.STATUS_RECEIVED || message.isCarbon()) { + return message.getTimeSent(); } } } - return timestamp; + return 0; } public void setMutedTill(long value) { @@ -680,6 +731,10 @@ public class Conversation extends AbstractEntity implements Blockable { return System.currentTimeMillis() < this.getLongAttribute(ATTRIBUTE_MUTED_TILL, 0); } + public boolean alwaysNotify() { + return mode == MODE_SINGLE || getBooleanAttribute(ATTRIBUTE_ALWAYS_NOTIFY,Config.ALWAYS_NOTIFY_BY_DEFAULT || isPnNA()); + } + public boolean setAttribute(String key, String value) { try { this.attributes.put(key, value); @@ -723,6 +778,15 @@ public class Conversation extends AbstractEntity implements Blockable { } } + public boolean getBooleanAttribute(String key, boolean defaultValue) { + String value = this.getAttribute(key); + if (value == null) { + return defaultValue; + } else { + return Boolean.parseBoolean(value); + } + } + public void add(Message message) { message.setConversation(this); synchronized (this.messages) { @@ -734,6 +798,7 @@ public class Conversation extends AbstractEntity implements Blockable { synchronized (this.messages) { this.messages.addAll(index, messages); } + account.getPgpDecryptionService().addAll(messages); } public void sort() { diff --git a/src/main/java/eu/siacs/conversations/entities/DownloadableFile.java b/src/main/java/eu/siacs/conversations/entities/DownloadableFile.java index c6a7d3d7..d35a4b01 100644 --- a/src/main/java/eu/siacs/conversations/entities/DownloadableFile.java +++ b/src/main/java/eu/siacs/conversations/entities/DownloadableFile.java @@ -1,27 +1,7 @@ package eu.siacs.conversations.entities; import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.URLConnection; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.Key; -import java.security.NoSuchAlgorithmException; - -import javax.crypto.Cipher; -import javax.crypto.CipherInputStream; -import javax.crypto.CipherOutputStream; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.SecretKeySpec; - -import de.thedevstack.android.logcat.Logging; - -import eu.siacs.conversations.Config; + import eu.siacs.conversations.utils.MimeUtils; public class DownloadableFile extends File { @@ -30,8 +10,7 @@ public class DownloadableFile extends File { private long expectedSize = 0; private String sha1sum; - private Key aeskey; - private String mime; + private byte[] aeskey; private byte[] iv = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0xf }; @@ -45,15 +24,7 @@ public class DownloadableFile extends File { } public long getExpectedSize() { - if (this.aeskey != null) { - if (this.expectedSize == 0) { - return 0; - } else { - return (this.expectedSize / 16 + 1) * 16; - } - } else { - return this.expectedSize; - } + return this.expectedSize; } public String getMimeType() { @@ -79,91 +50,38 @@ public class DownloadableFile extends File { this.sha1sum = sum; } - public void setKey(byte[] key) { - if (key.length == 48) { + public void setKeyAndIv(byte[] keyIvCombo) { + if (keyIvCombo.length == 48) { byte[] secretKey = new byte[32]; byte[] iv = new byte[16]; - System.arraycopy(key, 0, iv, 0, 16); - System.arraycopy(key, 16, secretKey, 0, 32); - this.aeskey = new SecretKeySpec(secretKey, "AES"); + System.arraycopy(keyIvCombo, 0, iv, 0, 16); + System.arraycopy(keyIvCombo, 16, secretKey, 0, 32); + this.aeskey = secretKey; this.iv = iv; - } else if (key.length >= 32) { + } else if (keyIvCombo.length >= 32) { byte[] secretKey = new byte[32]; - System.arraycopy(key, 0, secretKey, 0, 32); - this.aeskey = new SecretKeySpec(secretKey, "AES"); - } else if (key.length >= 16) { + System.arraycopy(keyIvCombo, 0, secretKey, 0, 32); + this.aeskey = secretKey; + } else if (keyIvCombo.length >= 16) { byte[] secretKey = new byte[16]; - System.arraycopy(key, 0, secretKey, 0, 16); - this.aeskey = new SecretKeySpec(secretKey, "AES"); + System.arraycopy(keyIvCombo, 0, secretKey, 0, 16); + this.aeskey = secretKey; } } - public Key getKey() { - return this.aeskey; + public void setKey(byte[] key) { + this.aeskey = key; } - public InputStream createInputStream() { - if (this.getKey() == null) { - try { - return new FileInputStream(this); - } catch (FileNotFoundException e) { - return null; - } - } else { - try { - IvParameterSpec ips = new IvParameterSpec(iv); - Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); - cipher.init(Cipher.ENCRYPT_MODE, this.getKey(), ips); - Logging.d(Config.LOGTAG, "opening encrypted input stream"); - return new CipherInputStream(new FileInputStream(this), cipher); - } catch (NoSuchAlgorithmException e) { - Logging.d(Config.LOGTAG, "no such algo: " + e.getMessage()); - return null; - } catch (NoSuchPaddingException e) { - Logging.d(Config.LOGTAG, "no such padding: " + e.getMessage()); - return null; - } catch (InvalidKeyException e) { - Logging.d(Config.LOGTAG, "invalid key: " + e.getMessage()); - return null; - } catch (InvalidAlgorithmParameterException e) { - Logging.d(Config.LOGTAG, "invavid iv:" + e.getMessage()); - return null; - } catch (FileNotFoundException e) { - return null; - } - } + public void setIv(byte[] iv) { + this.iv = iv; } - public OutputStream createOutputStream() { - if (this.getKey() == null) { - try { - return new FileOutputStream(this); - } catch (FileNotFoundException e) { - return null; - } - } else { - try { - IvParameterSpec ips = new IvParameterSpec(this.iv); - Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); - cipher.init(Cipher.DECRYPT_MODE, this.getKey(), ips); - Logging.d(Config.LOGTAG, "opening encrypted output stream"); - return new CipherOutputStream(new FileOutputStream(this), - cipher); - } catch (NoSuchAlgorithmException e) { - Logging.d(Config.LOGTAG, "no such algo: " + e.getMessage()); - return null; - } catch (NoSuchPaddingException e) { - Logging.d(Config.LOGTAG, "no such padding: " + e.getMessage()); - return null; - } catch (InvalidKeyException e) { - Logging.d(Config.LOGTAG, "invalid key: " + e.getMessage()); - return null; - } catch (InvalidAlgorithmParameterException e) { - Logging.d(Config.LOGTAG, "invavid iv:" + e.getMessage()); - return null; - } catch (FileNotFoundException e) { - return null; - } - } + public byte[] getKey() { + return this.aeskey; + } + + public byte[] getIv() { + return this.iv; } } diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java index d04dff87..8d0571a4 100644 --- a/src/main/java/eu/siacs/conversations/entities/Message.java +++ b/src/main/java/eu/siacs/conversations/entities/Message.java @@ -8,6 +8,7 @@ import java.net.URL; import java.util.Arrays; import eu.siacs.conversations.Config; +import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession; import eu.siacs.conversations.utils.GeoHelper; import eu.siacs.conversations.utils.MimeUtils; import eu.siacs.conversations.utils.UIHelper; @@ -34,6 +35,7 @@ public class Message extends AbstractEntity { public static final int ENCRYPTION_OTR = 2; public static final int ENCRYPTION_DECRYPTED = 3; public static final int ENCRYPTION_DECRYPTION_FAILED = 4; + public static final int ENCRYPTION_AXOLOTL = 5; public static final int TYPE_TEXT = 0; public static final int TYPE_IMAGE = 1; @@ -49,9 +51,12 @@ public class Message extends AbstractEntity { public static final String ENCRYPTION = "encryption"; public static final String STATUS = "status"; public static final String TYPE = "type"; + public static final String CARBON = "carbon"; public static final String REMOTE_MSG_ID = "remoteMsgId"; public static final String SERVER_MSG_ID = "serverMsgId"; public static final String RELATIVE_FILE_PATH = "relativeFilePath"; + public static final String FINGERPRINT = "axolotl_fingerprint"; + public static final String READ = "read"; public static final String ME_COMMAND = "/me "; @@ -59,12 +64,13 @@ public class Message extends AbstractEntity { protected String conversationUuid; protected Jid counterpart; protected Jid trueCounterpart; - private String body; + protected String body; protected String encryptedBody; protected long timeSent; protected int encryption; protected int status; protected int type; + protected boolean carbon = false; protected String relativeFilePath; protected boolean read = true; protected String remoteMsgId = null; @@ -73,6 +79,7 @@ public class Message extends AbstractEntity { protected Transferable transferable = null; private Message mNextMessage = null; private Message mPreviousMessage = null; + private String axolotlFingerprint = null; private Message() { @@ -92,16 +99,20 @@ public class Message extends AbstractEntity { encryption, status, TYPE_TEXT, + false, null, null, - null); + null, + null, + true); this.conversation = conversation; } private Message(final String uuid, final String conversationUUid, final Jid counterpart, final Jid trueCounterpart, final String body, final long timeSent, - final int encryption, final int status, final int type, final String remoteMsgId, - final String relativeFilePath, final String serverMsgId) { + final int encryption, final int status, final int type, final boolean carbon, + final String remoteMsgId, final String relativeFilePath, + final String serverMsgId, final String fingerprint, final boolean read) { this.uuid = uuid; this.conversationUuid = conversationUUid; this.counterpart = counterpart; @@ -111,9 +122,12 @@ public class Message extends AbstractEntity { this.encryption = encryption; this.status = status; this.type = type; + this.carbon = carbon; this.remoteMsgId = remoteMsgId; this.relativeFilePath = relativeFilePath; this.serverMsgId = serverMsgId; + this.axolotlFingerprint = fingerprint; + this.read = read; } public static Message fromCursor(Cursor cursor) { @@ -148,13 +162,16 @@ public class Message extends AbstractEntity { cursor.getInt(cursor.getColumnIndex(ENCRYPTION)), cursor.getInt(cursor.getColumnIndex(STATUS)), cursor.getInt(cursor.getColumnIndex(TYPE)), + cursor.getInt(cursor.getColumnIndex(CARBON))>0, cursor.getString(cursor.getColumnIndex(REMOTE_MSG_ID)), cursor.getString(cursor.getColumnIndex(RELATIVE_FILE_PATH)), - cursor.getString(cursor.getColumnIndex(SERVER_MSG_ID))); + cursor.getString(cursor.getColumnIndex(SERVER_MSG_ID)), + cursor.getString(cursor.getColumnIndex(FINGERPRINT)), + cursor.getInt(cursor.getColumnIndex(READ)) > 0); } public static Message createStatusMessage(Conversation conversation, String body) { - Message message = new Message(); + final Message message = new Message(); message.setType(Message.TYPE_STATUS); message.setConversation(conversation); message.setBody(body); @@ -181,9 +198,12 @@ public class Message extends AbstractEntity { values.put(ENCRYPTION, encryption); values.put(STATUS, status); values.put(TYPE, type); + values.put(CARBON, carbon ? 1 : 0); values.put(REMOTE_MSG_ID, remoteMsgId); values.put(RELATIVE_FILE_PATH, relativeFilePath); values.put(SERVER_MSG_ID, serverMsgId); + values.put(FINGERPRINT, axolotlFingerprint); + values.put(READ,read); return values; } @@ -304,6 +324,14 @@ public class Message extends AbstractEntity { this.type = type; } + public boolean isCarbon() { + return carbon; + } + + public void setCarbon(boolean carbon) { + this.carbon = carbon; + } + public void setTrueCounterpart(Jid trueCounterpart) { this.trueCounterpart = trueCounterpart; } @@ -333,7 +361,9 @@ public class Message extends AbstractEntity { if (message.getRemoteMsgId() != null) { return (message.getRemoteMsgId().equals(this.remoteMsgId) || message.getRemoteMsgId().equals(this.uuid)) && this.counterpart.equals(message.getCounterpart()) - && body.equals(otherBody); + && (body.equals(otherBody) + ||(message.getEncryption() == Message.ENCRYPTION_PGP + && message.getRemoteMsgId().matches("[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}"))) ; } else { return this.remoteMsgId == null && this.counterpart.equals(message.getCounterpart()) @@ -390,6 +420,7 @@ public class Message extends AbstractEntity { this.treatAsDownloadable() == Decision.NEVER && !message.getBody().startsWith(ME_COMMAND) && !this.getBody().startsWith(ME_COMMAND) + this.isTrusted() == message.isTrusted() ); } @@ -405,11 +436,14 @@ public class Message extends AbstractEntity { } public String getMergedBody() { - final Message next = this.next(); - if (this.mergeable(next)) { - return getBody() + MERGE_SEPARATOR + next.getMergedBody(); + StringBuilder body = new StringBuilder(this.body.trim()); + Message current = this; + while(current.mergeable(current.next())) { + current = current.next(); + body.append(MERGE_SEPARATOR); + body.append(current.getBody()); } - return getBody(); + return body.toString(); } public boolean hasMeCommand() { @@ -417,20 +451,23 @@ public class Message extends AbstractEntity { } public int getMergedStatus() { - final Message next = this.next(); - if (this.mergeable(next)) { - return next.getStatus(); + int status = this.status; + Message current = this; + while(current.mergeable(current.next())) { + current = current.next(); + status = current.status; } - return getStatus(); + return status; } public long getMergedTimeSent() { - Message next = this.next(); - if (this.mergeable(next)) { - return next.getMergedTimeSent(); - } else { - return getTimeSent(); + long time = this.timeSent; + Message current = this; + while(current.mergeable(current.next())) { + current = current.next(); + time = current.timeSent; } + return time; } public boolean wasMergedIntoPrevious() { @@ -478,18 +515,15 @@ public class Message extends AbstractEntity { if (path == null || path.isEmpty()) { return null; } - + String filename = path.substring(path.lastIndexOf('/') + 1).toLowerCase(); - int dotPosition = filename.lastIndexOf("."); - if (dotPosition != -1) - { + if (dotPosition != -1) { String extension = filename.substring(dotPosition + 1); - // we want the real file extension, not the crypto one if (Arrays.asList(Transferable.VALID_CRYPTO_EXTENSIONS).contains(extension)) { - return extractRelevantExtension(path.substring(0,dotPosition)); + return extractRelevantExtension(filename.substring(0,dotPosition)); } else { return extension; } @@ -673,4 +707,55 @@ public class Message extends AbstractEntity { public int width = 0; public int height = 0; } + + public void setAxolotlFingerprint(String fingerprint) { + this.axolotlFingerprint = fingerprint; + } + + public String getAxolotlFingerprint() { + return axolotlFingerprint; + } + + public boolean isTrusted() { + XmppAxolotlSession.Trust t = conversation.getAccount().getAxolotlService().getFingerprintTrust(axolotlFingerprint); + return t != null && t.trusted(); + } + + private int getPreviousEncryption() { + for (Message iterator = this.prev(); iterator != null; iterator = iterator.prev()){ + if( iterator.isCarbon() || iterator.getStatus() == STATUS_RECEIVED ) { + continue; + } + return iterator.getEncryption(); + } + return ENCRYPTION_NONE; + } + + private int getNextEncryption() { + for (Message iterator = this.next(); iterator != null; iterator = iterator.next()){ + if( iterator.isCarbon() || iterator.getStatus() == STATUS_RECEIVED ) { + continue; + } + return iterator.getEncryption(); + } + return conversation.getNextEncryption(); + } + + public boolean isValidInSession() { + int pastEncryption = getCleanedEncryption(this.getPreviousEncryption()); + int futureEncryption = getCleanedEncryption(this.getNextEncryption()); + + boolean inUnencryptedSession = pastEncryption == ENCRYPTION_NONE + || futureEncryption == ENCRYPTION_NONE + || pastEncryption != futureEncryption; + + return inUnencryptedSession || getCleanedEncryption(this.getEncryption()) == pastEncryption; + } + + private static int getCleanedEncryption(int encryption) { + if (encryption == ENCRYPTION_DECRYPTED || encryption == ENCRYPTION_DECRYPTION_FAILED) { + return ENCRYPTION_PGP; + } + return encryption; + } } diff --git a/src/main/java/eu/siacs/conversations/entities/MucOptions.java b/src/main/java/eu/siacs/conversations/entities/MucOptions.java index d867a370..068dbf83 100644 --- a/src/main/java/eu/siacs/conversations/entities/MucOptions.java +++ b/src/main/java/eu/siacs/conversations/entities/MucOptions.java @@ -1,21 +1,35 @@ package eu.siacs.conversations.entities; +import android.annotation.SuppressLint; + import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.PgpEngine; import eu.siacs.conversations.xml.Element; +import eu.siacs.conversations.xmpp.forms.Data; +import eu.siacs.conversations.xmpp.forms.Field; import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; +import eu.siacs.conversations.xmpp.pep.Avatar; import eu.siacs.conversations.xmpp.stanzas.PresencePacket; -import android.annotation.SuppressLint; - @SuppressLint("DefaultLocale") public class MucOptions { + public Account getAccount() { + return this.conversation.getAccount(); + } + + public void setSelf(User user) { + this.self = user; + } + public enum Affiliation { OWNER("owner", 4, R.string.owner), ADMIN("admin", 3, R.string.admin), @@ -23,7 +37,7 @@ public class MucOptions { OUTCAST("outcast", 0, R.string.outcast), NONE("none", 1, R.string.no_affiliation); - private Affiliation(String string, int rank, int resId) { + Affiliation(String string, int rank, int resId) { this.string = string; this.resId = resId; this.rank = rank; @@ -52,18 +66,20 @@ public class MucOptions { } public enum Role { - MODERATOR("moderator", R.string.moderator), - VISITOR("visitor", R.string.visitor), - PARTICIPANT("participant", R.string.participant), - NONE("none", R.string.no_role); + MODERATOR("moderator", R.string.moderator,3), + VISITOR("visitor", R.string.visitor,1), + PARTICIPANT("participant", R.string.participant,2), + NONE("none", R.string.no_role,0); - private Role(String string, int resId) { + Role(String string, int resId, int rank) { this.string = string; this.resId = resId; + this.rank = rank; } private String string; private int resId; + private int rank; public int getResId() { return resId; @@ -73,6 +89,10 @@ public class MucOptions { public String toString() { return this.string; } + + public boolean ranks(Role role) { + return rank >= role.rank; + } } public static final int ERROR_NO_ERROR = 0; @@ -81,6 +101,7 @@ public class MucOptions { public static final int ERROR_PASSWORD_REQUIRED = 3; public static final int ERROR_BANNED = 4; public static final int ERROR_MEMBERS_ONLY = 5; + public static final int ERROR_NO_RESPONSE = 6; public static final int KICKED_FROM_ROOM = 9; @@ -92,32 +113,31 @@ public class MucOptions { public static final String STATUS_CODE_LOST_MEMBERSHIP = "321"; private interface OnEventListener { - public void onSuccess(); + void onSuccess(); - public void onFailure(); + void onFailure(); } public interface OnRenameListener extends OnEventListener { } - public interface OnJoinListener extends OnEventListener { - - } - - public class User { + public static class User { private Role role = Role.NONE; private Affiliation affiliation = Affiliation.NONE; - private String name; private Jid jid; + private Jid fullJid; private long pgpKeyId = 0; + private Avatar avatar; + private MucOptions options; - public String getName() { - return name; + public User(MucOptions options, Jid from) { + this.options = options; + this.fullJid = from; } - public void setName(String user) { - this.name = user; + public String getName() { + return this.fullJid.getResourcepart(); } public void setJid(Jid jid) { @@ -158,7 +178,7 @@ public class MucOptions { return false; } else { User o = (User) other; - return name != null && name.equals(o.name) + return getName() != null && getName().equals(o.getName()) && jid != null && jid.equals(o.jid) && affiliation == o.affiliation && role == o.role; @@ -198,26 +218,48 @@ public class MucOptions { } public Contact getContact() { - return account.getRoster().getContactFromRoster(getJid()); + return getAccount().getRoster().getContactFromRoster(getJid()); + } + + public boolean setAvatar(Avatar avatar) { + if (this.avatar != null && this.avatar.equals(avatar)) { + return false; + } else { + this.avatar = avatar; + return true; + } + } + + public String getAvatar() { + return avatar == null ? null : avatar.getFilename(); + } + + public Account getAccount() { + return options.getAccount(); + } + + public Jid getFullJid() { + return fullJid; } } private Account account; - private List<User> users = new CopyOnWriteArrayList<>(); + private final Map<String, User> users = Collections.synchronizedMap(new LinkedHashMap<String, User>()); private List<String> features = new ArrayList<>(); + private Data form = new Data(); private Conversation conversation; private boolean isOnline = false; - private int error = ERROR_UNKNOWN; - private OnRenameListener onRenameListener = null; - private OnJoinListener onJoinListener = null; - private User self = new User(); + private int error = ERROR_NO_RESPONSE; + public OnRenameListener onRenameListener = null; + private User self; private String subject = null; private String password = null; - private boolean mNickChangingInProgress = false; + public boolean mNickChangingInProgress = false; public MucOptions(Conversation conversation) { this.account = conversation.getAccount(); this.conversation = conversation; + this.self = new User(this,createJoinJid(getProposedNick())); } public void updateFeatures(ArrayList<String> features) { @@ -225,18 +267,37 @@ public class MucOptions { this.features.addAll(features); } + public void updateFormData(Data form) { + this.form = form; + } + public boolean hasFeature(String feature) { return this.features.contains(feature); } public boolean canInvite() { - return !membersOnly() || self.getAffiliation().ranks(Affiliation.ADMIN); + Field field = this.form.getFieldByName("muc#roomconfig_allowinvites"); + return !membersOnly() || self.getRole().ranks(Role.MODERATOR) || (field != null && "1".equals(field.getValue())); + } + + public boolean canChangeSubject() { + Field field = this.form.getFieldByName("muc#roomconfig_changesubject"); + return self.getRole().ranks(Role.MODERATOR) || (field != null && "1".equals(field.getValue())); + } + + public boolean participating() { + return !online() || self.getRole().ranks(Role.PARTICIPANT); } public boolean membersOnly() { return hasFeature("muc_membersonly"); } + public boolean mamSupport() { + // Update with "urn:xmpp:mam:1" once we support it + return hasFeature("urn:xmpp:mam:0"); + } + public boolean nonanonymous() { return hasFeature("muc_nonanonymous"); } @@ -245,135 +306,55 @@ public class MucOptions { return hasFeature("muc_persistent"); } - public void deleteUser(String name) { - for (int i = 0; i < users.size(); ++i) { - if (users.get(i).getName().equals(name)) { - users.remove(i); - return; - } - } + public boolean moderated() { + return hasFeature("muc_moderated"); + } + + public User deleteUser(String name) { + return this.users.remove(name); } public void addUser(User user) { - for (int i = 0; i < users.size(); ++i) { - if (users.get(i).getName().equals(user.getName())) { - users.set(i, user); - return; - } - } - users.add(user); - } - - public void processPacket(PresencePacket packet, PgpEngine pgp) { - final Jid from = packet.getFrom(); - if (!from.isBareJid()) { - final String name = from.getResourcepart(); - final String type = packet.getAttribute("type"); - final Element x = packet.findChild("x", "http://jabber.org/protocol/muc#user"); - final List<String> codes = getStatusCodes(x); - if (type == null) { - User user = new User(); - if (x != null) { - Element item = x.findChild("item"); - if (item != null && name != null) { - user.setName(name); - user.setAffiliation(item.getAttribute("affiliation")); - user.setRole(item.getAttribute("role")); - user.setJid(item.getAttributeAsJid("jid")); - if (codes.contains(STATUS_CODE_SELF_PRESENCE) || packet.getFrom().equals(this.conversation.getJid())) { - this.isOnline = true; - this.error = ERROR_NO_ERROR; - self = user; - if (mNickChangingInProgress) { - onRenameListener.onSuccess(); - mNickChangingInProgress = false; - } else if (this.onJoinListener != null) { - this.onJoinListener.onSuccess(); - this.onJoinListener = null; - } - } else { - addUser(user); - } - if (pgp != null) { - Element signed = packet.findChild("x", "jabber:x:signed"); - if (signed != null) { - Element status = packet.findChild("status"); - String msg; - if (status != null) { - msg = status.getContent(); - } else { - msg = ""; - } - user.setPgpKeyId(pgp.fetchKeyId(account, msg, - signed.getContent())); - } - } - } - } - } else if (type.equals("unavailable")) { - if (codes.contains(STATUS_CODE_SELF_PRESENCE) || - packet.getFrom().equals(this.conversation.getJid())) { - if (codes.contains(STATUS_CODE_CHANGED_NICK)) { - this.mNickChangingInProgress = true; - } else if (codes.contains(STATUS_CODE_KICKED)) { - setError(KICKED_FROM_ROOM); - } else if (codes.contains(STATUS_CODE_BANNED)) { - setError(ERROR_BANNED); - } else if (codes.contains(STATUS_CODE_LOST_MEMBERSHIP)) { - setError(ERROR_MEMBERS_ONLY); - } else { - setError(ERROR_UNKNOWN); - } - } else { - deleteUser(name); - } - } else if (type.equals("error")) { - Element error = packet.findChild("error"); - if (error != null && error.hasChild("conflict")) { - if (isOnline) { - if (onRenameListener != null) { - onRenameListener.onFailure(); - } - } else { - setError(ERROR_NICK_IN_USE); - } - } else if (error != null && error.hasChild("not-authorized")) { - setError(ERROR_PASSWORD_REQUIRED); - } else if (error != null && error.hasChild("forbidden")) { - setError(ERROR_BANNED); - } else if (error != null && error.hasChild("registration-required")) { - setError(ERROR_MEMBERS_ONLY); - } - } - } + this.users.put(user.getName(), user); } - private void setError(int error) { - this.isOnline = false; + public User findUser(String name) { + return this.users.get(name); + } + + public boolean isUserInRoom(String name) { + return findUser(name) != null; + } + + public void setError(int error) { + this.isOnline = isOnline && error == ERROR_NO_ERROR; this.error = error; - if (onJoinListener != null) { - onJoinListener.onFailure(); - onJoinListener = null; - } } - private List<String> getStatusCodes(Element x) { - List<String> codes = new ArrayList<>(); - if (x != null) { - for (Element child : x.getChildren()) { - if (child.getName().equals("status")) { - String code = child.getAttribute("code"); - if (code != null) { - codes.add(code); - } - } + public void setOnline() { + this.isOnline = true; + } + + public ArrayList<User> getUsers() { + return new ArrayList<>(users.values()); + } + + public List<User> getUsers(int max) { + ArrayList<User> users = new ArrayList<>(); + int i = 1; + for(User user : this.users.values()) { + users.add(user); + if (i >= max) { + break; + } else { + ++i; } } - return codes; + return users; } - public List<User> getUsers() { - return this.users; + public int getUserCount() { + return this.users.size(); } public String getProposedNick() { @@ -408,13 +389,9 @@ public class MucOptions { this.onRenameListener = listener; } - public void setOnJoinListener(OnJoinListener listener) { - this.onJoinListener = listener; - } - public void setOffline() { this.users.clear(); - this.error = 0; + this.error = ERROR_NO_RESPONSE; this.isOnline = false; } @@ -432,8 +409,8 @@ public class MucOptions { public String createNameFromParticipants() { if (users.size() >= 2) { - List<String> names = new ArrayList<String>(); - for (User user : users) { + List<String> names = new ArrayList<>(); + for (User user : getUsers(5)) { Contact contact = user.getContact(); if (contact != null && !contact.getDisplayName().isEmpty()) { names.add(contact.getDisplayName().split("\\s+")[0]); @@ -456,20 +433,21 @@ public class MucOptions { public long[] getPgpKeyIds() { List<Long> ids = new ArrayList<>(); - for (User user : getUsers()) { + for (User user : this.users.values()) { if (user.getPgpKeyId() != 0) { ids.add(user.getPgpKeyId()); } } - long[] primitivLongArray = new long[ids.size()]; + ids.add(account.getPgpId()); + long[] primitiveLongArray = new long[ids.size()]; for (int i = 0; i < ids.size(); ++i) { - primitivLongArray[i] = ids.get(i); + primitiveLongArray[i] = ids.get(i); } - return primitivLongArray; + return primitiveLongArray; } public boolean pgpKeysInUse() { - for (User user : getUsers()) { + for (User user : this.users.values()) { if (user.getPgpKeyId() != 0) { return true; } @@ -478,7 +456,7 @@ public class MucOptions { } public boolean everybodyHasKeys() { - for (User user : getUsers()) { + for (User user : this.users.values()) { if (user.getPgpKeyId() == 0) { return false; } @@ -494,13 +472,9 @@ public class MucOptions { } } - public Jid getTrueCounterpart(String counterpart) { - for (User user : this.getUsers()) { - if (user.getName().equals(counterpart)) { - return user.getJid(); - } - } - return null; + public Jid getTrueCounterpart(String name) { + User user = findUser(name); + return user == null ? null : user.getJid(); } public String getPassword() { diff --git a/src/main/java/eu/siacs/conversations/entities/Presences.java b/src/main/java/eu/siacs/conversations/entities/Presences.java index bccf3117..4729a11b 100644 --- a/src/main/java/eu/siacs/conversations/entities/Presences.java +++ b/src/main/java/eu/siacs/conversations/entities/Presences.java @@ -15,7 +15,7 @@ public class Presences { public static final int DND = 3; public static final int OFFLINE = 4; - private Hashtable<String, Integer> presences = new Hashtable<String, Integer>(); + private final Hashtable<String, Integer> presences = new Hashtable<>(); public Hashtable<String, Integer> getPresences() { return this.presences; diff --git a/src/main/java/eu/siacs/conversations/entities/Transferable.java b/src/main/java/eu/siacs/conversations/entities/Transferable.java index 2db6e3c9..016c81bd 100644 --- a/src/main/java/eu/siacs/conversations/entities/Transferable.java +++ b/src/main/java/eu/siacs/conversations/entities/Transferable.java @@ -4,7 +4,7 @@ public interface Transferable { String[] VALID_IMAGE_EXTENSIONS = {"webp", "jpeg", "jpg", "png", "jpe"}; String[] VALID_CRYPTO_EXTENSIONS = {"pgp", "gpg", "otr"}; - String[] WELL_KNOWN_EXTENSIONS = {"pdf","m4a"}; + String[] WELL_KNOWN_EXTENSIONS = {"pdf","m4a","mp4"}; int STATUS_UNKNOWN = 0x200; int STATUS_CHECKING = 0x201; diff --git a/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java b/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java index 89c8cf5f..c30ae5d8 100644 --- a/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java @@ -12,6 +12,9 @@ import java.util.List; import java.util.Locale; import java.util.TimeZone; +import eu.siacs.conversations.crypto.axolotl.AxolotlService; +import eu.siacs.conversations.services.XmppConnectionService; +import eu.siacs.conversations.utils.PhoneHelper; import de.thedevstack.conversationsplus.ConversationsPlusApplication; import de.tzur.conversations.Settings; @@ -26,18 +29,38 @@ public abstract class AbstractGenerator { "http://jabber.org/protocol/caps", "http://jabber.org/protocol/disco#info", "urn:xmpp:avatar:metadata+notify", + "http://jabber.org/protocol/nick+notify", "urn:xmpp:ping", "jabber:iq:version", - "http://jabber.org/protocol/chatstates"}; + "http://jabber.org/protocol/chatstates", + AxolotlService.PEP_DEVICE_LIST+"+notify"}; private final String[] MESSAGE_CONFIRMATION_FEATURES = { "urn:xmpp:chat-markers:0", "urn:xmpp:receipts" }; private String mVersion = null; - public final String IDENTITY_TYPE = "phone"; + protected final String IDENTITY_NAME = "Conversations"; + protected final String IDENTITY_TYPE = "phone"; private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US); + protected XmppConnectionService mXmppConnectionService; + + protected AbstractGenerator(XmppConnectionService service) { + this.mXmppConnectionService = service; + } + + protected String getIdentityVersion() { + if (mVersion == null) { + this.mVersion = PhoneHelper.getVersionName(mXmppConnectionService); + } + return this.mVersion; + } + + public String getIdentityName() { + return IDENTITY_NAME + " " + getIdentityVersion(); + } + public String getCapHash() { StringBuilder s = new StringBuilder(); s.append("client/" + IDENTITY_TYPE + "//" + ConversationsPlusApplication.getNameAndVersion() + "<"); diff --git a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java index ce32cedb..6bc1f0fc 100644 --- a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java @@ -1,10 +1,24 @@ package eu.siacs.conversations.generator; + +import android.util.Base64; +import android.util.Log; + +import org.whispersystems.libaxolotl.IdentityKey; +import org.whispersystems.libaxolotl.ecc.ECPublicKey; +import org.whispersystems.libaxolotl.state.PreKeyRecord; +import org.whispersystems.libaxolotl.state.SignedPreKeyRecord; + +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.List; +import java.util.Set; import de.thedevstack.conversationsplus.ConversationsPlusApplication; +import eu.siacs.conversations.Config; +import eu.siacs.conversations.crypto.axolotl.AxolotlService; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.DownloadableFile; @@ -19,6 +33,10 @@ import eu.siacs.conversations.xmpp.stanzas.IqPacket; public class IqGenerator extends AbstractGenerator { + public IqGenerator(final XmppConnectionService service) { + super(service); + } + public IqPacket discoResponse(final IqPacket request) { final IqPacket packet = new IqPacket(IqPacket.TYPE.RESULT); packet.setId(request.getId()); @@ -44,17 +62,154 @@ public class IqGenerator extends AbstractGenerator { return packet; } - public static IqPacket retrieveVcardAvatar(final Avatar avatar) { + protected IqPacket publish(final String node, final Element item) { + final IqPacket packet = new IqPacket(IqPacket.TYPE.SET); + final Element pubsub = packet.addChild("pubsub", + "http://jabber.org/protocol/pubsub"); + final Element publish = pubsub.addChild("publish"); + publish.setAttribute("node", node); + publish.addChild(item); + return packet; + } + + protected IqPacket retrieve(String node, Element item) { + final IqPacket packet = new IqPacket(IqPacket.TYPE.GET); + final Element pubsub = packet.addChild("pubsub", + "http://jabber.org/protocol/pubsub"); + final Element items = pubsub.addChild("items"); + items.setAttribute("node", node); + if (item != null) { + items.addChild(item); + } + return packet; + } + + public IqPacket publishNick(String nick) { + final Element item = new Element("item"); + item.addChild("nick","http://jabber.org/protocol/nick").setContent(nick); + return publish("http://jabber.org/protocol/nick", item); + } + + public IqPacket publishAvatar(Avatar avatar) { + final Element item = new Element("item"); + item.setAttribute("id", avatar.sha1sum); + final Element data = item.addChild("data", "urn:xmpp:avatar:data"); + data.setContent(avatar.image); + return publish("urn:xmpp:avatar:data", item); + } + + public IqPacket publishAvatarMetadata(final Avatar avatar) { + final Element item = new Element("item"); + item.setAttribute("id", avatar.sha1sum); + final Element metadata = item + .addChild("metadata", "urn:xmpp:avatar:metadata"); + final Element info = metadata.addChild("info"); + info.setAttribute("bytes", avatar.size); + info.setAttribute("id", avatar.sha1sum); + info.setAttribute("height", avatar.height); + info.setAttribute("width", avatar.height); + info.setAttribute("type", avatar.type); + return publish("urn:xmpp:avatar:metadata", item); + } + + public IqPacket retrievePepAvatar(final Avatar avatar) { + final Element item = new Element("item"); + item.setAttribute("id", avatar.sha1sum); + final IqPacket packet = retrieve("urn:xmpp:avatar:data", item); + packet.setTo(avatar.owner); + return packet; + } + + public IqPacket retrieveVcardAvatar(final Avatar avatar) { final IqPacket packet = new IqPacket(IqPacket.TYPE.GET); packet.setTo(avatar.owner); packet.addChild("vCard", "vcard-temp"); return packet; } + public IqPacket retrieveAvatarMetaData(final Jid to) { + final IqPacket packet = retrieve("urn:xmpp:avatar:metadata", null); + if (to != null) { + packet.setTo(to); + } + return packet; + } + + public IqPacket retrieveDeviceIds(final Jid to) { + final IqPacket packet = retrieve(AxolotlService.PEP_DEVICE_LIST, null); + if(to != null) { + packet.setTo(to); + } + return packet; + } + + public IqPacket retrieveBundlesForDevice(final Jid to, final int deviceid) { + final IqPacket packet = retrieve(AxolotlService.PEP_BUNDLES+":"+deviceid, null); + packet.setTo(to); + return packet; + } + + public IqPacket retrieveVerificationForDevice(final Jid to, final int deviceid) { + final IqPacket packet = retrieve(AxolotlService.PEP_VERIFICATION+":"+deviceid, null); + packet.setTo(to); + return packet; + } + + public IqPacket publishDeviceIds(final Set<Integer> ids) { + final Element item = new Element("item"); + final Element list = item.addChild("list", AxolotlService.PEP_PREFIX); + for(Integer id:ids) { + final Element device = new Element("device"); + device.setAttribute("id", id); + list.addChild(device); + } + return publish(AxolotlService.PEP_DEVICE_LIST, item); + } + + public IqPacket publishBundles(final SignedPreKeyRecord signedPreKeyRecord, final IdentityKey identityKey, + final Set<PreKeyRecord> preKeyRecords, final int deviceId) { + final Element item = new Element("item"); + final Element bundle = item.addChild("bundle", AxolotlService.PEP_PREFIX); + final Element signedPreKeyPublic = bundle.addChild("signedPreKeyPublic"); + signedPreKeyPublic.setAttribute("signedPreKeyId", signedPreKeyRecord.getId()); + ECPublicKey publicKey = signedPreKeyRecord.getKeyPair().getPublicKey(); + signedPreKeyPublic.setContent(Base64.encodeToString(publicKey.serialize(),Base64.DEFAULT)); + final Element signedPreKeySignature = bundle.addChild("signedPreKeySignature"); + signedPreKeySignature.setContent(Base64.encodeToString(signedPreKeyRecord.getSignature(),Base64.DEFAULT)); + final Element identityKeyElement = bundle.addChild("identityKey"); + identityKeyElement.setContent(Base64.encodeToString(identityKey.serialize(), Base64.DEFAULT)); + + final Element prekeys = bundle.addChild("prekeys", AxolotlService.PEP_PREFIX); + for(PreKeyRecord preKeyRecord:preKeyRecords) { + final Element prekey = prekeys.addChild("preKeyPublic"); + prekey.setAttribute("preKeyId", preKeyRecord.getId()); + prekey.setContent(Base64.encodeToString(preKeyRecord.getKeyPair().getPublicKey().serialize(), Base64.DEFAULT)); + } + + return publish(AxolotlService.PEP_BUNDLES+":"+deviceId, item); + } + + public IqPacket publishVerification(byte[] signature, X509Certificate[] certificates, final int deviceId) { + final Element item = new Element("item"); + final Element verification = item.addChild("verification", AxolotlService.PEP_PREFIX); + final Element chain = verification.addChild("chain"); + for(int i = 0; i < certificates.length; ++i) { + try { + Element certificate = chain.addChild("certificate"); + certificate.setContent(Base64.encodeToString(certificates[i].getEncoded(), Base64.DEFAULT)); + certificate.setAttribute("index",i); + } catch (CertificateEncodingException e) { + Log.d(Config.LOGTAG, "could not encode certificate"); + } + } + verification.addChild("signature").setContent(Base64.encodeToString(signature, Base64.DEFAULT)); + return publish(AxolotlService.PEP_VERIFICATION+":"+deviceId, item); + } + public IqPacket queryMessageArchiveManagement(final MessageArchiveService.Query mam) { final IqPacket packet = new IqPacket(IqPacket.TYPE.SET); final Element query = packet.query("urn:xmpp:mam:0"); - query.setAttribute("queryid",mam.getQueryId()); + query.setAttribute("queryid", mam.getQueryId()); final Data data = new Data(); data.setFormType("urn:xmpp:mam:0"); if (mam.muc()) { @@ -62,8 +217,9 @@ public class IqGenerator extends AbstractGenerator { } else if (mam.getWith()!=null) { data.put("with", mam.getWith().toString()); } - data.put("start",getTimestamp(mam.getStart())); - data.put("end",getTimestamp(mam.getEnd())); + data.put("start", getTimestamp(mam.getStart())); + data.put("end", getTimestamp(mam.getEnd())); + data.submit(); query.addChild(data); if (mam.getPagingOrder() == MessageArchiveService.PagingOrder.REVERSE) { query.addChild("set", "http://jabber.org/protocol/rsm").addChild("before").setContent(mam.getReference()); @@ -132,12 +288,25 @@ public class IqGenerator extends AbstractGenerator { return packet; } - public IqPacket requestHttpUploadSlot(Jid host, DownloadableFile file) { + public IqPacket requestHttpUploadSlot(Jid host, DownloadableFile file, String mime) { IqPacket packet = new IqPacket(IqPacket.TYPE.GET); packet.setTo(host); Element request = packet.addChild("request",Xmlns.HTTP_UPLOAD); request.addChild("filename").setContent(file.getName()); request.addChild("size").setContent(String.valueOf(file.getExpectedSize())); + if (mime != null) { + request.addChild("content-type").setContent(mime); + } return packet; } + + public IqPacket generateCreateAccountWithCaptcha(Account account, String id, Data data) { + final IqPacket register = new IqPacket(IqPacket.TYPE.SET); + + register.setTo(account.getServer()); + register.setId(id); + register.query("jabber:iq:register").addChild(data); + + return register; + } } diff --git a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java index 2413c54e..9b5dabb0 100644 --- a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java @@ -1,6 +1,10 @@ package eu.siacs.conversations.generator; +import net.java.otr4j.OtrException; +import net.java.otr4j.session.Session; + import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.Date; import java.util.Locale; import java.util.TimeZone; @@ -10,17 +14,22 @@ import net.java.otr4j.session.Session; import de.thedevstack.conversationsplus.ConversationsPlusPreferences; +import eu.siacs.conversations.crypto.axolotl.XmppAxolotlMessage; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Message; +import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xmpp.chatstate.ChatState; import eu.siacs.conversations.xmpp.jid.Jid; import eu.siacs.conversations.xmpp.stanzas.MessagePacket; public class MessageGenerator extends AbstractGenerator { + public MessageGenerator(XmppConnectionService service) { + super(service); + } - private MessagePacket preparePacket(Message message, boolean addDelay) { + private MessagePacket preparePacket(Message message) { Conversation conversation = message.getConversation(); Account account = conversation.getAccount(); MessagePacket packet = new MessagePacket(); @@ -43,13 +52,10 @@ public class MessageGenerator extends AbstractGenerator { } packet.setFrom(account.getJid()); packet.setId(message.getUuid()); - if (addDelay) { - addDelay(packet, message.getTimeSent()); - } return packet; } - private void addDelay(MessagePacket packet, long timestamp) { + public void addDelay(MessagePacket packet, long timestamp) { final SimpleDateFormat mDateFormat = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US); mDateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); @@ -58,20 +64,30 @@ public class MessageGenerator extends AbstractGenerator { delay.setAttribute("stamp", mDateFormat.format(date)); } - public MessagePacket generateOtrChat(Message message) { - return generateOtrChat(message, false); + public MessagePacket generateAxolotlChat(Message message, XmppAxolotlMessage axolotlMessage) { + MessagePacket packet = preparePacket(message); + if (axolotlMessage == null) { + return null; + } + packet.setAxolotlMessage(axolotlMessage.toElement()); + packet.addChild("store", "urn:xmpp:hints"); + return packet; + } + + public static void addMessageHints(MessagePacket packet) { + packet.addChild("private", "urn:xmpp:carbons:2"); + packet.addChild("no-copy", "urn:xmpp:hints"); + packet.addChild("no-permanent-store", "urn:xmpp:hints"); + packet.addChild("no-permanent-storage", "urn:xmpp:hints"); //do not copy this. this is wrong. it is *store* } - public MessagePacket generateOtrChat(Message message, boolean addDelay) { + public MessagePacket generateOtrChat(Message message) { Session otrSession = message.getConversation().getOtrSession(); if (otrSession == null) { return null; } - MessagePacket packet = preparePacket(message, addDelay); - packet.addChild("private", "urn:xmpp:carbons:2"); - packet.addChild("no-copy", "urn:xmpp:hints"); - packet.addChild("no-permanent-store", "urn:xmpp:hints"); - packet.addChild("no-permanent-storage", "urn:xmpp:hints"); + MessagePacket packet = preparePacket(message); + addMessageHints(packet); try { String content; if (message.hasFileOnRemoteHost()) { @@ -87,25 +103,21 @@ public class MessageGenerator extends AbstractGenerator { } public MessagePacket generateChat(Message message) { - return generateChat(message, false); - } - - public MessagePacket generateChat(Message message, boolean addDelay) { - MessagePacket packet = preparePacket(message, addDelay); + MessagePacket packet = preparePacket(message); + String content; if (message.hasFileOnRemoteHost()) { - packet.setBody(message.getFileParams().url.toString()); + Message.FileParams fileParams = message.getFileParams(); + content = fileParams.url.toString(); + packet.addChild("x","jabber:x:oob").addChild("url").setContent(content); } else { - packet.setBody(message.getBody()); + content = message.getBody(); } + packet.setBody(content); return packet; } public MessagePacket generatePgpChat(Message message) { - return generatePgpChat(message, false); - } - - public MessagePacket generatePgpChat(Message message, boolean addDelay) { - MessagePacket packet = preparePacket(message, addDelay); + MessagePacket packet = preparePacket(message); packet.setBody("This is an XEP-0027 encrypted message"); if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { packet.addChild("x", "jabber:x:encrypted").setContent(message.getEncryptedBody()); @@ -118,25 +130,27 @@ public class MessageGenerator extends AbstractGenerator { public MessagePacket generateChatState(Conversation conversation) { final Account account = conversation.getAccount(); MessagePacket packet = new MessagePacket(); + packet.setType(MessagePacket.TYPE_CHAT); packet.setTo(conversation.getJid().toBareJid()); packet.setFrom(account.getJid()); packet.addChild(ChatState.toElement(conversation.getOutgoingChatState())); + packet.addChild("no-store", "urn:xmpp:hints"); + packet.addChild("no-storage", "urn:xmpp:hints"); //wrong! don't copy this. Its *store* return packet; } public MessagePacket confirm(final Account account, final Jid to, final String id) { MessagePacket packet = new MessagePacket(); - packet.setType(MessagePacket.TYPE_NORMAL); + packet.setType(MessagePacket.TYPE_CHAT); packet.setTo(to); packet.setFrom(account.getJid()); - Element received = packet.addChild("displayed", - "urn:xmpp:chat-markers:0"); + Element received = packet.addChild("displayed","urn:xmpp:chat-markers:0"); received.setAttribute("id", id); + packet.addChild("store", "urn:xmpp:hints"); return packet; } - public MessagePacket conferenceSubject(Conversation conversation, - String subject) { + public MessagePacket conferenceSubject(Conversation conversation,String subject) { MessagePacket packet = new MessagePacket(); packet.setType(MessagePacket.TYPE_GROUPCHAT); packet.setTo(conversation.getJid().toBareJid()); @@ -170,14 +184,14 @@ public class MessageGenerator extends AbstractGenerator { return packet; } - public MessagePacket received(Account account, - MessagePacket originalMessage, String namespace) { + public MessagePacket received(Account account, MessagePacket originalMessage, ArrayList<String> namespaces, int type) { MessagePacket receivedPacket = new MessagePacket(); - receivedPacket.setType(MessagePacket.TYPE_NORMAL); + receivedPacket.setType(type); receivedPacket.setTo(originalMessage.getFrom()); receivedPacket.setFrom(account.getJid()); - Element received = receivedPacket.addChild("received", namespace); - received.setAttribute("id", originalMessage.getId()); + for(String namespace : namespaces) { + receivedPacket.addChild("received", namespace).setAttribute("id", originalMessage.getId()); + } return receivedPacket; } diff --git a/src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java b/src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java index 52ed504c..fdfde88c 100644 --- a/src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java @@ -2,11 +2,17 @@ package eu.siacs.conversations.generator; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; +import eu.siacs.conversations.entities.Presences; +import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xmpp.stanzas.PresencePacket; public class PresenceGenerator extends AbstractGenerator { + public PresenceGenerator(XmppConnectionService service) { + super(service); + } + private PresencePacket subscription(String type, Contact contact) { PresencePacket packet = new PresencePacket(); packet.setAttribute("type", type); @@ -31,12 +37,25 @@ public class PresenceGenerator extends AbstractGenerator { return subscription("subscribed", contact); } - public PresencePacket sendPresence(Account account) { + public PresencePacket selfPresence(Account account, int presence) { PresencePacket packet = new PresencePacket(); + switch(presence) { + case Presences.AWAY: + packet.addChild("show").setContent("away"); + break; + case Presences.XA: + packet.addChild("show").setContent("xa"); + break; + case Presences.CHAT: + packet.addChild("show").setContent("chat"); + break; + case Presences.DND: + packet.addChild("show").setContent("dnd"); + break; + } packet.setFrom(account.getJid()); String sig = account.getPgpSignature(); if (sig != null) { - packet.addChild("status").setContent("online"); packet.addChild("x", "jabber:x:signed").setContent(sig); } String capHash = getCapHash(); diff --git a/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java b/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java index 58a6d1e3..910c43f3 100644 --- a/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java +++ b/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java @@ -1,7 +1,13 @@ package eu.siacs.conversations.http; +import android.os.Build; + import org.apache.http.conn.ssl.StrictHostnameVerifier; +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Proxy; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.util.List; @@ -38,9 +44,9 @@ public class HttpConnectionManager extends AbstractConnectionManager { return connection; } - public HttpUploadConnection createNewUploadConnection(Message message) { + public HttpUploadConnection createNewUploadConnection(Message message, boolean delay) { HttpUploadConnection connection = new HttpUploadConnection(this); - connection.init(message); + connection.init(message,delay); this.uploadConnections.add(connection); return connection; } @@ -87,4 +93,8 @@ public class HttpConnectionManager extends AbstractConnectionManager { } catch (final KeyManagementException | NoSuchAlgorithmException ignored) { } } + + public Proxy getProxy() throws IOException { + return new Proxy(Proxy.Type.HTTP, new InetSocketAddress(InetAddress.getLocalHost(), 8118)); + } } diff --git a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java index 7d364eec..553988ae 100644 --- a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java +++ b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java @@ -1,12 +1,21 @@ package eu.siacs.conversations.http; import android.app.PendingIntent; +import android.content.Intent; +import android.net.Uri; +import android.os.PowerManager; +import android.util.Log; +import android.util.Pair; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; +import java.net.InetAddress; +import java.net.InetSocketAddress; import java.net.MalformedURLException; +import java.net.Proxy; import java.net.URL; import javax.net.ssl.HttpsURLConnection; @@ -17,10 +26,11 @@ import de.thedevstack.conversationsplus.utils.StreamUtil; import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.Account; -import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.entities.DownloadableFile; import eu.siacs.conversations.entities.Message; +import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.persistance.FileBackend; +import eu.siacs.conversations.services.AbstractConnectionManager; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.ui.UiCallback; import eu.siacs.conversations.utils.CryptoHelper; @@ -36,20 +46,25 @@ public class HttpUploadConnection implements Transferable { private XmppConnectionService mXmppConnectionService; private boolean canceled = false; + private boolean delayed = false; private Account account; private DownloadableFile file; private Message message; + private String mime; private URL mGetUrl; private URL mPutUrl; + private boolean mUseTor = false; private byte[] key = null; private long transmitted = 0; - private long expected = 1; + + private InputStream mFileInputStream; public HttpUploadConnection(HttpConnectionManager httpConnectionManager) { this.mHttpConnectionManager = httpConnectionManager; this.mXmppConnectionService = httpConnectionManager.getXmppConnectionService(); + this.mUseTor = mXmppConnectionService.useTorToConnect(); } @Override @@ -64,12 +79,15 @@ public class HttpUploadConnection implements Transferable { @Override public long getFileSize() { - return this.file.getExpectedSize(); + return file == null ? 0 : file.getExpectedSize(); } @Override public int getProgress() { - return (int) ((((double) transmitted) / expected) * 100); + if (file == null) { + return 0; + } + return (int) ((((double) transmitted) / file.getExpectedSize()) * 100); } @Override @@ -80,25 +98,34 @@ public class HttpUploadConnection implements Transferable { private void fail() { mHttpConnectionManager.finishUploadConnection(this); message.setTransferable(null); - mXmppConnectionService.markMessage(message,Message.STATUS_SEND_FAILED); + mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED); + FileBackend.close(mFileInputStream); } - public void init(Message message) { + public void init(Message message, boolean delay) { this.message = message; - message.setTransferable(this); - mXmppConnectionService.markMessage(message,Message.STATUS_UNSEND); this.account = message.getConversation().getAccount(); - this.file = FileBackend.getFile(message, false); - this.file.setExpectedSize(this.file.getSize()); - - if (Config.ENCRYPT_ON_HTTP_UPLOADED) { + this.file = mXmppConnectionService.getFileBackend().getFile(message, false); + this.mime = this.file.getMimeType(); + this.delayed = delay; + if (Config.ENCRYPT_ON_HTTP_UPLOADED + || message.getEncryption() == Message.ENCRYPTION_AXOLOTL + || message.getEncryption() == Message.ENCRYPTION_OTR) { this.key = new byte[48]; mXmppConnectionService.getRNG().nextBytes(this.key); - this.file.setKey(this.key); + this.file.setKeyAndIv(this.key); } - + Pair<InputStream,Integer> pair; + try { + pair = AbstractConnectionManager.createInputStream(file, true); + } catch (FileNotFoundException e) { + fail(); + return; + } + this.file.setExpectedSize(pair.second); + this.mFileInputStream = pair.first; Jid host = account.getXmppConnection().findDiscoItemByFeature(Xmlns.HTTP_UPLOAD); - IqPacket request = mXmppConnectionService.getIqGenerator().requestHttpUploadSlot(host,file); + IqPacket request = mXmppConnectionService.getIqGenerator().requestHttpUploadSlot(host,file,mime); mXmppConnectionService.sendIqPacket(account, request, new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket packet) { @@ -122,6 +149,8 @@ public class HttpUploadConnection implements Transferable { } } }); + message.setTransferable(this); + mXmppConnectionService.markMessage(message, Message.STATUS_UNSEND); } private class FileUploader implements Runnable { @@ -133,47 +162,54 @@ public class HttpUploadConnection implements Transferable { private void upload() { OutputStream os = null; - InputStream is = null; HttpURLConnection connection = null; + PowerManager.WakeLock wakeLock = mHttpConnectionManager.createWakeLock("http_upload_"+message.getUuid()); try { + wakeLock.acquire(); Logging.d(Config.LOGTAG, "uploading to " + mPutUrl.toString()); - connection = (HttpURLConnection) mPutUrl.openConnection(); + if (mUseTor) { + connection = (HttpURLConnection) mPutUrl.openConnection(mHttpConnectionManager.getProxy()); + } else { + connection = (HttpURLConnection) mPutUrl.openConnection(); + } if (connection instanceof HttpsURLConnection) { mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, true); } connection.setRequestMethod("PUT"); connection.setFixedLengthStreamingMode((int) file.getExpectedSize()); + connection.setRequestProperty("Content-Type", mime == null ? "application/octet-stream" : mime); + connection.setRequestProperty("User-Agent",mXmppConnectionService.getIqGenerator().getIdentityName()); connection.setDoOutput(true); connection.connect(); os = connection.getOutputStream(); - is = file.createInputStream(); transmitted = 0; - expected = file.getExpectedSize(); int count = -1; byte[] buffer = new byte[4096]; - while (((count = is.read(buffer)) != -1) && !canceled) { + while (((count = mFileInputStream.read(buffer)) != -1) && !canceled) { transmitted += count; os.write(buffer, 0, count); mXmppConnectionService.updateConversationUi(); } os.flush(); os.close(); - is.close(); + mFileInputStream.close(); int code = connection.getResponseCode(); if (code == 200 || code == 201) { Logging.d(Config.LOGTAG, "finished uploading file"); - Message.FileParams params = message.getFileParams(); if (key != null) { mGetUrl = new URL(mGetUrl.toString() + "#" + CryptoHelper.bytesToHex(key)); } MessageUtil.updateFileParams(message, mGetUrl); + Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); + intent.setData(Uri.fromFile(file)); + mXmppConnectionService.sendBroadcast(intent); message.setTransferable(null); message.setCounterpart(message.getConversation().getJid().toBareJid()); if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { mXmppConnectionService.getPgpEngine().encrypt(message, new UiCallback<Message>() { @Override public void success(Message message) { - mXmppConnectionService.resendMessage(message); + mXmppConnectionService.resendMessage(message,delayed); } @Override @@ -187,7 +223,7 @@ public class HttpUploadConnection implements Transferable { } }); } else { - mXmppConnectionService.resendMessage(message); + mXmppConnectionService.resendMessage(message, delayed); } } else { fail(); @@ -201,6 +237,7 @@ public class HttpUploadConnection implements Transferable { if (connection != null) { connection.disconnect(); } + wakeLock.release(); } } } diff --git a/src/main/java/eu/siacs/conversations/parser/AbstractParser.java b/src/main/java/eu/siacs/conversations/parser/AbstractParser.java index ca20f592..d825543c 100644 --- a/src/main/java/eu/siacs/conversations/parser/AbstractParser.java +++ b/src/main/java/eu/siacs/conversations/parser/AbstractParser.java @@ -12,6 +12,7 @@ import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xmpp.jid.Jid; +import eu.siacs.conversations.xmpp.stanzas.AbstractStanza; public abstract class AbstractParser { @@ -72,16 +73,13 @@ public abstract class AbstractParser { return dateFormat.parse(timestamp); } - protected void updateLastseen(final Element packet, final Account account, - final boolean presenceOverwrite) { - final Jid from = packet.getAttributeAsJid("from"); - updateLastseen(packet, account, from, presenceOverwrite); - } + protected void updateLastseen(final AbstractStanza packet, final Account account, final boolean presenceOverwrite) { + updateLastseen(getTimestamp(packet), account, packet.getFrom(), presenceOverwrite); + } - protected void updateLastseen(final Element packet, final Account account, final Jid from, final boolean presenceOverwrite) { + protected void updateLastseen(long timestamp, final Account account, final Jid from, final boolean presenceOverwrite) { final String presence = from == null || from.isBareJid() ? "" : from.getResourcepart(); final Contact contact = account.getRoster().getContact(from); - final long timestamp = getTimestamp(packet); if (timestamp >= contact.lastseen.time) { contact.lastseen.time = timestamp; if (!presence.isEmpty() && presenceOverwrite) { @@ -89,4 +87,12 @@ public abstract class AbstractParser { } } } + + protected String avatarData(Element items) { + Element item = items.findChild("item"); + if (item == null) { + return null; + } + return item.findChildContent("data", "urn:xmpp:avatar:data"); + } } diff --git a/src/main/java/eu/siacs/conversations/parser/IqParser.java b/src/main/java/eu/siacs/conversations/parser/IqParser.java index 1183fe23..4406a28a 100644 --- a/src/main/java/eu/siacs/conversations/parser/IqParser.java +++ b/src/main/java/eu/siacs/conversations/parser/IqParser.java @@ -1,11 +1,32 @@ package eu.siacs.conversations.parser; +import android.support.annotation.NonNull; +import android.util.Base64; +import android.util.Log; +import android.util.Pair; + +import org.whispersystems.libaxolotl.IdentityKey; +import org.whispersystems.libaxolotl.InvalidKeyException; +import org.whispersystems.libaxolotl.ecc.Curve; +import org.whispersystems.libaxolotl.ecc.ECPublicKey; +import org.whispersystems.libaxolotl.state.PreKeyBundle; + +import java.io.ByteArrayInputStream; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; import de.thedevstack.android.logcat.Logging; import eu.siacs.conversations.Config; +import eu.siacs.conversations.crypto.axolotl.AxolotlService; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.services.AvatarService; @@ -59,9 +80,201 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { mXmppConnectionService.updateRosterUi(); } + public String avatarData(final IqPacket packet) { + final Element pubsub = packet.findChild("pubsub", + "http://jabber.org/protocol/pubsub"); + if (pubsub == null) { + return null; + } + final Element items = pubsub.findChild("items"); + if (items == null) { + return null; + } + return super.avatarData(items); + } + + public Element getItem(final IqPacket packet) { + final Element pubsub = packet.findChild("pubsub", + "http://jabber.org/protocol/pubsub"); + if (pubsub == null) { + return null; + } + final Element items = pubsub.findChild("items"); + if (items == null) { + return null; + } + return items.findChild("item"); + } + + @NonNull + public Set<Integer> deviceIds(final Element item) { + Set<Integer> deviceIds = new HashSet<>(); + if (item != null) { + final Element list = item.findChild("list"); + if (list != null) { + for (Element device : list.getChildren()) { + if (!device.getName().equals("device")) { + continue; + } + try { + Integer id = Integer.valueOf(device.getAttribute("id")); + deviceIds.add(id); + } catch (NumberFormatException e) { + Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Encountered invalid <device> node in PEP ("+e.getMessage()+"):" + device.toString()+ ", skipping..."); + continue; + } + } + } + } + return deviceIds; + } + + public Integer signedPreKeyId(final Element bundle) { + final Element signedPreKeyPublic = bundle.findChild("signedPreKeyPublic"); + if(signedPreKeyPublic == null) { + return null; + } + return Integer.valueOf(signedPreKeyPublic.getAttribute("signedPreKeyId")); + } + + public ECPublicKey signedPreKeyPublic(final Element bundle) { + ECPublicKey publicKey = null; + final Element signedPreKeyPublic = bundle.findChild("signedPreKeyPublic"); + if(signedPreKeyPublic == null) { + return null; + } + try { + publicKey = Curve.decodePoint(Base64.decode(signedPreKeyPublic.getContent(),Base64.DEFAULT), 0); + } catch (InvalidKeyException | IllegalArgumentException e) { + Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Invalid signedPreKeyPublic in PEP: " + e.getMessage()); + } + return publicKey; + } + + public byte[] signedPreKeySignature(final Element bundle) { + final Element signedPreKeySignature = bundle.findChild("signedPreKeySignature"); + if(signedPreKeySignature == null) { + return null; + } + try { + return Base64.decode(signedPreKeySignature.getContent(), Base64.DEFAULT); + } catch (IllegalArgumentException e) { + Log.e(Config.LOGTAG,AxolotlService.LOGPREFIX+" : Invalid base64 in signedPreKeySignature"); + return null; + } + } + + public IdentityKey identityKey(final Element bundle) { + IdentityKey identityKey = null; + final Element identityKeyElement = bundle.findChild("identityKey"); + if(identityKeyElement == null) { + return null; + } + try { + identityKey = new IdentityKey(Base64.decode(identityKeyElement.getContent(), Base64.DEFAULT), 0); + } catch (InvalidKeyException | IllegalArgumentException e) { + Log.e(Config.LOGTAG,AxolotlService.LOGPREFIX+" : "+"Invalid identityKey in PEP: "+e.getMessage()); + } + return identityKey; + } + + public Map<Integer, ECPublicKey> preKeyPublics(final IqPacket packet) { + Map<Integer, ECPublicKey> preKeyRecords = new HashMap<>(); + Element item = getItem(packet); + if (item == null) { + Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Couldn't find <item> in bundle IQ packet: " + packet); + return null; + } + final Element bundleElement = item.findChild("bundle"); + if(bundleElement == null) { + return null; + } + final Element prekeysElement = bundleElement.findChild("prekeys"); + if(prekeysElement == null) { + Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Couldn't find <prekeys> in bundle IQ packet: " + packet); + return null; + } + for(Element preKeyPublicElement : prekeysElement.getChildren()) { + if(!preKeyPublicElement.getName().equals("preKeyPublic")){ + Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Encountered unexpected tag in prekeys list: " + preKeyPublicElement); + continue; + } + Integer preKeyId = Integer.valueOf(preKeyPublicElement.getAttribute("preKeyId")); + try { + ECPublicKey preKeyPublic = Curve.decodePoint(Base64.decode(preKeyPublicElement.getContent(), Base64.DEFAULT), 0); + preKeyRecords.put(preKeyId, preKeyPublic); + } catch (InvalidKeyException | IllegalArgumentException e) { + Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Invalid preKeyPublic (ID="+preKeyId+") in PEP: "+ e.getMessage()+", skipping..."); + continue; + } + } + return preKeyRecords; + } + + public Pair<X509Certificate[],byte[]> verification(final IqPacket packet) { + Element item = getItem(packet); + Element verification = item != null ? item.findChild("verification",AxolotlService.PEP_PREFIX) : null; + Element chain = verification != null ? verification.findChild("chain") : null; + Element signature = verification != null ? verification.findChild("signature") : null; + if (chain != null && signature != null) { + List<Element> certElements = chain.getChildren(); + X509Certificate[] certificates = new X509Certificate[certElements.size()]; + try { + CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); + int i = 0; + for(Element cert : certElements) { + certificates[i] = (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(Base64.decode(cert.getContent(),Base64.DEFAULT))); + ++i; + } + return new Pair<>(certificates,Base64.decode(signature.getContent(),Base64.DEFAULT)); + } catch (CertificateException e) { + return null; + } + } else { + return null; + } + } + + public PreKeyBundle bundle(final IqPacket bundle) { + Element bundleItem = getItem(bundle); + if(bundleItem == null) { + return null; + } + final Element bundleElement = bundleItem.findChild("bundle"); + if(bundleElement == null) { + return null; + } + ECPublicKey signedPreKeyPublic = signedPreKeyPublic(bundleElement); + Integer signedPreKeyId = signedPreKeyId(bundleElement); + byte[] signedPreKeySignature = signedPreKeySignature(bundleElement); + IdentityKey identityKey = identityKey(bundleElement); + if(signedPreKeyPublic == null || identityKey == null) { + return null; + } + + return new PreKeyBundle(0, 0, 0, null, + signedPreKeyId, signedPreKeyPublic, signedPreKeySignature, identityKey); + } + + public List<PreKeyBundle> preKeys(final IqPacket preKeys) { + List<PreKeyBundle> bundles = new ArrayList<>(); + Map<Integer, ECPublicKey> preKeyPublics = preKeyPublics(preKeys); + if ( preKeyPublics != null) { + for (Integer preKeyId : preKeyPublics.keySet()) { + ECPublicKey preKeyPublic = preKeyPublics.get(preKeyId); + bundles.add(new PreKeyBundle(0, 0, preKeyId, preKeyPublic, + 0, null, null, null)); + } + } + + return bundles; + } + @Override public void onIqPacketReceived(final Account account, final IqPacket packet) { - if (packet.hasChild("query", Xmlns.ROSTER) && packet.fromServer(account)) { + if (packet.getType() == IqPacket.TYPE.ERROR || packet.getType() == IqPacket.TYPE.TIMEOUT) { + return; + } else if (packet.hasChild("query", Xmlns.ROSTER) && packet.fromServer(account)) { final Element query = packet.findChild("query"); // If this is in response to a query for the whole roster: if (packet.getType() == IqPacket.TYPE.RESULT) { @@ -131,15 +344,13 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { final IqPacket response = packet.generateResponse(IqPacket.TYPE.RESULT); mXmppConnectionService.sendIqPacket(account, response, null); } else { - if ((packet.getType() == IqPacket.TYPE.GET) - || (packet.getType() == IqPacket.TYPE.SET)) { + if (packet.getType() == IqPacket.TYPE.GET || packet.getType() == IqPacket.TYPE.SET) { final IqPacket response = packet.generateResponse(IqPacket.TYPE.ERROR); final Element error = response.addChild("error"); error.setAttribute("type", "cancel"); - error.addChild("feature-not-implemented", - "urn:ietf:params:xml:ns:xmpp-stanzas"); + error.addChild("feature-not-implemented","urn:ietf:params:xml:ns:xmpp-stanzas"); account.getXmppConnection().sendIqPacket(response, null); - } + } } } diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 10d78bac..99354100 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -1,16 +1,23 @@ package eu.siacs.conversations.parser; +import android.util.Log; import android.util.Pair; +import eu.siacs.conversations.crypto.PgpDecryptionService; import net.java.otr4j.session.Session; import net.java.otr4j.session.SessionStatus; +import java.util.ArrayList; +import java.util.Set; import de.thedevstack.android.logcat.Logging; import de.thedevstack.conversationsplus.ConversationsPlusPreferences; import de.thedevstack.conversationsplus.utils.AvatarUtil; import eu.siacs.conversations.Config; +import eu.siacs.conversations.crypto.axolotl.AxolotlService; +import eu.siacs.conversations.crypto.axolotl.XmppAxolotlMessage; import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.entities.Bookmark; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Message; @@ -40,6 +47,10 @@ public class MessageParser extends AbstractParser implements Jid from = packet.getFrom(); if (from.toBareJid().equals(account.getJid().toBareJid())) { conversation.setOutgoingChatState(state); + if (state == ChatState.ACTIVE || state == ChatState.COMPOSING) { + mXmppConnectionService.markRead(conversation); + account.activateGracePeriod(); + } return false; } else { return conversation.setIncomingChatState(state); @@ -73,11 +84,9 @@ public class MessageParser extends AbstractParser implements body = otrSession.transformReceiving(body); SessionStatus status = otrSession.getSessionStatus(); if (body == null && status == SessionStatus.ENCRYPTED) { - conversation.setNextEncryption(Message.ENCRYPTION_OTR); mXmppConnectionService.onOtrSessionEstablished(conversation); return null; } else if (body == null && status == SessionStatus.FINISHED) { - conversation.setNextEncryption(Message.ENCRYPTION_NONE); conversation.resetOtrSession(); mXmppConnectionService.updateConversationUi(); return null; @@ -98,6 +107,27 @@ public class MessageParser extends AbstractParser implements } } + private Message parseAxolotlChat(Element axolotlMessage, Jid from, String id, Conversation conversation, int status) { + Message finishedMessage = null; + AxolotlService service = conversation.getAccount().getAxolotlService(); + XmppAxolotlMessage xmppAxolotlMessage = XmppAxolotlMessage.fromElement(axolotlMessage, from.toBareJid()); + XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = service.processReceivingPayloadMessage(xmppAxolotlMessage); + if(plaintextMessage != null) { + finishedMessage = new Message(conversation, plaintextMessage.getPlaintext(), Message.ENCRYPTION_AXOLOTL, status); + finishedMessage.setAxolotlFingerprint(plaintextMessage.getFingerprint()); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(finishedMessage.getConversation().getAccount())+" Received Message with session fingerprint: "+plaintextMessage.getFingerprint()); + } + + return finishedMessage; + } + + private Message parsePGPChat(final Conversation conversation, String pgpEncrypted, int status) { + final Message message = new Message(conversation, pgpEncrypted, Message.ENCRYPTION_PGP, status); + PgpDecryptionService pgpDecryptionService = conversation.getAccount().getPgpDecryptionService(); + pgpDecryptionService.add(message); + return message; + } + private class Invite { Jid jid; String password; @@ -138,13 +168,24 @@ public class MessageParser extends AbstractParser implements return null; } + private static String extractStanzaId(Element packet, Jid by) { + for(Element child : packet.getChildren()) { + if (child.getName().equals("stanza-id") + && "urn:xmpp:sid:0".equals(child.getNamespace()) + && by.equals(child.getAttributeAsJid("by"))) { + return child.getAttribute("id"); + } + } + return null; + } + private void parseEvent(final Element event, final Jid from, final Account account) { Element items = event.findChild("items"); String node = items == null ? null : items.getAttribute("node"); if ("urn:xmpp:avatar:metadata".equals(node)) { Avatar avatar = Avatar.parseMetadata(items); if (avatar != null) { - avatar.owner = from; + avatar.owner = from.toBareJid(); if (AvatarUtil.isAvatarCached(avatar)) { if (account.getJid().toBareJid().equals(from)) { if (account.setAvatar(avatar.getFilename())) { @@ -167,13 +208,20 @@ public class MessageParser extends AbstractParser implements } else if ("http://jabber.org/protocol/nick".equals(node)) { Element i = items.findChild("item"); Element nick = i == null ? null : i.findChild("nick", "http://jabber.org/protocol/nick"); - if (nick != null) { + if (nick != null && nick.getContent() != null) { Contact contact = account.getRoster().getContact(from); contact.setPresenceName(nick.getContent()); AvatarService.getInstance().clear(account); mXmppConnectionService.updateConversationUi(); mXmppConnectionService.updateAccountUi(); } + } else if (AxolotlService.PEP_DEVICE_LIST.equals(node)) { + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Received PEP device list update from "+ from + ", processing..."); + Element item = items.findChild("item"); + Set<Integer> deviceIds = mXmppConnectionService.getIqParser().deviceIds(item); + AxolotlService axolotlService = account.getAxolotlService(); + axolotlService.registerDevices(from, deviceIds); + mXmppConnectionService.updateAccountUi(); } } @@ -181,6 +229,13 @@ public class MessageParser extends AbstractParser implements if (packet.getType() == MessagePacket.TYPE_ERROR) { Jid from = packet.getFrom(); if (from != null) { + Element error = packet.findChild("error"); + String text = error == null ? null : error.findChildContent("text"); + if (text != null) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": sending message to "+ from+ " failed - " + text); + } else if (error != null) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": sending message to "+ from+ " failed - " + error); + } Message message = mXmppConnectionService.markMessage(account, from.toBareJid(), packet.getId(), @@ -202,6 +257,7 @@ public class MessageParser extends AbstractParser implements final MessagePacket packet; Long timestamp = null; final boolean isForwarded; + boolean isCarbon = false; String serverMsgId = null; final Element fin = original.findChild("fin", "urn:xmpp:mam:0"); if (fin != null) { @@ -232,7 +288,8 @@ public class MessageParser extends AbstractParser implements return; } timestamp = f != null ? f.second : null; - isForwarded = f != null; + isCarbon = f != null; + isForwarded = isCarbon; } else { packet = original; isForwarded = false; @@ -242,25 +299,26 @@ public class MessageParser extends AbstractParser implements timestamp = AbstractParser.getTimestamp(packet, System.currentTimeMillis()); } final String body = packet.getBody(); - final String encrypted = packet.findChildContent("x", "jabber:x:encrypted"); - final Element mucUserElement = packet.findChild("x","http://jabber.org/protocol/muc#user"); + final Element mucUserElement = packet.findChild("x", "http://jabber.org/protocol/muc#user"); + final String pgpEncrypted = packet.findChildContent("x", "jabber:x:encrypted"); + final Element axolotlEncrypted = packet.findChild(XmppAxolotlMessage.CONTAINERTAG, AxolotlService.PEP_PREFIX); int status; final Jid counterpart; final Jid to = packet.getTo(); final Jid from = packet.getFrom(); final String remoteMsgId = packet.getId(); - if (from == null || to == null) { - Logging.d(Config.LOGTAG,"no to or from in: "+packet.toString()); + if (from == null) { + Log.d(Config.LOGTAG,"no from in: "+packet.toString()); return; } boolean isTypeGroupChat = packet.getType() == MessagePacket.TYPE_GROUPCHAT; - boolean isProperlyAddressed = !to.isBareJid() || account.countPresences() == 1; + boolean isProperlyAddressed = (to != null ) && (!to.isBareJid() || account.countPresences() == 1); boolean isMucStatusMessage = from.isBareJid() && mucUserElement != null && mucUserElement.hasChild("status"); if (packet.fromAccount(account)) { status = Message.STATUS_SEND; - counterpart = to; + counterpart = to != null ? to : account.getJid(); } else { status = Message.STATUS_RECEIVED; counterpart = from; @@ -271,21 +329,20 @@ public class MessageParser extends AbstractParser implements return; } - if (extractChatState(mXmppConnectionService.find(account,from), packet)) { + if (extractChatState(mXmppConnectionService.find(account, counterpart.toBareJid()), packet)) { mXmppConnectionService.updateConversationUi(); } - if ((body != null || encrypted != null) && !isMucStatusMessage) { - Conversation conversation = mXmppConnectionService.findOrCreateConversation(account, counterpart.toBareJid(), isTypeGroupChat); + if ((body != null || pgpEncrypted != null || axolotlEncrypted != null) && !isMucStatusMessage) { + Conversation conversation = mXmppConnectionService.findOrCreateConversation(account, counterpart.toBareJid(), isTypeGroupChat, query); if (isTypeGroupChat) { if (counterpart.getResourcepart().equals(conversation.getMucOptions().getActualNick())) { status = Message.STATUS_SEND_RECEIVED; if (mXmppConnectionService.markMessage(conversation, remoteMsgId, status)) { return; - } else { - Message message = conversation.findSentMessageWithBody(body); + } else if (remoteMsgId == null || Config.IGNORE_ID_REWRITE_IN_MUC) { + Message message = conversation.findSentMessageWithBody(packet.getBody()); if (message != null) { - message.setRemoteMsgId(remoteMsgId); mXmppConnectionService.markMessage(message, status); return; } @@ -304,59 +361,80 @@ public class MessageParser extends AbstractParser implements } else { message = new Message(conversation, body, Message.ENCRYPTION_NONE, status); } - } else if (encrypted != null) { - message = new Message(conversation, encrypted, Message.ENCRYPTION_PGP, status); + } else if (pgpEncrypted != null) { + message = parsePGPChat(conversation, pgpEncrypted, status); + } else if (axolotlEncrypted != null) { + message = parseAxolotlChat(axolotlEncrypted, from, remoteMsgId, conversation, status); + if (message == null) { + return; + } } else { message = new Message(conversation, body, Message.ENCRYPTION_NONE, status); } + + if (serverMsgId == null) { + serverMsgId = extractStanzaId(packet, isTypeGroupChat ? conversation.getJid().toBareJid() : account.getServer()); + } + message.setCounterpart(counterpart); message.setRemoteMsgId(remoteMsgId); message.setServerMsgId(serverMsgId); + message.setCarbon(isCarbon); message.setTime(timestamp); message.markable = packet.hasChild("markable", "urn:xmpp:chat-markers:0"); if (conversation.getMode() == Conversation.MODE_MULTI) { - message.setTrueCounterpart(conversation.getMucOptions().getTrueCounterpart(counterpart.getResourcepart())); + Jid trueCounterpart = conversation.getMucOptions().getTrueCounterpart(counterpart.getResourcepart()); + message.setTrueCounterpart(trueCounterpart); + if (trueCounterpart != null) { + updateLastseen(timestamp, account, trueCounterpart, false); + } if (!isTypeGroupChat) { message.setType(Message.TYPE_PRIVATE); } + } else { + updateLastseen(timestamp, account, packet.getFrom(), true); } - updateLastseen(packet,account,true); - boolean checkForDuplicates = serverMsgId != null + boolean checkForDuplicates = query != null || (isTypeGroupChat && packet.hasChild("delay","urn:xmpp:delay")) || message.getType() == Message.TYPE_PRIVATE; if (checkForDuplicates && conversation.hasDuplicateMessage(message)) { - Logging.d(Config.LOGTAG,"skipping duplicate message from "+message.getCounterpart().toString()+" "+message.getBody()); + Log.d(Config.LOGTAG,"skipping duplicate message from "+message.getCounterpart().toString()+" "+message.getBody()); return; } - if (query != null) { - query.incrementMessageCount(); - } + conversation.add(message); - if (serverMsgId == null) { + + if (query == null || query.getWith() == null) { //either no mam or catchup if (status == Message.STATUS_SEND || status == Message.STATUS_SEND_RECEIVED) { mXmppConnectionService.markRead(conversation); - account.activateGracePeriod(); + if (query == null) { + account.activateGracePeriod(); + } } else { message.markUnread(); } + } + + if (query != null) { + query.incrementMessageCount(); + } else { mXmppConnectionService.updateConversationUi(); } - if (ConversationsPlusPreferences.confirmMessages() && remoteMsgId != null && !isForwarded) { + if (ConversationsPlusPreferences.confirmMessages() && remoteMsgId != null && !isForwarded && !isTypeGroupChat) { + ArrayList<String> receiptsNamespaces = new ArrayList<>(); if (packet.hasChild("markable", "urn:xmpp:chat-markers:0")) { - MessagePacket receipt = mXmppConnectionService - .getMessageGenerator().received(account, packet, "urn:xmpp:chat-markers:0"); - mXmppConnectionService.sendMessagePacket(account, receipt); + receiptsNamespaces.add("urn:xmpp:chat-markers:0"); } if (packet.hasChild("request", "urn:xmpp:receipts")) { - MessagePacket receipt = mXmppConnectionService - .getMessageGenerator().received(account, packet, "urn:xmpp:receipts"); - mXmppConnectionService.sendMessagePacket(account, receipt); + receiptsNamespaces.add("urn:xmpp:receipts"); } - } - if (account.getXmppConnection() != null && account.getXmppConnection().getFeatures().advancedStreamFeaturesLoaded()) { - if (conversation.setLastMessageTransmitted(System.currentTimeMillis())) { - mXmppConnectionService.updateConversation(conversation); + if (receiptsNamespaces.size() > 0) { + MessagePacket receipt = mXmppConnectionService.getMessageGenerator().received(account, + packet, + receiptsNamespaces, + packet.getType()); + mXmppConnectionService.sendMessagePacket(account, receipt); } } @@ -377,16 +455,27 @@ public class MessageParser extends AbstractParser implements && ConversationsPlusPreferences.autoDownloadFileLink() && mXmppConnectionService.isDownloadAllowedInConnection()) { manager.createNewDownloadConnection(message); - } else { - mXmppConnectionService.getNotificationService().push(message); + } else if (!message.isRead()) { + if (query == null) { + mXmppConnectionService.getNotificationService().push(message); + } else if (query.getWith() == null) { // mam catchup + mXmppConnectionService.getNotificationService().pushFromBacklog(message); + } } - } else { //no body + } else if (!packet.hasChild("body")){ //no body if (isTypeGroupChat) { Conversation conversation = mXmppConnectionService.find(account, from.toBareJid()); if (packet.hasChild("subject")) { if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) { conversation.setHasMessagesLeftOnServer(conversation.countMessages() > 0); - conversation.getMucOptions().setSubject(packet.findChildContent("subject")); + String subject = packet.findChildContent("subject"); + conversation.getMucOptions().setSubject(subject); + final Bookmark bookmark = conversation.getBookmark(); + if (bookmark != null && bookmark.getBookmarkName() == null) { + if (bookmark.setBookmarkName(subject)) { + mXmppConnectionService.pushBookmarks(account); + } + } mXmppConnectionService.updateConversationUi(); return; } @@ -418,7 +507,7 @@ public class MessageParser extends AbstractParser implements mXmppConnectionService.markRead(conversation); } } else { - updateLastseen(packet, account, true); + updateLastseen(timestamp, account, packet.getFrom(), true); final Message displayedMessage = mXmppConnectionService.markMessage(account, from.toBareJid(), displayed.getAttribute("id"), Message.STATUS_SEND_DISPLAYED); Message message = displayedMessage == null ? null : displayedMessage.prev(); while (message != null diff --git a/src/main/java/eu/siacs/conversations/parser/PresenceParser.java b/src/main/java/eu/siacs/conversations/parser/PresenceParser.java index 5faf6eaf..bbb21c84 100644 --- a/src/main/java/eu/siacs/conversations/parser/PresenceParser.java +++ b/src/main/java/eu/siacs/conversations/parser/PresenceParser.java @@ -1,14 +1,19 @@ package eu.siacs.conversations.parser; -import java.util.ArrayList; +import android.util.Log; +import java.util.ArrayList; +import java.util.List; import de.thedevstack.conversationsplus.utils.AvatarUtil; import de.thedevstack.conversationsplus.utils.UiUpdateHelper; + +import eu.siacs.conversations.Config; import eu.siacs.conversations.crypto.PgpEngine; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; +import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.MucOptions; import eu.siacs.conversations.entities.Presences; import eu.siacs.conversations.generator.PresenceGenerator; @@ -28,19 +33,19 @@ public class PresenceParser extends AbstractParser implements } public void parseConferencePresence(PresencePacket packet, Account account) { - PgpEngine mPgpEngine = mXmppConnectionService.getPgpEngine(); final Conversation conversation = packet.getFrom() == null ? null : mXmppConnectionService.find(account, packet.getFrom().toBareJid()); if (conversation != null) { final MucOptions mucOptions = conversation.getMucOptions(); boolean before = mucOptions.online(); - int count = mucOptions.getUsers().size(); - final ArrayList<MucOptions.User> tileUserBefore = new ArrayList<>(mucOptions.getUsers().subList(0,Math.min(mucOptions.getUsers().size(),5))); - mucOptions.processPacket(packet, mPgpEngine); - final ArrayList<MucOptions.User> tileUserAfter = new ArrayList<>(mucOptions.getUsers().subList(0,Math.min(mucOptions.getUsers().size(),5))); + int count = mucOptions.getUserCount(); + final List<MucOptions.User> tileUserBefore = mucOptions.getUsers(5); + processConferencePresence(packet, mucOptions); + final List<MucOptions.User> tileUserAfter = mucOptions.getUsers(5); if (!tileUserAfter.equals(tileUserBefore)) { + Loggin.d(Config.LOGTAG,account.getJid().toBareJid()+": update tiles for "+conversation.getName()); AvatarService.getInstance().clear(conversation); } - if (before != mucOptions.online() || (mucOptions.online() && count != mucOptions.getUsers().size())) { + if (before != mucOptions.online() || (mucOptions.online() && count != mucOptions.getUserCount())) { UiUpdateHelper.updateConversationUi(); } else if (mucOptions.online()) { UiUpdateHelper.updateMucRosterUi(); @@ -48,8 +53,116 @@ public class PresenceParser extends AbstractParser implements } } - public void parseContactPresence(PresencePacket packet, Account account) { - PresenceGenerator mPresenceGenerator = mXmppConnectionService.getPresenceGenerator(); + private void processConferencePresence(PresencePacket packet, MucOptions mucOptions) { + final Jid from = packet.getFrom(); + if (!from.isBareJid()) { + final String type = packet.getAttribute("type"); + final Element x = packet.findChild("x", "http://jabber.org/protocol/muc#user"); + Avatar avatar = Avatar.parsePresence(packet.findChild("x", "vcard-temp:x:update")); + final List<String> codes = getStatusCodes(x); + if (type == null) { + if (x != null) { + Element item = x.findChild("item"); + if (item != null && !from.isBareJid()) { + mucOptions.setError(MucOptions.ERROR_NO_ERROR); + MucOptions.User user = new MucOptions.User(mucOptions,from); + user.setAffiliation(item.getAttribute("affiliation")); + user.setRole(item.getAttribute("role")); + user.setJid(item.getAttributeAsJid("jid")); + if (codes.contains(MucOptions.STATUS_CODE_SELF_PRESENCE) || packet.getFrom().equals(mucOptions.getConversation().getJid())) { + mucOptions.setOnline(); + mucOptions.setSelf(user); + if (mucOptions.mNickChangingInProgress) { + if (mucOptions.onRenameListener != null) { + mucOptions.onRenameListener.onSuccess(); + } + mucOptions.mNickChangingInProgress = false; + } + } else { + mucOptions.addUser(user); + } + if (mXmppConnectionService.getPgpEngine() != null) { + Element signed = packet.findChild("x", "jabber:x:signed"); + if (signed != null) { + Element status = packet.findChild("status"); + String msg = status == null ? "" : status.getContent(); + long keyId = mXmppConnectionService.getPgpEngine().fetchKeyId(mucOptions.getAccount(), msg, signed.getContent()); + if (keyId != 0) { + user.setPgpKeyId(keyId); + } + } + } + if (avatar != null) { + avatar.owner = from; + if (mXmppConnectionService.getFileBackend().isAvatarCached(avatar)) { + if (user.setAvatar(avatar)) { + mXmppConnectionService.getAvatarService().clear(user); + } + } else { + mXmppConnectionService.fetchAvatar(mucOptions.getAccount(), avatar); + } + } + } + } + } else if (type.equals("unavailable")) { + if (codes.contains(MucOptions.STATUS_CODE_SELF_PRESENCE) || + packet.getFrom().equals(mucOptions.getConversation().getJid())) { + if (codes.contains(MucOptions.STATUS_CODE_CHANGED_NICK)) { + mucOptions.mNickChangingInProgress = true; + } else if (codes.contains(MucOptions.STATUS_CODE_KICKED)) { + mucOptions.setError(MucOptions.KICKED_FROM_ROOM); + } else if (codes.contains(MucOptions.STATUS_CODE_BANNED)) { + mucOptions.setError(MucOptions.ERROR_BANNED); + } else if (codes.contains(MucOptions.STATUS_CODE_LOST_MEMBERSHIP)) { + mucOptions.setError(MucOptions.ERROR_MEMBERS_ONLY); + } else { + mucOptions.setError(MucOptions.ERROR_UNKNOWN); + Log.d(Config.LOGTAG, "unknown error in conference: " + packet); + } + } else if (!from.isBareJid()){ + MucOptions.User user = mucOptions.deleteUser(from.getResourcepart()); + if (user != null) { + mXmppConnectionService.getAvatarService().clear(user); + } + } + } else if (type.equals("error")) { + Element error = packet.findChild("error"); + if (error != null && error.hasChild("conflict")) { + if (mucOptions.online()) { + if (mucOptions.onRenameListener != null) { + mucOptions.onRenameListener.onFailure(); + } + } else { + mucOptions.setError(MucOptions.ERROR_NICK_IN_USE); + } + } else if (error != null && error.hasChild("not-authorized")) { + mucOptions.setError(MucOptions.ERROR_PASSWORD_REQUIRED); + } else if (error != null && error.hasChild("forbidden")) { + mucOptions.setError(MucOptions.ERROR_BANNED); + } else if (error != null && error.hasChild("registration-required")) { + mucOptions.setError(MucOptions.ERROR_MEMBERS_ONLY); + } + } + } + } + + private static List<String> getStatusCodes(Element x) { + List<String> codes = new ArrayList<>(); + if (x != null) { + for (Element child : x.getChildren()) { + if (child.getName().equals("status")) { + String code = child.getAttribute("code"); + if (code != null) { + codes.add(code); + } + } + } + } + return codes; + } + + public void parseContactPresence(final PresencePacket packet, final Account account) { + final PresenceGenerator mPresenceGenerator = mXmppConnectionService.getPresenceGenerator(); final Jid from = packet.getFrom(); if (from == null) { return; @@ -97,6 +210,19 @@ public class PresenceParser extends AbstractParser implements mPresenceGenerator.sendPresenceUpdatesTo(contact)); } else { contact.setOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST); + final Conversation conversation = mXmppConnectionService.findOrCreateConversation( + account, contact.getJid().toBareJid(), false); + final String statusMessage = packet.findChildContent("status"); + if (statusMessage != null + && !statusMessage.isEmpty() + && conversation.countMessages() == 0) { + conversation.add(new Message( + conversation, + statusMessage, + Message.ENCRYPTION_NONE, + Message.STATUS_RECEIVED + )); + } } } UiUpdateHelper.updateRosterUi(); diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index 50f6d4d4..79d69348 100644 --- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -1,12 +1,43 @@ package eu.siacs.conversations.persistance; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.DatabaseUtils; +import android.database.sqlite.SQLiteCantOpenDatabaseException; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.util.Base64; +import android.util.Log; +import android.util.Pair; + +import org.whispersystems.libaxolotl.AxolotlAddress; +import org.whispersystems.libaxolotl.IdentityKey; +import org.whispersystems.libaxolotl.IdentityKeyPair; +import org.whispersystems.libaxolotl.InvalidKeyException; +import org.whispersystems.libaxolotl.state.PreKeyRecord; +import org.whispersystems.libaxolotl.state.SessionRecord; +import org.whispersystems.libaxolotl.state.SignedPreKeyRecord; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; import java.util.List; +import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import de.thedevstack.android.logcat.Logging; import eu.siacs.conversations.Config; +import eu.siacs.conversations.crypto.axolotl.AxolotlService; +import eu.siacs.conversations.crypto.axolotl.SQLiteAxolotlStore; +import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; @@ -26,7 +57,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { private static DatabaseBackend instance = null; private static final String DATABASE_NAME = "history"; - private static final int DATABASE_VERSION = 14; + private static final int DATABASE_VERSION = 22; private static String CREATE_CONTATCS_STATEMENT = "create table " + Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, " @@ -34,12 +65,67 @@ public class DatabaseBackend extends SQLiteOpenHelper { + Contact.JID + " TEXT," + Contact.KEYS + " TEXT," + Contact.PHOTOURI + " TEXT," + Contact.OPTIONS + " NUMBER," + Contact.SYSTEMACCOUNT + " NUMBER, " + Contact.AVATAR + " TEXT, " - + Contact.LAST_PRESENCE + " TEXT, " + Contact.LAST_TIME + " NUMBER, " + + Contact.LAST_PRESENCE + " TEXT, " + Contact.LAST_TIME + " NUMBER, " + Contact.GROUPS + " TEXT, FOREIGN KEY(" + Contact.ACCOUNT + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, UNIQUE(" + Contact.ACCOUNT + ", " + Contact.JID + ") ON CONFLICT REPLACE);"; + private static String CREATE_PREKEYS_STATEMENT = "CREATE TABLE " + + SQLiteAxolotlStore.PREKEY_TABLENAME + "(" + + SQLiteAxolotlStore.ACCOUNT + " TEXT, " + + SQLiteAxolotlStore.ID + " INTEGER, " + + SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY(" + + SQLiteAxolotlStore.ACCOUNT + + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, " + + "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", " + + SQLiteAxolotlStore.ID + + ") ON CONFLICT REPLACE" + + ");"; + + private static String CREATE_SIGNED_PREKEYS_STATEMENT = "CREATE TABLE " + + SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME + "(" + + SQLiteAxolotlStore.ACCOUNT + " TEXT, " + + SQLiteAxolotlStore.ID + " INTEGER, " + + SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY(" + + SQLiteAxolotlStore.ACCOUNT + + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, " + + "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", " + + SQLiteAxolotlStore.ID + + ") ON CONFLICT REPLACE" + + ");"; + + private static String CREATE_SESSIONS_STATEMENT = "CREATE TABLE " + + SQLiteAxolotlStore.SESSION_TABLENAME + "(" + + SQLiteAxolotlStore.ACCOUNT + " TEXT, " + + SQLiteAxolotlStore.NAME + " TEXT, " + + SQLiteAxolotlStore.DEVICE_ID + " INTEGER, " + + SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY(" + + SQLiteAxolotlStore.ACCOUNT + + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, " + + "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", " + + SQLiteAxolotlStore.NAME + ", " + + SQLiteAxolotlStore.DEVICE_ID + + ") ON CONFLICT REPLACE" + + ");"; + + private static String CREATE_IDENTITIES_STATEMENT = "CREATE TABLE " + + SQLiteAxolotlStore.IDENTITIES_TABLENAME + "(" + + SQLiteAxolotlStore.ACCOUNT + " TEXT, " + + SQLiteAxolotlStore.NAME + " TEXT, " + + SQLiteAxolotlStore.OWN + " INTEGER, " + + SQLiteAxolotlStore.FINGERPRINT + " TEXT, " + + SQLiteAxolotlStore.CERTIFICATE + " BLOB, " + + SQLiteAxolotlStore.TRUSTED + " INTEGER, " + + SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY(" + + SQLiteAxolotlStore.ACCOUNT + + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, " + + "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", " + + SQLiteAxolotlStore.NAME + ", " + + SQLiteAxolotlStore.FINGERPRINT + + ") ON CONFLICT IGNORE" + + ");"; + private DatabaseBackend(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @@ -50,9 +136,10 @@ public class DatabaseBackend extends SQLiteOpenHelper { db.execSQL("create table " + Account.TABLENAME + "(" + Account.UUID + " TEXT PRIMARY KEY," + Account.USERNAME + " TEXT," + Account.SERVER + " TEXT," + Account.PASSWORD + " TEXT," + + Account.DISPLAY_NAME + " TEXT, " + Account.ROSTERVERSION + " TEXT," + Account.OPTIONS + " NUMBER, " + Account.AVATAR + " TEXT, " + Account.KEYS - + " TEXT)"); + + " TEXT, " + Account.HOSTNAME + " TEXT, " + Account.PORT + " NUMBER DEFAULT 5222)"); db.execSQL("create table " + Conversation.TABLENAME + " (" + Conversation.UUID + " TEXT PRIMARY KEY, " + Conversation.NAME + " TEXT, " + Conversation.CONTACT + " TEXT, " @@ -70,12 +157,19 @@ public class DatabaseBackend extends SQLiteOpenHelper { + Message.STATUS + " NUMBER," + Message.TYPE + " NUMBER, " + Message.RELATIVE_FILE_PATH + " TEXT, " + Message.SERVER_MSG_ID + " TEXT, " + + Message.FINGERPRINT + " TEXT, " + + Message.CARBON + " INTEGER, " + + Message.READ + " NUMBER DEFAULT 1, " + Message.REMOTE_MSG_ID + " TEXT, FOREIGN KEY(" + Message.CONVERSATION + ") REFERENCES " + Conversation.TABLENAME + "(" + Conversation.UUID + ") ON DELETE CASCADE);"); db.execSQL(CREATE_CONTATCS_STATEMENT); + db.execSQL(CREATE_SESSIONS_STATEMENT); + db.execSQL(CREATE_PREKEYS_STATEMENT); + db.execSQL(CREATE_SIGNED_PREKEYS_STATEMENT); + db.execSQL(CREATE_IDENTITIES_STATEMENT); } @Override @@ -110,12 +204,12 @@ public class DatabaseBackend extends SQLiteOpenHelper { db.execSQL("ALTER TABLE " + Conversation.TABLENAME + " ADD COLUMN " + Conversation.ATTRIBUTES + " TEXT"); } - if (oldVersion < 9 && newVersion >= 9) { - db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN " - + Contact.LAST_TIME + " NUMBER"); - db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN " - + Contact.LAST_PRESENCE + " TEXT"); - } + if (oldVersion < 9 && newVersion >= 9) { + db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN " + + Contact.LAST_TIME + " NUMBER"); + db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN " + + Contact.LAST_PRESENCE + " TEXT"); + } if (oldVersion < 10 && newVersion >= 10) { db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.RELATIVE_FILE_PATH + " TEXT"); @@ -123,23 +217,23 @@ public class DatabaseBackend extends SQLiteOpenHelper { if (oldVersion < 11 && newVersion >= 11) { db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN " + Contact.GROUPS + " TEXT"); - db.execSQL("delete from "+Contact.TABLENAME); - db.execSQL("update "+Account.TABLENAME+" set "+Account.ROSTERVERSION+" = NULL"); + db.execSQL("delete from " + Contact.TABLENAME); + db.execSQL("update " + Account.TABLENAME + " set " + Account.ROSTERVERSION + " = NULL"); } if (oldVersion < 12 && newVersion >= 12) { db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.SERVER_MSG_ID + " TEXT"); } if (oldVersion < 13 && newVersion >= 13) { - db.execSQL("delete from "+Contact.TABLENAME); - db.execSQL("update "+Account.TABLENAME+" set "+Account.ROSTERVERSION+" = NULL"); + db.execSQL("delete from " + Contact.TABLENAME); + db.execSQL("update " + Account.TABLENAME + " set " + Account.ROSTERVERSION + " = NULL"); } if (oldVersion < 14 && newVersion >= 14) { // migrate db to new, canonicalized JID domainpart representation // Conversation table Cursor cursor = db.rawQuery("select * from " + Conversation.TABLENAME, new String[0]); - while(cursor.moveToNext()) { + while (cursor.moveToNext()) { String newJid; try { newJid = Jid.fromString( @@ -147,8 +241,8 @@ public class DatabaseBackend extends SQLiteOpenHelper { ).toString(); } catch (InvalidJidException ignored) { Logging.e(Config.LOGTAG, "Failed to migrate Conversation CONTACTJID " - +cursor.getString(cursor.getColumnIndex(Conversation.CONTACTJID)) - +": " + ignored +". Skipping..."); + + cursor.getString(cursor.getColumnIndex(Conversation.CONTACTJID)) + + ": " + ignored + ". Skipping..."); continue; } @@ -157,14 +251,14 @@ public class DatabaseBackend extends SQLiteOpenHelper { cursor.getString(cursor.getColumnIndex(Conversation.UUID)), }; db.execSQL("update " + Conversation.TABLENAME - + " set " + Conversation.CONTACTJID + " = ? " + + " set " + Conversation.CONTACTJID + " = ? " + " where " + Conversation.UUID + " = ?", updateArgs); } cursor.close(); // Contact table cursor = db.rawQuery("select * from " + Contact.TABLENAME, new String[0]); - while(cursor.moveToNext()) { + while (cursor.moveToNext()) { String newJid; try { newJid = Jid.fromString( @@ -172,8 +266,8 @@ public class DatabaseBackend extends SQLiteOpenHelper { ).toString(); } catch (InvalidJidException ignored) { Logging.e(Config.LOGTAG, "Failed to migrate Contact JID " - +cursor.getString(cursor.getColumnIndex(Contact.JID)) - +": " + ignored +". Skipping..."); + + cursor.getString(cursor.getColumnIndex(Contact.JID)) + + ": " + ignored + ". Skipping..."); continue; } @@ -191,7 +285,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { // Account table cursor = db.rawQuery("select * from " + Account.TABLENAME, new String[0]); - while(cursor.moveToNext()) { + while (cursor.moveToNext()) { String newServer; try { newServer = Jid.fromParts( @@ -201,8 +295,8 @@ public class DatabaseBackend extends SQLiteOpenHelper { ).getDomainpart(); } catch (InvalidJidException ignored) { Logging.e(Config.LOGTAG, "Failed to migrate Account SERVER " - +cursor.getString(cursor.getColumnIndex(Account.SERVER)) - +": " + ignored +". Skipping..."); + + cursor.getString(cursor.getColumnIndex(Account.SERVER)) + + ": " + ignored + ". Skipping..."); continue; } @@ -216,6 +310,59 @@ public class DatabaseBackend extends SQLiteOpenHelper { } cursor.close(); } + if (oldVersion < 15 && newVersion >= 15) { + recreateAxolotlDb(db); + db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + + Message.FINGERPRINT + " TEXT"); + } + if (oldVersion < 16 && newVersion >= 16) { + db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + + Message.CARBON + " INTEGER"); + } + if (oldVersion < 19 && newVersion >= 19) { + db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.DISPLAY_NAME + " TEXT"); + } + if (oldVersion < 20 && newVersion >= 20) { + db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.HOSTNAME + " TEXT"); + db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.PORT + " NUMBER DEFAULT 5222"); + } + /* Any migrations that alter the Account table need to happen BEFORE this migration, as it + * depends on account de-serialization. + */ + if (oldVersion < 17 && newVersion >= 17) { + List<Account> accounts = getAccounts(db); + for (Account account : accounts) { + String ownDeviceIdString = account.getKey(SQLiteAxolotlStore.JSONKEY_REGISTRATION_ID); + if (ownDeviceIdString == null) { + continue; + } + int ownDeviceId = Integer.valueOf(ownDeviceIdString); + AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toString(), ownDeviceId); + deleteSession(db, account, ownAddress); + IdentityKeyPair identityKeyPair = loadOwnIdentityKeyPair(db, account); + if (identityKeyPair != null) { + setIdentityKeyTrust(db, account, identityKeyPair.getPublicKey().getFingerprint().replaceAll("\\s", ""), XmppAxolotlSession.Trust.TRUSTED); + } else { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not load own identity key pair"); + } + } + } + if (oldVersion < 18 && newVersion >= 18) { + db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.READ + " NUMBER DEFAULT 1"); + } + + if (oldVersion < 21 && newVersion >= 21) { + List<Account> accounts = getAccounts(db); + for (Account account : accounts) { + account.unsetPgpSignature(); + db.update(Account.TABLENAME, account.getContentValues(), Account.UUID + + "=?", new String[]{account.getUuid()}); + } + } + + if (oldVersion < 22 && newVersion >= 22) { + db.execSQL("ALTER TABLE " + SQLiteAxolotlStore.IDENTITIES_TABLENAME + " ADD COLUMN " + SQLiteAxolotlStore.CERTIFICATE); + } } public static synchronized DatabaseBackend getInstance(Context context) { @@ -240,26 +387,10 @@ public class DatabaseBackend extends SQLiteOpenHelper { db.insert(Account.TABLENAME, null, account.getContentValues()); } - public void createContact(Contact contact) { - SQLiteDatabase db = this.getWritableDatabase(); - db.insert(Contact.TABLENAME, null, contact.getContentValues()); - } - - public int getConversationCount() { - SQLiteDatabase db = this.getReadableDatabase(); - Cursor cursor = db.rawQuery("select count(uuid) as count from " - + Conversation.TABLENAME + " where " + Conversation.STATUS - + "=" + Conversation.STATUS_AVAILABLE, null); - cursor.moveToFirst(); - int count = cursor.getInt(0); - cursor.close(); - return count; - } - public CopyOnWriteArrayList<Conversation> getConversations(int status) { CopyOnWriteArrayList<Conversation> list = new CopyOnWriteArrayList<>(); SQLiteDatabase db = this.getReadableDatabase(); - String[] selectionArgs = { Integer.toString(status) }; + String[] selectionArgs = {Integer.toString(status)}; Cursor cursor = db.rawQuery("select * from " + Conversation.TABLENAME + " where " + Conversation.STATUS + " = ? order by " + Conversation.CREATED + " desc", selectionArgs); @@ -275,20 +406,20 @@ public class DatabaseBackend extends SQLiteOpenHelper { } public ArrayList<Message> getMessages(Conversation conversation, int limit, - long timestamp) { + long timestamp) { ArrayList<Message> list = new ArrayList<>(); SQLiteDatabase db = this.getReadableDatabase(); Cursor cursor; if (timestamp == -1) { - String[] selectionArgs = { conversation.getUuid() }; + String[] selectionArgs = {conversation.getUuid()}; cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION + "=?", selectionArgs, null, null, Message.TIME_SENT + " DESC", String.valueOf(limit)); } else { - String[] selectionArgs = { conversation.getUuid(), - Long.toString(timestamp) }; + String[] selectionArgs = {conversation.getUuid(), + Long.toString(timestamp)}; cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION - + "=? and " + Message.TIME_SENT + "<?", selectionArgs, + + "=? and " + Message.TIME_SENT + "<?", selectionArgs, null, null, Message.TIME_SENT + " DESC", String.valueOf(limit)); } @@ -304,15 +435,52 @@ public class DatabaseBackend extends SQLiteOpenHelper { return list; } + public Iterable<Message> getMessagesIterable(final Conversation conversation) { + return new Iterable<Message>() { + @Override + public Iterator<Message> iterator() { + class MessageIterator implements Iterator<Message> { + SQLiteDatabase db = getReadableDatabase(); + String[] selectionArgs = {conversation.getUuid()}; + Cursor cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION + + "=?", selectionArgs, null, null, Message.TIME_SENT + + " ASC", null); + + public MessageIterator() { + cursor.moveToFirst(); + } + + @Override + public boolean hasNext() { + return !cursor.isAfterLast(); + } + + @Override + public Message next() { + Message message = Message.fromCursor(cursor); + cursor.moveToNext(); + return message; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + } + return new MessageIterator(); + } + }; + } + public Conversation findConversation(final Account account, final Jid contactJid) { SQLiteDatabase db = this.getReadableDatabase(); - String[] selectionArgs = { account.getUuid(), + String[] selectionArgs = {account.getUuid(), contactJid.toBareJid().toString() + "/%", contactJid.toBareJid().toString() - }; + }; Cursor cursor = db.query(Conversation.TABLENAME, null, Conversation.ACCOUNT + "=? AND (" + Conversation.CONTACTJID - + " like ? OR "+Conversation.CONTACTJID+"=?)", selectionArgs, null, null, null); + + " like ? OR " + Conversation.CONTACTJID + "=?)", selectionArgs, null, null, null); if (cursor.getCount() == 0) return null; cursor.moveToFirst(); @@ -323,14 +491,18 @@ public class DatabaseBackend extends SQLiteOpenHelper { public void updateConversation(final Conversation conversation) { final SQLiteDatabase db = this.getWritableDatabase(); - final String[] args = { conversation.getUuid() }; + final String[] args = {conversation.getUuid()}; db.update(Conversation.TABLENAME, conversation.getContentValues(), Conversation.UUID + "=?", args); } public List<Account> getAccounts() { - List<Account> list = new ArrayList<>(); SQLiteDatabase db = this.getReadableDatabase(); + return getAccounts(db); + } + + private List<Account> getAccounts(SQLiteDatabase db) { + List<Account> list = new ArrayList<>(); Cursor cursor = db.query(Account.TABLENAME, null, null, null, null, null, null); while (cursor.moveToNext()) { @@ -342,14 +514,14 @@ public class DatabaseBackend extends SQLiteOpenHelper { public void updateAccount(Account account) { SQLiteDatabase db = this.getWritableDatabase(); - String[] args = { account.getUuid() }; + String[] args = {account.getUuid()}; db.update(Account.TABLENAME, account.getContentValues(), Account.UUID + "=?", args); } public void deleteAccount(Account account) { SQLiteDatabase db = this.getWritableDatabase(); - String[] args = { account.getUuid() }; + String[] args = {account.getUuid()}; db.delete(Account.TABLENAME, Account.UUID + "=?", args); } @@ -378,7 +550,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { public void updateMessage(Message message) { SQLiteDatabase db = this.getWritableDatabase(); - String[] args = { message.getUuid() }; + String[] args = {message.getUuid()}; db.update(Message.TABLENAME, message.getContentValues(), Message.UUID + "=?", args); } @@ -386,7 +558,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { public void readRoster(Roster roster) { SQLiteDatabase db = this.getReadableDatabase(); Cursor cursor; - String args[] = { roster.getAccount().getUuid() }; + String args[] = {roster.getAccount().getUuid()}; cursor = db.query(Contact.TABLENAME, null, Contact.ACCOUNT + "=?", args, null, null, null); while (cursor.moveToNext()) { roster.initContact(Contact.fromCursor(cursor)); @@ -397,89 +569,517 @@ public class DatabaseBackend extends SQLiteOpenHelper { public void writeRoster(final Roster roster) { final Account account = roster.getAccount(); final SQLiteDatabase db = this.getWritableDatabase(); + db.beginTransaction(); for (Contact contact : roster.getContacts()) { if (contact.getOption(Contact.Options.IN_ROSTER)) { db.insert(Contact.TABLENAME, null, contact.getContentValues()); } else { String where = Contact.ACCOUNT + "=? AND " + Contact.JID + "=?"; - String[] whereArgs = { account.getUuid(), contact.getJid().toString() }; + String[] whereArgs = {account.getUuid(), contact.getJid().toString()}; db.delete(Contact.TABLENAME, where, whereArgs); } } + db.setTransactionSuccessful(); + db.endTransaction(); account.setRosterVersion(roster.getVersion()); updateAccount(account); } - public void deleteMessage(Message message) { - SQLiteDatabase db = this.getWritableDatabase(); - String[] args = { message.getUuid() }; - db.delete(Message.TABLENAME, Message.UUID + "=?", args); - } - public void deleteMessagesInConversation(Conversation conversation) { SQLiteDatabase db = this.getWritableDatabase(); - String[] args = { conversation.getUuid() }; + String[] args = {conversation.getUuid()}; db.delete(Message.TABLENAME, Message.CONVERSATION + "=?", args); } - public Conversation findConversationByUuid(String conversationUuid) { - SQLiteDatabase db = this.getReadableDatabase(); - String[] selectionArgs = { conversationUuid }; - Cursor cursor = db.query(Conversation.TABLENAME, null, - Conversation.UUID + "=?", selectionArgs, null, null, null); - if (cursor.getCount() == 0) { + public Pair<Long, String> getLastMessageReceived(Account account) { + try { + SQLiteDatabase db = this.getReadableDatabase(); + String sql = "select messages.timeSent,messages.serverMsgId from accounts join conversations on accounts.uuid=conversations.accountUuid join messages on conversations.uuid=messages.conversationUuid where accounts.uuid=? and (messages.status=0 or messages.carbon=1 or messages.serverMsgId not null) order by messages.timesent desc limit 1"; + String[] args = {account.getUuid()}; + Cursor cursor = db.rawQuery(sql, args); + if (cursor.getCount() == 0) { + return null; + } else { + cursor.moveToFirst(); + return new Pair<>(cursor.getLong(0), cursor.getString(1)); + } + } catch (Exception e) { return null; } - cursor.moveToFirst(); - Conversation conversation = Conversation.fromCursor(cursor); + } + + private Cursor getCursorForSession(Account account, AxolotlAddress contact) { + final SQLiteDatabase db = this.getReadableDatabase(); + String[] columns = null; + String[] selectionArgs = {account.getUuid(), + contact.getName(), + Integer.toString(contact.getDeviceId())}; + Cursor cursor = db.query(SQLiteAxolotlStore.SESSION_TABLENAME, + columns, + SQLiteAxolotlStore.ACCOUNT + " = ? AND " + + SQLiteAxolotlStore.NAME + " = ? AND " + + SQLiteAxolotlStore.DEVICE_ID + " = ? ", + selectionArgs, + null, null, null); + + return cursor; + } + + public SessionRecord loadSession(Account account, AxolotlAddress contact) { + SessionRecord session = null; + Cursor cursor = getCursorForSession(account, contact); + if (cursor.getCount() != 0) { + cursor.moveToFirst(); + try { + session = new SessionRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT)); + } catch (IOException e) { + cursor.close(); + throw new AssertionError(e); + } + } cursor.close(); - return conversation; + return session; + } + + public List<Integer> getSubDeviceSessions(Account account, AxolotlAddress contact) { + final SQLiteDatabase db = this.getReadableDatabase(); + return getSubDeviceSessions(db, account, contact); + } + + private List<Integer> getSubDeviceSessions(SQLiteDatabase db, Account account, AxolotlAddress contact) { + List<Integer> devices = new ArrayList<>(); + String[] columns = {SQLiteAxolotlStore.DEVICE_ID}; + String[] selectionArgs = {account.getUuid(), + contact.getName()}; + Cursor cursor = db.query(SQLiteAxolotlStore.SESSION_TABLENAME, + columns, + SQLiteAxolotlStore.ACCOUNT + " = ? AND " + + SQLiteAxolotlStore.NAME + " = ?", + selectionArgs, + null, null, null); + + while (cursor.moveToNext()) { + devices.add(cursor.getInt( + cursor.getColumnIndex(SQLiteAxolotlStore.DEVICE_ID))); + } + + cursor.close(); + return devices; + } + + public boolean containsSession(Account account, AxolotlAddress contact) { + Cursor cursor = getCursorForSession(account, contact); + int count = cursor.getCount(); + cursor.close(); + return count != 0; + } + + public void storeSession(Account account, AxolotlAddress contact, SessionRecord session) { + SQLiteDatabase db = this.getWritableDatabase(); + ContentValues values = new ContentValues(); + values.put(SQLiteAxolotlStore.NAME, contact.getName()); + values.put(SQLiteAxolotlStore.DEVICE_ID, contact.getDeviceId()); + values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(session.serialize(), Base64.DEFAULT)); + values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid()); + db.insert(SQLiteAxolotlStore.SESSION_TABLENAME, null, values); + } + + public void deleteSession(Account account, AxolotlAddress contact) { + SQLiteDatabase db = this.getWritableDatabase(); + deleteSession(db, account, contact); + } + + private void deleteSession(SQLiteDatabase db, Account account, AxolotlAddress contact) { + String[] args = {account.getUuid(), + contact.getName(), + Integer.toString(contact.getDeviceId())}; + db.delete(SQLiteAxolotlStore.SESSION_TABLENAME, + SQLiteAxolotlStore.ACCOUNT + " = ? AND " + + SQLiteAxolotlStore.NAME + " = ? AND " + + SQLiteAxolotlStore.DEVICE_ID + " = ? ", + args); } - public Message findMessageByUuid(String messageUuid) { + public void deleteAllSessions(Account account, AxolotlAddress contact) { + SQLiteDatabase db = this.getWritableDatabase(); + String[] args = {account.getUuid(), contact.getName()}; + db.delete(SQLiteAxolotlStore.SESSION_TABLENAME, + SQLiteAxolotlStore.ACCOUNT + "=? AND " + + SQLiteAxolotlStore.NAME + " = ?", + args); + } + + private Cursor getCursorForPreKey(Account account, int preKeyId) { SQLiteDatabase db = this.getReadableDatabase(); - String[] selectionArgs = { messageUuid }; - Cursor cursor = db.query(Message.TABLENAME, null, Message.UUID + "=?", - selectionArgs, null, null, null); - if (cursor.getCount() == 0) { - return null; + String[] columns = {SQLiteAxolotlStore.KEY}; + String[] selectionArgs = {account.getUuid(), Integer.toString(preKeyId)}; + Cursor cursor = db.query(SQLiteAxolotlStore.PREKEY_TABLENAME, + columns, + SQLiteAxolotlStore.ACCOUNT + "=? AND " + + SQLiteAxolotlStore.ID + "=?", + selectionArgs, + null, null, null); + + return cursor; + } + + public PreKeyRecord loadPreKey(Account account, int preKeyId) { + PreKeyRecord record = null; + Cursor cursor = getCursorForPreKey(account, preKeyId); + if (cursor.getCount() != 0) { + cursor.moveToFirst(); + try { + record = new PreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT)); + } catch (IOException e) { + throw new AssertionError(e); + } } - cursor.moveToFirst(); - Message message = Message.fromCursor(cursor); cursor.close(); - return message; + return record; } - public Account findAccountByUuid(String accountUuid) { + public boolean containsPreKey(Account account, int preKeyId) { + Cursor cursor = getCursorForPreKey(account, preKeyId); + int count = cursor.getCount(); + cursor.close(); + return count != 0; + } + + public void storePreKey(Account account, PreKeyRecord record) { + SQLiteDatabase db = this.getWritableDatabase(); + ContentValues values = new ContentValues(); + values.put(SQLiteAxolotlStore.ID, record.getId()); + values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(record.serialize(), Base64.DEFAULT)); + values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid()); + db.insert(SQLiteAxolotlStore.PREKEY_TABLENAME, null, values); + } + + public void deletePreKey(Account account, int preKeyId) { + SQLiteDatabase db = this.getWritableDatabase(); + String[] args = {account.getUuid(), Integer.toString(preKeyId)}; + db.delete(SQLiteAxolotlStore.PREKEY_TABLENAME, + SQLiteAxolotlStore.ACCOUNT + "=? AND " + + SQLiteAxolotlStore.ID + "=?", + args); + } + + private Cursor getCursorForSignedPreKey(Account account, int signedPreKeyId) { SQLiteDatabase db = this.getReadableDatabase(); - String[] selectionArgs = { accountUuid }; - Cursor cursor = db.query(Account.TABLENAME, null, Account.UUID + "=?", - selectionArgs, null, null, null); - if (cursor.getCount() == 0) { - return null; + String[] columns = {SQLiteAxolotlStore.KEY}; + String[] selectionArgs = {account.getUuid(), Integer.toString(signedPreKeyId)}; + Cursor cursor = db.query(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, + columns, + SQLiteAxolotlStore.ACCOUNT + "=? AND " + SQLiteAxolotlStore.ID + "=?", + selectionArgs, + null, null, null); + + return cursor; + } + + public SignedPreKeyRecord loadSignedPreKey(Account account, int signedPreKeyId) { + SignedPreKeyRecord record = null; + Cursor cursor = getCursorForSignedPreKey(account, signedPreKeyId); + if (cursor.getCount() != 0) { + cursor.moveToFirst(); + try { + record = new SignedPreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT)); + } catch (IOException e) { + throw new AssertionError(e); + } } - cursor.moveToFirst(); - Account account = Account.fromCursor(cursor); cursor.close(); - return account; + return record; } - public List<Message> getImageMessages(Conversation conversation) { - ArrayList<Message> list = new ArrayList<>(); + public List<SignedPreKeyRecord> loadSignedPreKeys(Account account) { + List<SignedPreKeyRecord> prekeys = new ArrayList<>(); SQLiteDatabase db = this.getReadableDatabase(); - Cursor cursor; - String[] selectionArgs = { conversation.getUuid(), String.valueOf(Message.TYPE_IMAGE) }; - cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION - + "=? AND "+Message.TYPE+"=?", selectionArgs, null, null,null); + String[] columns = {SQLiteAxolotlStore.KEY}; + String[] selectionArgs = {account.getUuid()}; + Cursor cursor = db.query(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, + columns, + SQLiteAxolotlStore.ACCOUNT + "=?", + selectionArgs, + null, null, null); + + while (cursor.moveToNext()) { + try { + prekeys.add(new SignedPreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT))); + } catch (IOException ignored) { + } + } + cursor.close(); + return prekeys; + } + + public boolean containsSignedPreKey(Account account, int signedPreKeyId) { + Cursor cursor = getCursorForPreKey(account, signedPreKeyId); + int count = cursor.getCount(); + cursor.close(); + return count != 0; + } + + public void storeSignedPreKey(Account account, SignedPreKeyRecord record) { + SQLiteDatabase db = this.getWritableDatabase(); + ContentValues values = new ContentValues(); + values.put(SQLiteAxolotlStore.ID, record.getId()); + values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(record.serialize(), Base64.DEFAULT)); + values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid()); + db.insert(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, null, values); + } + + public void deleteSignedPreKey(Account account, int signedPreKeyId) { + SQLiteDatabase db = this.getWritableDatabase(); + String[] args = {account.getUuid(), Integer.toString(signedPreKeyId)}; + db.delete(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, + SQLiteAxolotlStore.ACCOUNT + "=? AND " + + SQLiteAxolotlStore.ID + "=?", + args); + } + + private Cursor getIdentityKeyCursor(Account account, String name, boolean own) { + final SQLiteDatabase db = this.getReadableDatabase(); + return getIdentityKeyCursor(db, account, name, own); + } + + private Cursor getIdentityKeyCursor(SQLiteDatabase db, Account account, String name, boolean own) { + return getIdentityKeyCursor(db, account, name, own, null); + } + + private Cursor getIdentityKeyCursor(Account account, String fingerprint) { + final SQLiteDatabase db = this.getReadableDatabase(); + return getIdentityKeyCursor(db, account, fingerprint); + } + + private Cursor getIdentityKeyCursor(SQLiteDatabase db, Account account, String fingerprint) { + return getIdentityKeyCursor(db, account, null, null, fingerprint); + } + + private Cursor getIdentityKeyCursor(SQLiteDatabase db, Account account, String name, Boolean own, String fingerprint) { + String[] columns = {SQLiteAxolotlStore.TRUSTED, + SQLiteAxolotlStore.KEY}; + ArrayList<String> selectionArgs = new ArrayList<>(4); + selectionArgs.add(account.getUuid()); + String selectionString = SQLiteAxolotlStore.ACCOUNT + " = ?"; + if (name != null) { + selectionArgs.add(name); + selectionString += " AND " + SQLiteAxolotlStore.NAME + " = ?"; + } + if (fingerprint != null) { + selectionArgs.add(fingerprint); + selectionString += " AND " + SQLiteAxolotlStore.FINGERPRINT + " = ?"; + } + if (own != null) { + selectionArgs.add(own ? "1" : "0"); + selectionString += " AND " + SQLiteAxolotlStore.OWN + " = ?"; + } + Cursor cursor = db.query(SQLiteAxolotlStore.IDENTITIES_TABLENAME, + columns, + selectionString, + selectionArgs.toArray(new String[selectionArgs.size()]), + null, null, null); + + return cursor; + } + + public IdentityKeyPair loadOwnIdentityKeyPair(Account account) { + SQLiteDatabase db = getReadableDatabase(); + return loadOwnIdentityKeyPair(db, account); + } + + private IdentityKeyPair loadOwnIdentityKeyPair(SQLiteDatabase db, Account account) { + String name = account.getJid().toBareJid().toString(); + IdentityKeyPair identityKeyPair = null; + Cursor cursor = getIdentityKeyCursor(db, account, name, true); + if (cursor.getCount() != 0) { + cursor.moveToFirst(); + try { + identityKeyPair = new IdentityKeyPair(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT)); + } catch (InvalidKeyException e) { + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Encountered invalid IdentityKey in database for account" + account.getJid().toBareJid() + ", address: " + name); + } + } + cursor.close(); + + return identityKeyPair; + } + + public Set<IdentityKey> loadIdentityKeys(Account account, String name) { + return loadIdentityKeys(account, name, null); + } + + public Set<IdentityKey> loadIdentityKeys(Account account, String name, XmppAxolotlSession.Trust trust) { + Set<IdentityKey> identityKeys = new HashSet<>(); + Cursor cursor = getIdentityKeyCursor(account, name, false); + + while (cursor.moveToNext()) { + if (trust != null && + cursor.getInt(cursor.getColumnIndex(SQLiteAxolotlStore.TRUSTED)) + != trust.getCode()) { + continue; + } + try { + identityKeys.add(new IdentityKey(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT), 0)); + } catch (InvalidKeyException e) { + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Encountered invalid IdentityKey in database for account" + account.getJid().toBareJid() + ", address: " + name); + } + } + cursor.close(); + + return identityKeys; + } + + public long numTrustedKeys(Account account, String name) { + SQLiteDatabase db = getReadableDatabase(); + String[] args = { + account.getUuid(), + name, + String.valueOf(XmppAxolotlSession.Trust.TRUSTED.getCode()), + String.valueOf(XmppAxolotlSession.Trust.TRUSTED_X509.getCode()) + }; + return DatabaseUtils.queryNumEntries(db, SQLiteAxolotlStore.IDENTITIES_TABLENAME, + SQLiteAxolotlStore.ACCOUNT + " = ?" + + " AND " + SQLiteAxolotlStore.NAME + " = ?" + + " AND (" + SQLiteAxolotlStore.TRUSTED + " = ? OR " + SQLiteAxolotlStore.TRUSTED + " = ?)", + args + ); + } + + private void storeIdentityKey(Account account, String name, boolean own, String fingerprint, String base64Serialized) { + storeIdentityKey(account, name, own, fingerprint, base64Serialized, XmppAxolotlSession.Trust.UNDECIDED); + } + + private void storeIdentityKey(Account account, String name, boolean own, String fingerprint, String base64Serialized, XmppAxolotlSession.Trust trusted) { + SQLiteDatabase db = this.getWritableDatabase(); + ContentValues values = new ContentValues(); + values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid()); + values.put(SQLiteAxolotlStore.NAME, name); + values.put(SQLiteAxolotlStore.OWN, own ? 1 : 0); + values.put(SQLiteAxolotlStore.FINGERPRINT, fingerprint); + values.put(SQLiteAxolotlStore.KEY, base64Serialized); + values.put(SQLiteAxolotlStore.TRUSTED, trusted.getCode()); + db.insert(SQLiteAxolotlStore.IDENTITIES_TABLENAME, null, values); + } + + public XmppAxolotlSession.Trust isIdentityKeyTrusted(Account account, String fingerprint) { + Cursor cursor = getIdentityKeyCursor(account, fingerprint); + XmppAxolotlSession.Trust trust = null; if (cursor.getCount() > 0) { - cursor.moveToLast(); - do { - Message message = Message.fromCursor(cursor); - message.setConversation(conversation); - list.add(message); - } while (cursor.moveToPrevious()); + cursor.moveToFirst(); + int trustValue = cursor.getInt(cursor.getColumnIndex(SQLiteAxolotlStore.TRUSTED)); + trust = XmppAxolotlSession.Trust.fromCode(trustValue); } cursor.close(); - return list; + return trust; + } + + public boolean setIdentityKeyTrust(Account account, String fingerprint, XmppAxolotlSession.Trust trust) { + SQLiteDatabase db = this.getWritableDatabase(); + return setIdentityKeyTrust(db, account, fingerprint, trust); + } + + private boolean setIdentityKeyTrust(SQLiteDatabase db, Account account, String fingerprint, XmppAxolotlSession.Trust trust) { + String[] selectionArgs = { + account.getUuid(), + fingerprint + }; + ContentValues values = new ContentValues(); + values.put(SQLiteAxolotlStore.TRUSTED, trust.getCode()); + int rows = db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME, values, + SQLiteAxolotlStore.ACCOUNT + " = ? AND " + + SQLiteAxolotlStore.FINGERPRINT + " = ? ", + selectionArgs); + return rows == 1; + } + + public boolean setIdentityKeyCertificate(Account account, String fingerprint, X509Certificate x509Certificate) { + SQLiteDatabase db = this.getWritableDatabase(); + String[] selectionArgs = { + account.getUuid(), + fingerprint + }; + try { + ContentValues values = new ContentValues(); + values.put(SQLiteAxolotlStore.CERTIFICATE, x509Certificate.getEncoded()); + return db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME, values, + SQLiteAxolotlStore.ACCOUNT + " = ? AND " + + SQLiteAxolotlStore.FINGERPRINT + " = ? ", + selectionArgs) == 1; + } catch (CertificateEncodingException e) { + Log.d(Config.LOGTAG, "could not encode certificate"); + return false; + } + } + + public X509Certificate getIdentityKeyCertifcate(Account account, String fingerprint) { + SQLiteDatabase db = this.getReadableDatabase(); + String[] selectionArgs = { + account.getUuid(), + fingerprint + }; + String[] colums = {SQLiteAxolotlStore.CERTIFICATE}; + String selection = SQLiteAxolotlStore.ACCOUNT + " = ? AND " + SQLiteAxolotlStore.FINGERPRINT + " = ? "; + Cursor cursor = db.query(SQLiteAxolotlStore.IDENTITIES_TABLENAME, colums, selection, selectionArgs, null, null, null); + if (cursor.getCount() < 1) { + return null; + } else { + cursor.moveToFirst(); + byte[] certificate = cursor.getBlob(cursor.getColumnIndex(SQLiteAxolotlStore.CERTIFICATE)); + if (certificate == null || certificate.length == 0) { + return null; + } + try { + CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); + return (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(certificate)); + } catch (CertificateException e) { + Log.d(Config.LOGTAG,"certificate exception "+e.getMessage()); + return null; + } + } + } + + public void storeIdentityKey(Account account, String name, IdentityKey identityKey) { + storeIdentityKey(account, name, false, identityKey.getFingerprint().replaceAll("\\s", ""), Base64.encodeToString(identityKey.serialize(), Base64.DEFAULT)); + } + + public void storeOwnIdentityKeyPair(Account account, IdentityKeyPair identityKeyPair) { + storeIdentityKey(account, account.getJid().toBareJid().toString(), true, identityKeyPair.getPublicKey().getFingerprint().replaceAll("\\s", ""), Base64.encodeToString(identityKeyPair.serialize(), Base64.DEFAULT), XmppAxolotlSession.Trust.TRUSTED); + } + + public void recreateAxolotlDb() { + recreateAxolotlDb(getWritableDatabase()); + } + + public void recreateAxolotlDb(SQLiteDatabase db) { + Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + ">>> (RE)CREATING AXOLOTL DATABASE <<<"); + db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.SESSION_TABLENAME); + db.execSQL(CREATE_SESSIONS_STATEMENT); + db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.PREKEY_TABLENAME); + db.execSQL(CREATE_PREKEYS_STATEMENT); + db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME); + db.execSQL(CREATE_SIGNED_PREKEYS_STATEMENT); + db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.IDENTITIES_TABLENAME); + db.execSQL(CREATE_IDENTITIES_STATEMENT); + } + + public void wipeAxolotlDb(Account account) { + String accountName = account.getUuid(); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ">>> WIPING AXOLOTL DATABASE FOR ACCOUNT " + accountName + " <<<"); + SQLiteDatabase db = this.getWritableDatabase(); + String[] deleteArgs = { + accountName + }; + db.delete(SQLiteAxolotlStore.SESSION_TABLENAME, + SQLiteAxolotlStore.ACCOUNT + " = ?", + deleteArgs); + db.delete(SQLiteAxolotlStore.PREKEY_TABLENAME, + SQLiteAxolotlStore.ACCOUNT + " = ?", + deleteArgs); + db.delete(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, + SQLiteAxolotlStore.ACCOUNT + " = ?", + deleteArgs); + db.delete(SQLiteAxolotlStore.IDENTITIES_TABLENAME, + SQLiteAxolotlStore.ACCOUNT + " = ?", + deleteArgs); } } diff --git a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java index aba4c090..f242b928 100644 --- a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java @@ -1,12 +1,30 @@ package eu.siacs.conversations.persistance; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.RectF; +import android.net.Uri; +import android.os.Environment; +import android.util.Base64; +import android.util.Base64OutputStream; +import android.util.Log; +import android.webkit.MimeTypeMap; + +import java.io.ByteArrayOutputStream; +import java.io.Closeable; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.net.Socket; import java.net.URL; +import java.security.DigestOutputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Date; @@ -29,44 +47,49 @@ import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.entities.DownloadableFile; import eu.siacs.conversations.entities.Message; +import eu.siacs.conversations.entities.Transferable; +import eu.siacs.conversations.services.XmppConnectionService; +import eu.siacs.conversations.utils.CryptoHelper; +import eu.siacs.conversations.utils.ExifHelper; +import eu.siacs.conversations.utils.FileUtils; +import eu.siacs.conversations.xmpp.pep.Avatar; -public final class FileBackend { +public class FileBackend { + private final SimpleDateFormat imageDateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US); - private static final SimpleDateFormat imageDateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US); + private XmppConnectionService mXmppConnectionService; - public static DownloadableFile getFile(Message message) { + public FileBackend(XmppConnectionService service) { + this.mXmppConnectionService = service; + } + + public DownloadableFile getFile(Message message) { return getFile(message, true); } - public static DownloadableFile getFile(Message message, boolean decrypted) { + public DownloadableFile getFile(Message message, boolean decrypted) { + final boolean encrypted = !decrypted + && (message.getEncryption() == Message.ENCRYPTION_PGP + || message.getEncryption() == Message.ENCRYPTION_DECRYPTED); + final DownloadableFile file; String path = message.getRelativeFilePath(); - String extension; - if (path != null && !path.isEmpty()) { - String[] parts = path.split("\\."); - extension = "."+parts[parts.length - 1]; + if (path == null) { + path = message.getUuid(); + } + if (path.startsWith("/")) { + file = new DownloadableFile(path); } else { - if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_TEXT) { - extension = ".png"; + String mime = message.getMimeType(); + if (mime != null && mime.startsWith("image")) { + file = new DownloadableFile(getConversationsImageDirectory() + path); } else { - extension = ""; + file = new DownloadableFile(getConversationsFileDirectory() + path); } - path = message.getUuid()+extension; } - final boolean encrypted = !decrypted - && (message.getEncryption() == Message.ENCRYPTION_PGP - || message.getEncryption() == Message.ENCRYPTION_DECRYPTED); if (encrypted) { - return new DownloadableFile(getConversationsFileDirectory()+message.getUuid()+extension+".pgp"); + return new DownloadableFile(getConversationsFileDirectory() + file.getName() + ".pgp"); } else { - if (path.startsWith("/")) { - return new DownloadableFile(path); - } else { - if (Arrays.asList(Transferable.VALID_IMAGE_EXTENSIONS).contains(extension)) { - return new DownloadableFile(getConversationsImageDirectory() + path); - } else { - return new DownloadableFile(getConversationsFileDirectory() + path); - } - } + return file; } } @@ -86,12 +109,34 @@ public final class FileBackend { return FileBackend.getPrivateFileDirectoryPath() + File.separator + "Images" + File.separator; } - public static DownloadableFile copyFileToPrivateStorage(Message message, Uri uri) throws FileCopyException { - Logging.d(Config.LOGTAG, "copy " + uri.toString() + " to private storage"); - String mime = ConversationsPlusApplication.getInstance().getContentResolver().getType(uri); - String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mime); - message.setRelativeFilePath(FileBackend.getPrivateFileDirectoryPath() + message.getUuid() + "." + extension); - DownloadableFile file = getFile(message); + public boolean useImageAsIs(Uri uri) { + String path = getOriginalPath(uri); + if (path == null) { + return false; + } + File file = new File(path); + long size = file.length(); + if (size == 0 || size >= Config.IMAGE_MAX_SIZE ) { + return false; + } + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + try { + BitmapFactory.decodeStream(mXmppConnectionService.getContentResolver().openInputStream(uri), null, options); + if (options == null || options.outMimeType == null || options.outHeight <= 0 || options.outWidth <= 0) { + return false; + } + return (options.outWidth <= Config.IMAGE_SIZE && options.outHeight <= Config.IMAGE_SIZE && options.outMimeType.contains(Config.IMAGE_FORMAT.name().toLowerCase())); + } catch (FileNotFoundException e) { + return false; + } + } + + public String getOriginalPath(Uri uri) { + return FileUtils.getPath(mXmppConnectionService,uri); + } + + public void copyFileToPrivateStorage(File file, Uri uri) throws FileCopyException { file.getParentFile().mkdirs(); OutputStream os = null; InputStream is = null; @@ -118,39 +163,97 @@ public final class FileBackend { return file; } - public static DownloadableFile compressImageAndCopyToPrivateStorage(Message message, Bitmap scaledBitmap) throws FileCopyException { - message.setRelativeFilePath(FileBackend.getPrivateImageDirectoryPath() + message.getUuid() + ".png"); - DownloadableFile file = getFile(message); + public void copyFileToPrivateStorage(Message message, Uri uri) throws FileCopyException { + Log.d(Config.LOGTAG, "copy " + uri.toString() + " to private storage"); + String mime = mXmppConnectionService.getContentResolver().getType(uri); + String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mime); + message.setRelativeFilePath(message.getUuid() + "." + extension); + copyFileToPrivateStorage(mXmppConnectionService.getFileBackend().getFile(message), uri); + } + + private void copyImageToPrivateStorage(File file, Uri image, int sampleSize) throws FileCopyException { file.getParentFile().mkdirs(); + InputStream is = null; OutputStream os = null; try { file.createNewFile(); - os = new FileOutputStream(file); - - boolean success = scaledBitmap.compress(Bitmap.CompressFormat.PNG, 75, os); - if (!success) { - throw new FileCopyException(R.string.error_compressing_image); + is = mXmppConnectionService.getContentResolver().openInputStream(image); + Bitmap originalBitmap; + BitmapFactory.Options options = new BitmapFactory.Options(); + int inSampleSize = (int) Math.pow(2, sampleSize); + Log.d(Config.LOGTAG, "reading bitmap with sample size " + inSampleSize); + options.inSampleSize = inSampleSize; + originalBitmap = BitmapFactory.decodeStream(is, null, options); + is.close(); + if (originalBitmap == null) { + throw new FileCopyException(R.string.error_not_an_image_file); } - os.flush(); - } catch (IOException e) { - throw new FileCopyException(R.string.error_io_exception, e); + Bitmap scaledBitmap = resize(originalBitmap, Config.IMAGE_SIZE); + int rotation = getRotation(image); + scaledBitmap = rotate(scaledBitmap, rotation); + boolean targetSizeReached = false; + int quality = Config.IMAGE_QUALITY; + while(!targetSizeReached) { + os = new FileOutputStream(file); + boolean success = scaledBitmap.compress(Config.IMAGE_FORMAT, quality, os); + if (!success) { + throw new FileCopyException(R.string.error_compressing_image); + } + os.flush(); + targetSizeReached = file.length() <= Config.IMAGE_MAX_SIZE || quality <= 50; + quality -= 5; + } + scaledBitmap.recycle(); + return; + } catch (FileNotFoundException e) { + throw new FileCopyException(R.string.error_file_not_found); + } catch (IOException e) { + e.printStackTrace(); + throw new FileCopyException(R.string.error_io_exception); } catch (SecurityException e) { - throw new FileCopyException(R.string.error_security_exception_during_image_copy); - } catch (NullPointerException e) { + throw new FileCopyException(R.string.error_security_exception_during_image_copy); + } catch (OutOfMemoryError e) { + ++sampleSize; + if (sampleSize <= 3) { + copyImageToPrivateStorage(file, image, sampleSize); + } else { + throw new FileCopyException(R.string.error_out_of_memory); + } + } catch (NullPointerException e) { throw new FileCopyException(R.string.error_io_exception); } finally { StreamUtil.close(os); + StreamUtil.close(is); + } + } + + public void copyImageToPrivateStorage(File file, Uri image) throws FileCopyException { + copyImageToPrivateStorage(file, image, 0); + } + + public void copyImageToPrivateStorage(Message message, Uri image) throws FileCopyException { + switch(Config.IMAGE_FORMAT) { + case JPEG: + message.setRelativeFilePath(message.getUuid()+".jpg"); + break; + case PNG: + message.setRelativeFilePath(message.getUuid()+".png"); + break; + case WEBP: + message.setRelativeFilePath(message.getUuid()+".webp"); + break; } - return file; + copyImageToPrivateStorage(getFile(message), image); + updateFileParams(message); } - public static Uri getTakePhotoUri() { + public static Uri getTakePhotoUri() { StringBuilder pathBuilder = new StringBuilder(); pathBuilder.append(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)); pathBuilder.append('/'); pathBuilder.append("Camera"); pathBuilder.append('/'); - pathBuilder.append("IMG_" + imageDateFormat.format(new Date()) + ".jpg"); + pathBuilder.append("IMG_" + this.imageDateFormat.format(new Date()) + ".jpg"); Uri uri = Uri.parse("file://" + pathBuilder.toString()); File file = new File(uri.toString()); file.getParentFile().mkdirs(); diff --git a/src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java b/src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java index 2dcd858f..d4626fc9 100644 --- a/src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java +++ b/src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java @@ -1,5 +1,37 @@ package eu.siacs.conversations.services; +import android.Manifest; +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.PowerManager; +import android.util.Log; +import android.util.Pair; + +import org.bouncycastle.crypto.engines.AESEngine; +import org.bouncycastle.crypto.modes.AEADBlockCipher; +import org.bouncycastle.crypto.modes.GCMBlockCipher; +import org.bouncycastle.crypto.params.AEADParameters; +import org.bouncycastle.crypto.params.KeyParameter; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; + +import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; +import javax.crypto.CipherOutputStream; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +import eu.siacs.conversations.Config; +import eu.siacs.conversations.entities.DownloadableFile; import de.thedevstack.conversationsplus.ConversationsPlusPreferences; public class AbstractConnectionManager { @@ -12,4 +44,101 @@ public class AbstractConnectionManager { public XmppConnectionService getXmppConnectionService() { return this.mXmppConnectionService; } + + public long getAutoAcceptFileSize() { + String config = this.mXmppConnectionService.getPreferences().getString( + "auto_accept_file_size", "524288"); + try { + return Long.parseLong(config); + } catch (NumberFormatException e) { + return 524288; + } + } + + public boolean hasStoragePermission() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + return mXmppConnectionService.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED; + } else { + return true; + } + } + + public static Pair<InputStream,Integer> createInputStream(DownloadableFile file, boolean gcm) throws FileNotFoundException { + FileInputStream is; + int size; + is = new FileInputStream(file); + size = (int) file.getSize(); + if (file.getKey() == null) { + return new Pair<InputStream,Integer>(is,size); + } + try { + if (gcm) { + AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine()); + cipher.init(true, new AEADParameters(new KeyParameter(file.getKey()), 128, file.getIv())); + InputStream cis = new org.bouncycastle.crypto.io.CipherInputStream(is, cipher); + return new Pair<>(cis, cipher.getOutputSize(size)); + } else { + IvParameterSpec ips = new IvParameterSpec(file.getIv()); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(file.getKey(), "AES"), ips); + Log.d(Config.LOGTAG, "opening encrypted input stream"); + final int s = Config.REPORT_WRONG_FILESIZE_IN_OTR_JINGLE ? size : (size / 16 + 1) * 16; + return new Pair<InputStream,Integer>(new CipherInputStream(is, cipher),s); + } + } catch (InvalidKeyException e) { + return null; + } catch (NoSuchAlgorithmException e) { + return null; + } catch (NoSuchPaddingException e) { + return null; + } catch (InvalidAlgorithmParameterException e) { + return null; + } + } + + public static OutputStream createAppendedOutputStream(DownloadableFile file) { + return createOutputStream(file, false, true); + } + + public static OutputStream createOutputStream(DownloadableFile file, boolean gcm) { + return createOutputStream(file, gcm, false); + } + + private static OutputStream createOutputStream(DownloadableFile file, boolean gcm, boolean append) { + FileOutputStream os; + try { + os = new FileOutputStream(file, append); + if (file.getKey() == null) { + return os; + } + } catch (FileNotFoundException e) { + return null; + } + try { + if (gcm) { + AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine()); + cipher.init(false, new AEADParameters(new KeyParameter(file.getKey()), 128, file.getIv())); + return new org.bouncycastle.crypto.io.CipherOutputStream(os, cipher); + } else { + IvParameterSpec ips = new IvParameterSpec(file.getIv()); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(file.getKey(), "AES"), ips); + Log.d(Config.LOGTAG, "opening encrypted output stream"); + return new CipherOutputStream(os, cipher); + } + } catch (InvalidKeyException e) { + return null; + } catch (NoSuchAlgorithmException e) { + return null; + } catch (NoSuchPaddingException e) { + return null; + } catch (InvalidAlgorithmParameterException e) { + return null; + } + } + + public PowerManager.WakeLock createWakeLock(String name) { + PowerManager powerManager = (PowerManager) mXmppConnectionService.getSystemService(Context.POWER_SERVICE); + return powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,name); + } } diff --git a/src/main/java/eu/siacs/conversations/services/AvatarService.java b/src/main/java/eu/siacs/conversations/services/AvatarService.java index 1fb34c4c..1308f32b 100644 --- a/src/main/java/eu/siacs/conversations/services/AvatarService.java +++ b/src/main/java/eu/siacs/conversations/services/AvatarService.java @@ -28,6 +28,7 @@ import eu.siacs.conversations.entities.Bookmark; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.ListItem; +import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.MucOptions; import eu.siacs.conversations.generator.IqGenerator; import eu.siacs.conversations.persistance.DatabaseBackend; @@ -76,6 +77,36 @@ public class AvatarService { return avatar; } + public Bitmap get(final MucOptions.User user, final int size, boolean cachedOnly) { + Contact c = user.getContact(); + if (c != null && (c.getProfilePhoto() != null || c.getAvatar() != null)) { + return get(c, size, cachedOnly); + } else { + return getImpl(user, size, cachedOnly); + } + } + + private Bitmap getImpl(final MucOptions.User user, final int size, boolean cachedOnly) { + final String KEY = key(user, size); + Bitmap avatar = this.mXmppConnectionService.getBitmapCache().get(KEY); + if (avatar != null || cachedOnly) { + return avatar; + } + if (user.getAvatar() != null) { + avatar = mXmppConnectionService.getFileBackend().getAvatar(user.getAvatar(), size); + } + if (avatar == null) { + Contact contact = user.getContact(); + if (contact != null) { + avatar = get(contact, size, cachedOnly); + } else { + avatar = get(user.getName(), size, cachedOnly); + } + } + this.mXmppConnectionService.getBitmapCache().put(KEY, avatar); + return avatar; + } + public void clear(Contact contact) { synchronized (this.sizes) { for (Integer size : sizes) { @@ -94,6 +125,16 @@ public class AvatarService { + contact.getJid() + "_" + String.valueOf(size); } + private String key(MucOptions.User user, int size) { + synchronized (this.sizes) { + if (!this.sizes.contains(size)) { + this.sizes.add(size); + } + } + return PREFIX_CONTACT + "_" + user.getAccount().getJid().toBareJid() + "_" + + user.getFullJid() + "_" + String.valueOf(size); + } + public Bitmap get(ListItem item, int size) { return get(item,size,false); } @@ -139,7 +180,7 @@ public class AvatarService { if (bitmap != null || cachedOnly) { return bitmap; } - final List<MucOptions.User> users = new ArrayList<>(mucOptions.getUsers()); + final List<MucOptions.User> users = mucOptions.getUsers(); int count = users.size(); bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); @@ -147,11 +188,10 @@ public class AvatarService { if (count == 0) { String name = mucOptions.getConversation().getName(); - final String letter = name.isEmpty() ? "X" : name.substring(0,1); - final int color = UIHelper.getColorForName(name); - drawTile(canvas, letter, color, 0, 0, size, size); + drawTile(canvas, name, 0, 0, size, size); } else if (count == 1) { - drawTile(canvas, users.get(0), 0, 0, size, size); + drawTile(canvas, users.get(0), 0, 0, size / 2 - 1, size); + drawTile(canvas, mucOptions.getConversation().getAccount(), size / 2 + 1, 0, size, size); } else if (count == 2) { drawTile(canvas, users.get(0), 0, 0, size / 2 - 1, size); drawTile(canvas, users.get(1), size / 2 + 1, 0, size, size); @@ -196,9 +236,13 @@ public class AvatarService { } public Bitmap get(Account account, int size) { + return get(account, size, false); + } + + public Bitmap get(Account account, int size, boolean cachedOnly) { final String KEY = key(account, size); Bitmap avatar = ImageUtil.getBitmapFromCache(KEY); - if (avatar != null) { + if (avatar != null || cachedOnly) { return avatar; } avatar = AvatarUtil.getAvatar(account.getAvatar(), size); @@ -209,6 +253,24 @@ public class AvatarService { return avatar; } + public Bitmap get(Message message, int size, boolean cachedOnly) { + final Conversation conversation = message.getConversation(); + if (message.getStatus() == Message.STATUS_RECEIVED) { + Contact c = message.getContact(); + if (c != null && (c.getProfilePhoto() != null || c.getAvatar() != null)) { + return get(c, size, cachedOnly); + } else if (message.getConversation().getMode() == Conversation.MODE_MULTI){ + MucOptions.User user = conversation.getMucOptions().findUser(message.getCounterpart().getResourcepart()); + if (user != null) { + return getImpl(user,size,cachedOnly); + } + } + return get(UIHelper.getMessageDisplayName(message), size, cachedOnly); + } else { + return get(conversation.getAccount(), size, cachedOnly); + } + } + public void clear(Account account) { synchronized (this.sizes) { for (Integer size : sizes) { @@ -217,6 +279,14 @@ public class AvatarService { } } + public void clear(MucOptions.User user) { + synchronized (this.sizes) { + for (Integer size : sizes) { + this.mXmppConnectionService.getBitmapCache().remove(key(user, size)); + } + } + } + private String key(Account account, int size) { synchronized (this.sizes) { if (!this.sizes.contains(size)) { @@ -240,9 +310,7 @@ public class AvatarService { bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); final String trimmedName = name.trim(); - final String letter = trimmedName.isEmpty() ? "X" : trimmedName.substring(0,1); - final int color = UIHelper.getColorForName(name); - drawTile(canvas, letter, color, 0, 0, size, size); + drawTile(canvas, trimmedName, 0, 0, size, size); ImageUtil.addBitmapToCache(KEY, bitmap); return bitmap; } @@ -256,7 +324,7 @@ public class AvatarService { return PREFIX_GENERIC + "_" + name + "_" + String.valueOf(size); } - private void drawTile(Canvas canvas, String letter, int tileColor, + private boolean drawTile(Canvas canvas, String letter, int tileColor, int left, int top, int right, int bottom) { letter = letter.toUpperCase(Locale.getDefault()); Paint tilePaint = new Paint(), textPaint = new Paint(); @@ -272,10 +340,11 @@ public class AvatarService { textPaint.getTextBounds(letter, 0, 1, rect); float width = textPaint.measureText(letter); canvas.drawText(letter, (right + left) / 2 - width / 2, (top + bottom) - / 2 + rect.height() / 2, textPaint); + / 2 + rect.height() / 2, textPaint); + return true; } - private void drawTile(Canvas canvas, MucOptions.User user, int left, + private boolean drawTile(Canvas canvas, MucOptions.User user, int left, int top, int right, int bottom) { Contact contact = user.getContact(); if (contact != null) { @@ -285,24 +354,60 @@ public class AvatarService { } else if (contact.getAvatar() != null) { uri = AvatarUtil.getAvatarUri(contact.getAvatar()); } + if (drawTile(canvas, uri, left, top, right, bottom)) { + return true; + } + } else if (user.getAvatar() != null) { + Uri uri = mXmppConnectionService.getFileBackend().getAvatarUri(user.getAvatar()); + if (drawTile(canvas, uri, left, top, right, bottom)) { + return true; + } + } + String name = contact != null ? contact.getDisplayName() : user.getName(); + drawTile(canvas, name, left, top, right, bottom); + return true; + } + + private boolean drawTile(Canvas canvas, Account account, int left, int top, int right, int bottom) { + String avatar = account.getAvatar(); + if (avatar != null) { + Uri uri = mXmppConnectionService.getFileBackend().getAvatarUri(avatar); if (uri != null) { - Bitmap bitmap = ImageUtil.cropCenter(uri, bottom - top, right - left); - if (bitmap != null) { - drawTile(canvas, bitmap, left, top, right, bottom); - return; + if (drawTile(canvas, uri, left, top, right, bottom)) { + return true; } } } - String name = contact != null ? contact.getDisplayName() : user.getName(); - final String letter = name.isEmpty() ? "X" : name.substring(0,1); - final int color = UIHelper.getColorForName(name); - drawTile(canvas, letter, color, left, top, right, bottom); + return drawTile(canvas, account.getJid().toBareJid().toString(), left, top, right, bottom); + } + + private boolean drawTile(Canvas canvas, String name, int left, int top, int right, int bottom) { + if (name != null) { + final String letter = name.isEmpty() ? "X" : name.substring(0, 1); + final int color = UIHelper.getColorForName(name); + drawTile(canvas, letter, color, left, top, right, bottom); + return true; + } + return false; + } + + private boolean drawTile(Canvas canvas, Uri uri, int left, int top, int right, int bottom) { + if (uri != null) { + Bitmap bitmap = mXmppConnectionService.getFileBackend() + .cropCenter(uri, bottom - top, right - left); + if (bitmap != null) { + drawTile(canvas, bitmap, left, top, right, bottom); + return true; + } + } + return false; } - private void drawTile(Canvas canvas, Bitmap bm, int dstleft, int dsttop, + private boolean drawTile(Canvas canvas, Bitmap bm, int dstleft, int dsttop, int dstright, int dstbottom) { Rect dst = new Rect(dstleft, dsttop, dstright, dstbottom); canvas.drawBitmap(bm, null, dst, null); + return true; } public void publishAvatar(final Account account, diff --git a/src/main/java/eu/siacs/conversations/services/ContactChooserTargetService.java b/src/main/java/eu/siacs/conversations/services/ContactChooserTargetService.java new file mode 100644 index 00000000..c2a45bf2 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/services/ContactChooserTargetService.java @@ -0,0 +1,87 @@ +package eu.siacs.conversations.services; + +import android.annotation.TargetApi; +import android.app.PendingIntent; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.ServiceConnection; +import android.graphics.drawable.Icon; +import android.os.Build; +import android.os.Bundle; +import android.os.IBinder; +import android.os.SystemClock; +import android.service.chooser.ChooserTarget; +import android.service.chooser.ChooserTargetService; +import android.util.DisplayMetrics; +import android.util.Log; + +import java.util.ArrayList; +import java.util.List; + +import eu.siacs.conversations.entities.Conversation; +import eu.siacs.conversations.ui.ShareWithActivity; + +@TargetApi(Build.VERSION_CODES.M) +public class ContactChooserTargetService extends ChooserTargetService implements ServiceConnection { + + private final Object lock = new Object(); + + private XmppConnectionService mXmppConnectionService; + + private final int MAX_TARGETS = 5; + + @Override + public List<ChooserTarget> onGetChooserTargets(ComponentName targetActivityName, IntentFilter matchedFilter) { + Intent intent = new Intent(this, XmppConnectionService.class); + intent.setAction("contact_chooser"); + startService(intent); + bindService(intent, this, Context.BIND_AUTO_CREATE); + ArrayList<ChooserTarget> chooserTargets = new ArrayList<>(); + try { + waitForService(); + final ArrayList<Conversation> conversations = new ArrayList<>(); + if (!mXmppConnectionService.areMessagesInitialized()) { + return chooserTargets; + } + mXmppConnectionService.populateWithOrderedConversations(conversations, false); + final ComponentName componentName = new ComponentName(this, ShareWithActivity.class); + final int pixel = (int) (48 * getResources().getDisplayMetrics().density); + for(int i = 0; i < Math.min(conversations.size(),MAX_TARGETS); ++i) { + final Conversation conversation = conversations.get(i); + final String name = conversation.getName(); + final Icon icon = Icon.createWithBitmap(mXmppConnectionService.getAvatarService().get(conversation, pixel)); + final float score = (1.0f / MAX_TARGETS) * i; + final Bundle extras = new Bundle(); + extras.putString("uuid", conversation.getUuid()); + chooserTargets.add(new ChooserTarget(name, icon, score, componentName, extras)); + } + } catch (InterruptedException e) { + } + unbindService(this); + return chooserTargets; + } + + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + XmppConnectionService.XmppConnectionBinder binder = (XmppConnectionService.XmppConnectionBinder) service; + mXmppConnectionService = binder.getService(); + synchronized (this.lock) { + lock.notifyAll(); + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + mXmppConnectionService = null; + } + + private void waitForService() throws InterruptedException { + if (mXmppConnectionService == null) { + synchronized (this.lock) { + lock.wait(); + } + } + } +} diff --git a/src/main/java/eu/siacs/conversations/services/EventReceiver.java b/src/main/java/eu/siacs/conversations/services/EventReceiver.java index dfbe9db7..ceab1592 100644 --- a/src/main/java/eu/siacs/conversations/services/EventReceiver.java +++ b/src/main/java/eu/siacs/conversations/services/EventReceiver.java @@ -1,10 +1,11 @@ package eu.siacs.conversations.services; -import eu.siacs.conversations.persistance.DatabaseBackend; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import eu.siacs.conversations.persistance.DatabaseBackend; + public class EventReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { diff --git a/src/main/java/eu/siacs/conversations/services/ExportLogsService.java b/src/main/java/eu/siacs/conversations/services/ExportLogsService.java new file mode 100644 index 00000000..76983a90 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/services/ExportLogsService.java @@ -0,0 +1,146 @@ +package eu.siacs.conversations.services; + +import android.app.NotificationManager; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.os.IBinder; +import android.support.v4.app.NotificationCompat; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +import eu.siacs.conversations.R; +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.entities.Conversation; +import eu.siacs.conversations.entities.Message; +import eu.siacs.conversations.persistance.DatabaseBackend; +import eu.siacs.conversations.persistance.FileBackend; +import eu.siacs.conversations.xmpp.jid.Jid; + +public class ExportLogsService extends Service { + + private static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + private static final String DIRECTORY_STRING_FORMAT = FileBackend.getConversationsFileDirectory() + "/logs/%s"; + private static final String MESSAGE_STRING_FORMAT = "(%s) %s: %s\n"; + private static final int NOTIFICATION_ID = 1; + private static AtomicBoolean running = new AtomicBoolean(false); + private DatabaseBackend mDatabaseBackend; + private List<Account> mAccounts; + + @Override + public void onCreate() { + mDatabaseBackend = DatabaseBackend.getInstance(getBaseContext()); + mAccounts = mDatabaseBackend.getAccounts(); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + if (running.compareAndSet(false, true)) { + new Thread(new Runnable() { + @Override + public void run() { + running.set(false); + export(); + stopForeground(true); + stopSelf(); + } + }).start(); + } + return START_NOT_STICKY; + } + + private void export() { + List<Conversation> conversations = mDatabaseBackend.getConversations(Conversation.STATUS_AVAILABLE); + conversations.addAll(mDatabaseBackend.getConversations(Conversation.STATUS_ARCHIVED)); + NotificationManager mNotifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(getBaseContext()); + mBuilder.setContentTitle(getString(R.string.notification_export_logs_title)) + .setSmallIcon(R.drawable.ic_import_export_white_24dp) + .setProgress(conversations.size(), 0, false); + startForeground(NOTIFICATION_ID, mBuilder.build()); + + int progress = 0; + for (Conversation conversation : conversations) { + writeToFile(conversation); + progress++; + mBuilder.setProgress(conversations.size(), progress, false); + mNotifyManager.notify(NOTIFICATION_ID, mBuilder.build()); + } + } + + private void writeToFile(Conversation conversation) { + Jid accountJid = resolveAccountUuid(conversation.getAccountUuid()); + Jid contactJid = conversation.getJid(); + + File dir = new File(String.format(DIRECTORY_STRING_FORMAT,accountJid.toBareJid().toString())); + dir.mkdirs(); + + BufferedWriter bw = null; + try { + for (Message message : mDatabaseBackend.getMessagesIterable(conversation)) { + if (message.getType() == Message.TYPE_TEXT || message.hasFileOnRemoteHost()) { + String date = simpleDateFormat.format(new Date(message.getTimeSent())); + if (bw == null) { + bw = new BufferedWriter(new FileWriter( + new File(dir, contactJid.toBareJid().toString() + ".txt"))); + } + String jid = null; + switch (message.getStatus()) { + case Message.STATUS_RECEIVED: + jid = getMessageCounterpart(message); + break; + case Message.STATUS_SEND: + case Message.STATUS_SEND_RECEIVED: + case Message.STATUS_SEND_DISPLAYED: + jid = accountJid.toBareJid().toString(); + break; + } + if (jid != null) { + String body = message.hasFileOnRemoteHost() ? message.getFileParams().url.toString() : message.getBody(); + bw.write(String.format(MESSAGE_STRING_FORMAT, date, jid, + body.replace("\\\n", "\\ \n").replace("\n", "\\ \n"))); + } + } + } + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + if (bw != null) { + bw.close(); + } + } catch (IOException e1) { + e1.printStackTrace(); + } + } + } + + private Jid resolveAccountUuid(String accountUuid) { + for (Account account : mAccounts) { + if (account.getUuid().equals(accountUuid)) { + return account.getJid(); + } + } + return null; + } + + private String getMessageCounterpart(Message message) { + String trueCounterpart = (String) message.getContentValues().get(Message.TRUE_COUNTERPART); + if (trueCounterpart != null) { + return trueCounterpart; + } else { + return message.getCounterpart().toString(); + } + } + + @Override + public IBinder onBind(Intent intent) { + return null; + } +}
\ No newline at end of file diff --git a/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java b/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java index e86ca573..4400105e 100644 --- a/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java +++ b/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java @@ -1,5 +1,8 @@ package eu.siacs.conversations.services; +import android.util.Log; +import android.util.Pair; + import java.math.BigInteger; import java.util.ArrayList; import java.util.HashSet; @@ -35,7 +38,15 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { this.mXmppConnectionService = service; } - public void catchup(final Account account) { + private void catchup(final Account account) { + synchronized (this.queries) { + for(Iterator<Query> iterator = this.queries.iterator(); iterator.hasNext();) { + Query query = iterator.next(); + if (query.getAccount() == account) { + iterator.remove(); + } + } + } long startCatchup = getLastMessageTransmitted(account); long endCatchup = account.getXmppConnection().getLastSessionEstablished(); if (startCatchup == 0) { @@ -54,21 +65,33 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { this.execute(query); } - private long getLastMessageTransmitted(final Account account) { - long timestamp = 0; - for(final Conversation conversation : mXmppConnectionService.getConversations()) { - if (conversation.getAccount() == account) { - long tmp = conversation.getLastMessageTransmitted(); - if (tmp > timestamp) { - timestamp = tmp; - } - } + public void catchupMUC(final Conversation conversation) { + if (conversation.getLastMessageTransmitted() < 0 && conversation.countMessages() == 0) { + query(conversation, + 0, + System.currentTimeMillis()); + } else { + query(conversation, + conversation.getLastMessageTransmitted(), + System.currentTimeMillis()); } - return timestamp; + } + + private long getLastMessageTransmitted(final Account account) { + Pair<Long,String> pair = mXmppConnectionService.databaseBackend.getLastMessageReceived(account); + return pair == null ? 0 : pair.first; } public Query query(final Conversation conversation) { - return query(conversation,conversation.getAccount().getXmppConnection().getLastSessionEstablished()); + if (conversation.getLastMessageTransmitted() < 0 && conversation.countMessages() == 0) { + return query(conversation, + 0, + System.currentTimeMillis()); + } else { + return query(conversation, + conversation.getLastMessageTransmitted(), + conversation.getAccount().getXmppConnection().getLastSessionEstablished()); + } } public Query query(final Conversation conversation, long end) { @@ -111,7 +134,14 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { this.mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.ERROR) { + if (packet.getType() == IqPacket.TYPE.TIMEOUT) { + synchronized (MessageArchiveService.this.queries) { + MessageArchiveService.this.queries.remove(query); + if (query.hasCallback()) { + query.callback(); + } + } + } else if (packet.getType() != IqPacket.TYPE.RESULT) { Logging.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": error executing mam: " + packet.toString()); finalizeQuery(query); } @@ -131,25 +161,19 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { final Conversation conversation = query.getConversation(); if (conversation != null) { conversation.sort(); - if (conversation.setLastMessageTransmitted(query.getEnd())) { - this.mXmppConnectionService.databaseBackend.updateConversation(conversation); - } - if (query.hasCallback()) { - query.callback(); - } else { - conversation.setHasMessagesLeftOnServer(query.getMessageCount() > 0); - this.mXmppConnectionService.updateConversationUi(); - } } else { for(Conversation tmp : this.mXmppConnectionService.getConversations()) { if (tmp.getAccount() == query.getAccount()) { tmp.sort(); - if (tmp.setLastMessageTransmitted(query.getEnd())) { - this.mXmppConnectionService.databaseBackend.updateConversation(tmp); - } } } } + if (query.hasCallback()) { + query.callback(); + } else { + conversation.setHasMessagesLeftOnServer(query.getMessageCount() > 0); + this.mXmppConnectionService.updateConversationUi(); + } } public boolean queryInProgress(Conversation conversation, XmppConnectionService.OnMoreMessagesLoaded callback) { @@ -183,6 +207,9 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { if (complete || relevant == null || abort) { this.finalizeQuery(query); Logging.d(Config.LOGTAG,query.getAccount().getJid().toBareJid().toString()+": finished mam after "+query.getTotalCount()+" messages"); + if (query.getWith() == null && query.getMessageCount() > 0) { + mXmppConnectionService.getNotificationService().finishBacklog(true); + } } else { final Query nextQuery; if (query.getPagingOrder() == PagingOrder.NORMAL) { diff --git a/src/main/java/eu/siacs/conversations/services/NotificationService.java b/src/main/java/eu/siacs/conversations/services/NotificationService.java index 8245f0df..5a50a8df 100644 --- a/src/main/java/eu/siacs/conversations/services/NotificationService.java +++ b/src/main/java/eu/siacs/conversations/services/NotificationService.java @@ -6,6 +6,7 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import android.graphics.Bitmap; import android.net.Uri; import android.os.Build; @@ -17,6 +18,7 @@ import android.support.v4.app.NotificationCompat.Builder; import android.support.v4.app.TaskStackBuilder; import android.text.Html; import android.util.DisplayMetrics; +import android.util.Log; import org.json.JSONArray; import org.json.JSONObject; @@ -41,8 +43,10 @@ import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.ui.ConversationActivity; import eu.siacs.conversations.ui.ManageAccountActivity; +import eu.siacs.conversations.ui.TimePreference; import eu.siacs.conversations.utils.GeoHelper; import eu.siacs.conversations.utils.UIHelper; +import eu.siacs.conversations.xmpp.XmppConnection; public class NotificationService { @@ -94,6 +98,11 @@ public class NotificationService { mXmppConnectionService.sendBroadcast(i); } + + public boolean notificationsEnabled() { + return mXmppConnectionService.getPreferences().getBoolean("show_notification", true); + } + public boolean isQuietHours() { if (!ConversationsPlusPreferences.enableQuietHours()) { return false; @@ -109,47 +118,47 @@ public class NotificationService { } } - @SuppressLint("NewApi") - @SuppressWarnings("deprecation") - private boolean isInteractive() { - final PowerManager pm = (PowerManager) mXmppConnectionService - .getSystemService(Context.POWER_SERVICE); + public void pushFromBacklog(final Message message) { + if (notify(message)) { + synchronized (notifications) { + pushToStack(message); + } + } + } - final boolean isScreenOn; - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - isScreenOn = pm.isScreenOn(); - } else { - isScreenOn = pm.isInteractive(); + public void finishBacklog(boolean notify) { + synchronized (notifications) { + mXmppConnectionService.updateUnreadCountBadge(); + updateNotification(notify); } + } - return isScreenOn; + private void pushToStack(final Message message) { + final String conversationUuid = message.getConversationUuid(); + if (notifications.containsKey(conversationUuid)) { + notifications.get(conversationUuid).add(message); + } else { + final ArrayList<Message> mList = new ArrayList<>(); + mList.add(message); + notifications.put(conversationUuid, mList); + } } public void push(final Message message) { if (!notify(message)) { return; } - mXmppConnectionService.updateUnreadCountBadge(); - - final boolean isScreenOn = isInteractive(); - + mXmppConnectionService.updateUnreadCountBadge(); + final boolean isScreenOn = mXmppConnectionService.isInteractive(); if (this.mIsInForeground && isScreenOn && this.mOpenConversation == message.getConversation()) { return; } - synchronized (notifications) { - final String conversationUuid = message.getConversationUuid(); - if (notifications.containsKey(conversationUuid)) { - notifications.get(conversationUuid).add(message); - } else { - final ArrayList<Message> mList = new ArrayList<>(); - mList.add(message); - notifications.put(conversationUuid, mList); - } + pushToStack(message); final Account account = message.getConversation().getAccount(); final boolean doNotify = (!(this.mIsInForeground && this.mOpenConversation == null) || !isScreenOn) - && !account.inGracePeriod() - && !this.inMiniGracePeriod(account); + && !account.inGracePeriod() + && !this.inMiniGracePeriod(account); updateNotification(doNotify); if (doNotify) { notifyPebble(message); @@ -172,10 +181,10 @@ public class NotificationService { } private void setNotificationColor(final Builder mBuilder) { - mBuilder.setColor(mXmppConnectionService.getResources().getColor(R.color.notification)); + mBuilder.setColor(mXmppConnectionService.getResources().getColor(R.color.primary)); } - private void updateNotification(final boolean notify) { + public void updateNotification(final boolean notify) { final NotificationManager notificationManager = (NotificationManager) ConversationsPlusApplication.getAppContext().getSystemService(Context.NOTIFICATION_SERVICE); final String ringtone = ConversationsPlusPreferences.notificationRingtone(); @@ -230,8 +239,13 @@ public class NotificationService { if (messages.size() > 0) { conversation = messages.get(0).getConversation(); final String name = conversation.getName(); - style.addLine(Html.fromHtml("<b>" + name + "</b> " + if (Config.HIDE_MESSAGE_TEXT_IN_NOTIFICATION) { + int count = messages.size(); + style.addLine(Html.fromHtml("<b>"+name+"</b>: "+mXmppConnectionService.getResources().getQuantityString(R.plurals.x_messages,count,count))); + } else { + style.addLine(Html.fromHtml("<b>" + name + "</b>: " + UIHelper.getMessagePreview(mXmppConnectionService, messages.get(0)).first)); + } names.append(name); names.append(", "); } @@ -259,25 +273,32 @@ public class NotificationService { final Conversation conversation = messages.get(0).getConversation(); mBuilder.setLargeIcon(AvatarService.getInstance().get(conversation, getPixel(64))); mBuilder.setContentTitle(conversation.getName()); - Message message; - if ((message = getImage(messages)) != null) { - modifyForImage(mBuilder, message, messages, notify); + if (Config.HIDE_MESSAGE_TEXT_IN_NOTIFICATION) { + int count = messages.size(); + mBuilder.setContentText(mXmppConnectionService.getResources().getQuantityString(R.plurals.x_messages,count,count)); } else { - modifyForTextOnly(mBuilder, messages, notify); - } - if ((message = getFirstDownloadableMessage(messages)) != null) { - mBuilder.addAction( - Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? - R.drawable.ic_file_download_white_24dp : R.drawable.ic_action_download, - mXmppConnectionService.getResources().getString(R.string.download_x_file, - UIHelper.getFileDescriptionString(mXmppConnectionService, message)), - createDownloadIntent(message) - ); - } - if ((message = getFirstLocationMessage(messages)) != null) { - mBuilder.addAction(R.drawable.ic_room_white_24dp, - mXmppConnectionService.getString(R.string.show_location), - createShowLocationIntent(message)); + Message message; + if ((message = getImage(messages)) != null) { + modifyForImage(mBuilder, message, messages, notify); + } else if (conversation.getMode() == Conversation.MODE_MULTI) { + modifyForConference(mBuilder, conversation, messages, notify); + } else { + modifyForTextOnly(mBuilder, messages, notify); + } + if ((message = getFirstDownloadableMessage(messages)) != null) { + mBuilder.addAction( + Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? + R.drawable.ic_file_download_white_24dp : R.drawable.ic_action_download, + mXmppConnectionService.getResources().getString(R.string.download_x_file, + UIHelper.getFileDescriptionString(mXmppConnectionService, message)), + createDownloadIntent(message) + ); + } + if ((message = getFirstLocationMessage(messages)) != null) { + mBuilder.addAction(R.drawable.ic_room_white_24dp, + mXmppConnectionService.getString(R.string.show_location), + createShowLocationIntent(message)); + } } mBuilder.setContentIntent(createContentIntent(conversation)); } @@ -285,7 +306,7 @@ public class NotificationService { } private void modifyForImage(final Builder builder, final Message message, - final ArrayList<Message> messages, final boolean notify) { + final ArrayList<Message> messages, final boolean notify) { try { final Bitmap bitmap = ImageUtil.getThumbnail(message, getPixel(288), false); final ArrayList<Message> tmp = new ArrayList<>(); @@ -293,17 +314,17 @@ public class NotificationService { if (msg.getType() == Message.TYPE_TEXT && msg.getTransferable() == null) { tmp.add(msg); - } + } } final BigPictureStyle bigPictureStyle = new NotificationCompat.BigPictureStyle(); bigPictureStyle.bigPicture(bitmap); if (tmp.size() > 0) { bigPictureStyle.setSummaryText(getMergedBodies(tmp)); - builder.setContentText(UIHelper.getMessagePreview(mXmppConnectionService,tmp.get(0)).first); + builder.setContentText(UIHelper.getMessagePreview(mXmppConnectionService, tmp.get(0)).first); } else { builder.setContentText(mXmppConnectionService.getString( R.string.received_x_file, - UIHelper.getFileDescriptionString(mXmppConnectionService,message))); + UIHelper.getFileDescriptionString(mXmppConnectionService, message))); } builder.setStyle(bigPictureStyle); } catch (final FileNotFoundException e) { @@ -312,19 +333,40 @@ public class NotificationService { } private void modifyForTextOnly(final Builder builder, - final ArrayList<Message> messages, final boolean notify) { + final ArrayList<Message> messages, final boolean notify) { builder.setStyle(new NotificationCompat.BigTextStyle().bigText(getMergedBodies(messages))); - builder.setContentText(UIHelper.getMessagePreview(mXmppConnectionService,messages.get(0)).first); + builder.setContentText(UIHelper.getMessagePreview(mXmppConnectionService, messages.get(0)).first); if (notify) { - builder.setTicker(UIHelper.getMessagePreview(mXmppConnectionService,messages.get(messages.size() - 1)).first); + builder.setTicker(UIHelper.getMessagePreview(mXmppConnectionService, messages.get(messages.size() - 1)).first); + } + } + + private void modifyForConference(Builder builder, Conversation conversation, List<Message> messages, boolean notify) { + final Message first = messages.get(0); + final Message last = messages.get(messages.size() - 1); + final NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle(); + style.setBigContentTitle(conversation.getName()); + + for(Message message : messages) { + if (message.hasMeCommand()) { + style.addLine(UIHelper.getMessagePreview(mXmppConnectionService,message).first); + } else { + style.addLine(Html.fromHtml("<b>" + UIHelper.getMessageDisplayName(message) + "</b>: " + UIHelper.getMessagePreview(mXmppConnectionService, message).first)); + } + } + builder.setContentText((first.hasMeCommand() ? "" :UIHelper.getMessageDisplayName(first)+ ": ") +UIHelper.getMessagePreview(mXmppConnectionService, first).first); + builder.setStyle(style); + if (notify) { + builder.setTicker((last.hasMeCommand() ? "" : UIHelper.getMessageDisplayName(last) + ": ") + UIHelper.getMessagePreview(mXmppConnectionService,last).first); } } private Message getImage(final Iterable<Message> messages) { for (final Message message : messages) { - if (message.getType() == Message.TYPE_IMAGE + if (message.getType() != Message.TYPE_TEXT && message.getTransferable() == null - && message.getEncryption() != Message.ENCRYPTION_PGP) { + && message.getEncryption() != Message.ENCRYPTION_PGP + && message.getFileParams().height > 0) { return message; } } @@ -342,7 +384,7 @@ public class NotificationService { } private Message getFirstLocationMessage(final Iterable<Message> messages) { - for(final Message message : messages) { + for (final Message message : messages) { if (GeoHelper.isGeoUri(message.getBody())) { return message; } @@ -353,7 +395,7 @@ public class NotificationService { private CharSequence getMergedBodies(final ArrayList<Message> messages) { final StringBuilder text = new StringBuilder(); for (int i = 0; i < messages.size(); ++i) { - text.append(UIHelper.getMessagePreview(mXmppConnectionService,messages.get(i)).first); + text.append(UIHelper.getMessagePreview(mXmppConnectionService, messages.get(i)).first); if (i != messages.size() - 1) { text.append("\n"); } @@ -363,9 +405,9 @@ public class NotificationService { private PendingIntent createShowLocationIntent(final Message message) { Iterable<Intent> intents = GeoHelper.createGeoIntentsFromMessage(message); - for(Intent intent : intents) { + for (Intent intent : intents) { if (intent.resolveActivity(mXmppConnectionService.getPackageManager()) != null) { - return PendingIntent.getActivity(mXmppConnectionService,18,intent,PendingIntent.FLAG_UPDATE_CURRENT); + return PendingIntent.getActivity(mXmppConnectionService, 18, intent, PendingIntent.FLAG_UPDATE_CURRENT); } } return createOpenConversationsIntent(); @@ -373,7 +415,7 @@ public class NotificationService { private PendingIntent createContentIntent(final String conversationUuid, final String downloadMessageUuid) { final TaskStackBuilder stackBuilder = TaskStackBuilder - .create(mXmppConnectionService); + .create(mXmppConnectionService); stackBuilder.addParentStack(ConversationActivity.class); final Intent viewConversationIntent = new Intent(mXmppConnectionService, @@ -425,13 +467,13 @@ public class NotificationService { } private PendingIntent createDisableAccountIntent(final Account account) { - final Intent intent = new Intent(mXmppConnectionService,XmppConnectionService.class); + final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class); intent.setAction(XmppConnectionService.ACTION_DISABLE_ACCOUNT); - intent.putExtra("account",account.getJid().toBareJid().toString()); - return PendingIntent.getService(mXmppConnectionService,0,intent,PendingIntent.FLAG_UPDATE_CURRENT); + intent.putExtra("account", account.getJid().toBareJid().toString()); + return PendingIntent.getService(mXmppConnectionService, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); } - public boolean wasHighlightedOrPrivate(final Message message) { + private boolean wasHighlightedOrPrivate(final Message message) { final String nick = message.getConversation().getMucOptions().getActualNick(); final Pattern highlight = generateNickHighlightPattern(nick); if (message.getBody() == null || nick == null) { @@ -461,7 +503,7 @@ public class NotificationService { private int getPixel(final int dp) { final DisplayMetrics metrics = mXmppConnectionService.getResources() - .getDisplayMetrics(); + .getDisplayMetrics(); return ((int) (dp * metrics.density)); } @@ -471,7 +513,7 @@ public class NotificationService { private boolean inMiniGracePeriod(final Account account) { final int miniGrace = account.getStatus() == Account.State.ONLINE ? Config.MINI_GRACE_PERIOD - : Config.MINI_GRACE_PERIOD * 2; + : Config.MINI_GRACE_PERIOD * 2; return SystemClock.elapsedRealtime() < (this.mLastNotification + miniGrace); } @@ -479,41 +521,57 @@ public class NotificationService { final NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(mXmppConnectionService); mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.conversations_foreground_service)); - mBuilder.setContentText(mXmppConnectionService.getString(R.string.touch_to_open_conversations)); + if (Config.SHOW_CONNECTED_ACCOUNTS) { + List<Account> accounts = mXmppConnectionService.getAccounts(); + int enabled = 0; + int connected = 0; + for (Account account : accounts) { + if (account.isOnlineAndConnected()) { + connected++; + enabled++; + } else if (!account.isOptionSet(Account.OPTION_DISABLED)) { + enabled++; + } + } + mBuilder.setContentText(mXmppConnectionService.getString(R.string.connected_accounts, connected, enabled)); + } else { + mBuilder.setContentText(mXmppConnectionService.getString(R.string.touch_to_open_conversations)); + } mBuilder.setContentIntent(createOpenConversationsIntent()); mBuilder.setWhen(0); - mBuilder.setPriority(NotificationCompat.PRIORITY_MIN); + mBuilder.setPriority(Config.SHOW_CONNECTED_ACCOUNTS ? NotificationCompat.PRIORITY_DEFAULT : NotificationCompat.PRIORITY_MIN); final int cancelIcon; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { mBuilder.setCategory(Notification.CATEGORY_SERVICE); - mBuilder.setSmallIcon(R.drawable.ic_import_export_white_24dp); cancelIcon = R.drawable.ic_cancel_white_24dp; } else { - mBuilder.setSmallIcon(R.drawable.ic_stat_communication_import_export); cancelIcon = R.drawable.ic_action_cancel; } + mBuilder.setSmallIcon(R.drawable.ic_link_white_24dp); mBuilder.addAction(cancelIcon, mXmppConnectionService.getString(R.string.disable_foreground_service), createDisableForeground()); - setNotificationColor(mBuilder); return mBuilder.build(); } private PendingIntent createOpenConversationsIntent() { - return PendingIntent.getActivity(mXmppConnectionService, 0, new Intent(mXmppConnectionService,ConversationActivity.class),0); + return PendingIntent.getActivity(mXmppConnectionService, 0, new Intent(mXmppConnectionService, ConversationActivity.class), 0); } public void updateErrorNotification() { - final NotificationManager mNotificationManager = (NotificationManager) mXmppConnectionService.getSystemService(Context.NOTIFICATION_SERVICE); + final NotificationManager notificationManager = (NotificationManager) mXmppConnectionService.getSystemService(Context.NOTIFICATION_SERVICE); final List<Account> errors = new ArrayList<>(); for (final Account account : mXmppConnectionService.getAccounts()) { if (account.hasErrorStatus()) { errors.add(account); } } + if (mXmppConnectionService.getPreferences().getBoolean("keep_foreground_service", false)) { + notificationManager.notify(FOREGROUND_NOTIFICATION_ID, createForegroundNotification()); + } final NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(mXmppConnectionService); if (errors.size() == 0) { - mNotificationManager.cancel(ERROR_NOTIFICATION_ID); + notificationManager.cancel(ERROR_NOTIFICATION_ID); return; } else if (errors.size() == 1) { mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.problem_connecting_to_account)); @@ -540,12 +598,12 @@ public class NotificationService { final TaskStackBuilder stackBuilder = TaskStackBuilder.create(mXmppConnectionService); stackBuilder.addParentStack(ConversationActivity.class); - final Intent manageAccountsIntent = new Intent(mXmppConnectionService,ManageAccountActivity.class); + final Intent manageAccountsIntent = new Intent(mXmppConnectionService, ManageAccountActivity.class); stackBuilder.addNextIntent(manageAccountsIntent); - final PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0,PendingIntent.FLAG_UPDATE_CURRENT); + final PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); mBuilder.setContentIntent(resultPendingIntent); - mNotificationManager.notify(ERROR_NOTIFICATION_ID, mBuilder.build()); + notificationManager.notify(ERROR_NOTIFICATION_ID, mBuilder.build()); } } diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 5aca32d0..09a409fc 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -6,11 +6,16 @@ import android.app.PendingIntent; import android.app.Service; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; import android.database.ContentObserver; +import android.graphics.Bitmap; +import android.media.AudioManager; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.Uri; import android.os.Binder; +import android.os.Build; import android.os.Bundle; import android.os.FileObserver; import android.os.IBinder; @@ -18,7 +23,13 @@ import android.os.Looper; import android.os.PowerManager; import android.os.PowerManager.WakeLock; import android.os.SystemClock; +import android.preference.PreferenceManager; import android.provider.ContactsContract; +import android.security.KeyChain; +import android.util.DisplayMetrics; +import android.util.Log; +import android.util.LruCache; +import android.util.Pair; import net.java.otr4j.OtrException; import net.java.otr4j.session.Session; @@ -26,17 +37,22 @@ import net.java.otr4j.session.SessionID; import net.java.otr4j.session.SessionImpl; import net.java.otr4j.session.SessionStatus; +import org.openintents.openpgp.IOpenPgpService2; import org.openintents.openpgp.util.OpenPgpApi; import org.openintents.openpgp.util.OpenPgpServiceConnection; import java.math.BigInteger; import java.security.SecureRandom; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; +import java.util.HashMap; import java.util.Hashtable; +import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; @@ -58,6 +74,8 @@ import de.tzur.conversations.Settings; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.PgpEngine; +import eu.siacs.conversations.crypto.axolotl.AxolotlService; +import eu.siacs.conversations.crypto.axolotl.XmppAxolotlMessage; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Blockable; import eu.siacs.conversations.entities.Bookmark; @@ -68,6 +86,9 @@ import eu.siacs.conversations.entities.TransferablePlaceholder; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.MucOptions; import eu.siacs.conversations.entities.MucOptions.OnRenameListener; +import eu.siacs.conversations.entities.Presences; +import eu.siacs.conversations.entities.Transferable; +import eu.siacs.conversations.entities.TransferablePlaceholder; import eu.siacs.conversations.generator.IqGenerator; import eu.siacs.conversations.generator.MessageGenerator; import eu.siacs.conversations.generator.PresenceGenerator; @@ -83,11 +104,13 @@ import eu.siacs.conversations.utils.ExceptionHelper; import eu.siacs.conversations.utils.OnPhoneContactsLoadedListener; import eu.siacs.conversations.utils.PRNGFixes; import eu.siacs.conversations.utils.PhoneHelper; +import eu.siacs.conversations.utils.SerialSingleThreadExecutor; import eu.siacs.conversations.utils.Xmlns; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xmpp.OnBindListener; import eu.siacs.conversations.xmpp.OnContactStatusChanged; import eu.siacs.conversations.xmpp.OnIqPacketReceived; +import eu.siacs.conversations.xmpp.OnKeyStatusUpdated; import eu.siacs.conversations.xmpp.OnMessageAcknowledged; import eu.siacs.conversations.xmpp.OnMessagePacketReceived; import eu.siacs.conversations.xmpp.OnPresencePacketReceived; @@ -102,6 +125,7 @@ import eu.siacs.conversations.xmpp.jid.Jid; import eu.siacs.conversations.xmpp.jingle.JingleConnectionManager; import eu.siacs.conversations.xmpp.jingle.OnJinglePacketReceived; import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket; +import eu.siacs.conversations.xmpp.pep.Avatar; import eu.siacs.conversations.xmpp.stanzas.IqPacket; import eu.siacs.conversations.xmpp.stanzas.MessagePacket; import eu.siacs.conversations.xmpp.stanzas.PresencePacket; @@ -115,6 +139,14 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa private static final String ACTION_MERGE_PHONE_CONTACTS = "merge_phone_contacts"; public static final String ACTION_TRY_AGAIN = "try_again"; public static final String ACTION_DISABLE_ACCOUNT = "disable_account"; + private static final String ACTION_MERGE_PHONE_CONTACTS = "merge_phone_contacts"; + private final SerialSingleThreadExecutor mFileAddingExecutor = new SerialSingleThreadExecutor(); + private final SerialSingleThreadExecutor mDatabaseExecutor = new SerialSingleThreadExecutor(); + private final IBinder mBinder = new XmppConnectionBinder(); + private final List<Conversation> conversations = new CopyOnWriteArrayList<>(); + private final IqGenerator mIqGenerator = new IqGenerator(this); + private final List<String> mInProgressAvatarFetches = new ArrayList<>(); + public DatabaseBackend databaseBackend; private ContentObserver contactObserver = new ContentObserver(null) { @Override public void onChange(boolean selfChange) { @@ -125,59 +157,30 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa startService(intent); } }; - - private final IBinder mBinder = new XmppConnectionBinder(); - private final List<Conversation> conversations = new CopyOnWriteArrayList<>(); - private final FileObserver fileObserver = new FileObserver( - FileBackend.getConversationsImageDirectory()) { - - @Override - public void onEvent(int event, String path) { - if (event == FileObserver.DELETE) { - markFileDeleted(path.split("\\.")[0]); - } - } - }; - private final OnJinglePacketReceived jingleListener = new OnJinglePacketReceived() { - - @Override - public void onJinglePacketReceived(Account account, JinglePacket packet) { - mJingleConnectionManager.deliverPacket(account, packet); - } - }; - private final OnBindListener mOnBindListener = new OnBindListener() { - - @Override - public void onBind(final Account account) { - account.getRoster().clearPresences(); - account.pendingConferenceJoins.clear(); - account.pendingConferenceLeaves.clear(); - fetchRosterFromServer(account); - fetchBookmarks(account); - sendPresence(account); - connectMultiModeConversations(account); - updateConversationUi(); - } - }; - private final OnMessageAcknowledged mOnMessageAcknowledgedListener = new OnMessageAcknowledged() { - + private FileBackend fileBackend = new FileBackend(this); + private MemorizingTrustManager mMemorizingTrustManager; + private NotificationService mNotificationService = new NotificationService( + this); + private OnMessagePacketReceived mMessageParser = new MessageParser(this); + private OnPresencePacketReceived mPresenceParser = new PresenceParser(this); + private IqParser mIqParser = new IqParser(this); + private OnIqPacketReceived mDefaultIqHandler = new OnIqPacketReceived() { @Override - public void onMessageAcknowledged(Account account, String uuid) { - for (final Conversation conversation : getConversations()) { - if (conversation.getAccount() == account) { - Message message = conversation.findUnsentMessageWithUuid(uuid); - if (message != null) { - markMessage(message, Message.STATUS_SEND); - if (conversation.setLastMessageTransmitted(System.currentTimeMillis())) { - databaseBackend.updateConversation(conversation); - } - } + public void onIqPacketReceived(Account account, IqPacket packet) { + if (packet.getType() != IqPacket.TYPE.RESULT) { + Element error = packet.findChild("error"); + String text = error != null ? error.findChildContent("text") : null; + if (text != null) { + Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": received iq error - " + text); } } } }; - private final IqGenerator mIqGenerator = new IqGenerator(); - public DatabaseBackend databaseBackend; + private MessageGenerator mMessageGenerator = new MessageGenerator(this); + private PresenceGenerator mPresenceGenerator = new PresenceGenerator(this); + private List<Account> accounts; + private JingleConnectionManager mJingleConnectionManager = new JingleConnectionManager( + this); public OnContactStatusChanged onContactStatusChanged = new OnContactStatusChanged() { @Override @@ -204,38 +207,73 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } }; - private MemorizingTrustManager mMemorizingTrustManager; - private NotificationService mNotificationService = new NotificationService( + private HttpConnectionManager mHttpConnectionManager = new HttpConnectionManager( this); - private OnMessagePacketReceived mMessageParser = new MessageParser(this); - private OnPresencePacketReceived mPresenceParser = new PresenceParser(this); - private IqParser mIqParser = new IqParser(this); - private OnIqPacketReceived mDefaultIqHandler = new OnIqPacketReceived() { + private AvatarService mAvatarService = new AvatarService(this); + private MessageArchiveService mMessageArchiveService = new MessageArchiveService(this); + private OnConversationUpdate mOnConversationUpdate = null; + private final FileObserver fileObserver = new FileObserver( + FileBackend.getConversationsImageDirectory()) { + @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.ERROR) { - Element error = packet.findChild("error"); - String text = error != null ? error.findChildContent("text") : null; - if (text != null) { - Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": received iq error - "+text); + public void onEvent(int event, String path) { + if (event == FileObserver.DELETE) { + markFileDeleted(path.split("\\.")[0]); + } + } + }; + private final OnJinglePacketReceived jingleListener = new OnJinglePacketReceived() { + + @Override + public void onJinglePacketReceived(Account account, JinglePacket packet) { + mJingleConnectionManager.deliverPacket(account, packet); + } + }; + private final OnMessageAcknowledged mOnMessageAcknowledgedListener = new OnMessageAcknowledged() { + + @Override + public void onMessageAcknowledged(Account account, String uuid) { + for (final Conversation conversation : getConversations()) { + if (conversation.getAccount() == account) { + Message message = conversation.findUnsentMessageWithUuid(uuid); + if (message != null) { + markMessage(message, Message.STATUS_SEND); + } } } } }; - private MessageGenerator mMessageGenerator = new MessageGenerator(); - private PresenceGenerator mPresenceGenerator = new PresenceGenerator(); - private List<Account> accounts; - private JingleConnectionManager mJingleConnectionManager = new JingleConnectionManager( - this); - private HttpConnectionManager mHttpConnectionManager = new HttpConnectionManager( - this); - private MessageArchiveService mMessageArchiveService = new MessageArchiveService(this); - private OnConversationUpdate mOnConversationUpdate = null; private int convChangedListenerCount = 0; private OnShowErrorToast mOnShowErrorToast = null; private int showErrorToastListenerCount = 0; private int unreadCount = -1; private OnAccountUpdate mOnAccountUpdate = null; + private OnCaptchaRequested mOnCaptchaRequested = null; + private int accountChangedListenerCount = 0; + private int captchaRequestedListenerCount = 0; + private OnRosterUpdate mOnRosterUpdate = null; + private OnUpdateBlocklist mOnUpdateBlocklist = null; + private int updateBlocklistListenerCount = 0; + private int rosterChangedListenerCount = 0; + private OnMucRosterUpdate mOnMucRosterUpdate = null; + private int mucRosterChangedListenerCount = 0; + private OnKeyStatusUpdated mOnKeyStatusUpdated = null; + private int keyStatusUpdatedListenerCount = 0; + private SecureRandom mRandom; + private final OnBindListener mOnBindListener = new OnBindListener() { + + @Override + public void onBind(final Account account) { + account.getRoster().clearPresences(); + mJingleConnectionManager.cancelInTransmission(); + fetchRosterFromServer(account); + fetchBookmarks(account); + sendPresence(account); + mMessageArchiveService.executePendingQueries(account); + connectMultiModeConversations(account); + syncDirtyContacts(account); + } + }; private OnStatusChanged statusListener = new OnStatusChanged() { @Override @@ -245,14 +283,15 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa mOnAccountUpdate.onAccountUpdate(); } if (account.getStatus() == Account.State.ONLINE) { - for (Conversation conversation : account.pendingConferenceLeaves) { - leaveMuc(conversation); - } - for (Conversation conversation : account.pendingConferenceJoins) { - joinMuc(conversation); + if (connection != null && connection.getFeatures().csi()) { + if (checkListeners()) { + Logging.d(Config.LOGTAG, account.getJid().toBareJid() + " sending csi//inactive"); + connection.sendInactive(); + } else { + Logging.d(Config.LOGTAG, account.getJid().toBareJid() + " sending csi//active"); + connection.sendActive(); + } } - mMessageArchiveService.executePendingQueries(account); - mJingleConnectionManager.cancelInTransmission(); List<Conversation> conversations = getConversations(); for (Conversation conversation : conversations) { if (conversation.getAccount() == account) { @@ -260,28 +299,24 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa sendUnsentMessages(conversation); } } - if (connection != null && connection.getFeatures().csi()) { - if (checkListeners()) { - Logging.d(Config.LOGTAG, account.getJid().toBareJid() - + " sending csi//inactive"); - connection.sendInactive(); - } else { - Logging.d(Config.LOGTAG, account.getJid().toBareJid() - + " sending csi//active"); - connection.sendActive(); - } + for (Conversation conversation : account.pendingConferenceLeaves) { + leaveMuc(conversation); } - syncDirtyContacts(account); - scheduleWakeUpCall(Config.PING_MAX_INTERVAL,account.getUuid().hashCode()); + account.pendingConferenceLeaves.clear(); + for (Conversation conversation : account.pendingConferenceJoins) { + joinMuc(conversation); + } + account.pendingConferenceJoins.clear(); + scheduleWakeUpCall(Config.PING_MAX_INTERVAL, account.getUuid().hashCode()); } else if (account.getStatus() == Account.State.OFFLINE) { resetSendingToWaiting(account); if (!account.isOptionSet(Account.OPTION_DISABLED)) { - int timeToReconnect = mRandom.nextInt(50) + 10; - scheduleWakeUpCall(timeToReconnect,account.getUuid().hashCode()); + int timeToReconnect = mRandom.nextInt(20) + 10; + scheduleWakeUpCall(timeToReconnect, account.getUuid().hashCode()); } } else if (account.getStatus() == Account.State.REGISTRATION_SUCCESSFUL) { databaseBackend.updateAccount(account); - reconnectAccount(account, true); + reconnectAccount(account, true, false); } else if ((account.getStatus() != Account.State.CONNECTING) && (account.getStatus() != Account.State.NO_INTERNET)) { if (connection != null) { @@ -290,27 +325,26 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa + ": error connecting account. try again in " + next + "s for the " + (connection.getAttempt() + 1) + " time"); - scheduleWakeUpCall(next,account.getUuid().hashCode()); + scheduleWakeUpCall(next, account.getUuid().hashCode()); } - } + } getNotificationService().updateErrorNotification(); } }; - private int accountChangedListenerCount = 0; - private OnRosterUpdate mOnRosterUpdate = null; - private OnUpdateBlocklist mOnUpdateBlocklist = null; - private int updateBlocklistListenerCount = 0; - private int rosterChangedListenerCount = 0; - private OnMucRosterUpdate mOnMucRosterUpdate = null; - private int mucRosterChangedListenerCount = 0; - private SecureRandom mRandom; private OpenPgpServiceConnection pgpServiceConnection; private PgpEngine mPgpEngine = null; private WakeLock wakeLock; private PowerManager pm; + private LruCache<String, Bitmap> mBitmapCache; private Thread mPhoneContactMergerThread; + private EventReceiver mEventReceiver = new EventReceiver(); private boolean mRestoredFromDatabase = false; + + private static String generateFetchKey(Account account, final Avatar avatar) { + return account.getJid().toBareJid() + "_" + avatar.owner + "_" + avatar.sha1sum; + } + public boolean areMessagesInitialized() { return this.mRestoredFromDatabase; } @@ -319,8 +353,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa if (pgpServiceConnection.isBound()) { if (this.mPgpEngine == null) { this.mPgpEngine = new PgpEngine(new OpenPgpApi( - getApplicationContext(), - pgpServiceConnection.getService()), this); + getApplicationContext(), + pgpServiceConnection.getService()), this); } return mPgpEngine; } else { @@ -329,14 +363,22 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } + public FileBackend getFileBackend() { + return this.fileBackend; + } + + public AvatarService getAvatarService() { + return this.mAvatarService; + } + public void attachLocationToConversation(final Conversation conversation, final Uri uri, final UiCallback<Message> callback) { - int encryption = conversation.getNextEncryption(ConversationsPlusPreferences.forceEncryption()); + int encryption = conversation.getNextEncryption(); if (encryption == Message.ENCRYPTION_PGP) { encryption = Message.ENCRYPTION_DECRYPTED; } - Message message = new Message(conversation,uri.toString(),encryption); + Message message = new Message(conversation, uri.toString(), encryption); if (conversation.getNextCounterpart() != null) { message.setCounterpart(conversation.getNextCounterpart()); } @@ -348,16 +390,13 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } public void attachFileToConversation(final Conversation conversation, - final Uri uri, - final UiCallback<Message> callback) { + final Uri uri, + final UiCallback<Message> callback) { final Message message; - boolean forceEncryption = ConversationsPlusPreferences.forceEncryption(); - if (conversation.getNextEncryption(forceEncryption) == Message.ENCRYPTION_PGP) { - message = new Message(conversation, "", - Message.ENCRYPTION_DECRYPTED); + if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) { + message = new Message(conversation, "", Message.ENCRYPTION_DECRYPTED); } else { - message = new Message(conversation, "", - conversation.getNextEncryption(forceEncryption)); + message = new Message(conversation, "", conversation.getNextEncryption()); } message.setCounterpart(conversation.getNextCounterpart()); message.setType(Message.TYPE_FILE); @@ -390,7 +429,41 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } - public Conversation find(Bookmark bookmark) { + public void attachImageToConversation(final Conversation conversation, final Uri uri, final UiCallback<Message> callback) { + final String compressPictures = getCompressPicturesPreference(); + if ("never".equals(compressPictures) + || ("auto".equals(compressPictures) && getFileBackend().useImageAsIs(uri))) { + Log.d(Config.LOGTAG,conversation.getAccount().getJid().toBareJid()+ ": not compressing picture. sending as file"); + attachFileToConversation(conversation, uri, callback); + return; + } + final Message message; + if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) { + message = new Message(conversation, "", Message.ENCRYPTION_DECRYPTED); + } else { + message = new Message(conversation, "", conversation.getNextEncryption()); + } + message.setCounterpart(conversation.getNextCounterpart()); + message.setType(Message.TYPE_IMAGE); + mFileAddingExecutor.execute(new Runnable() { + + @Override + public void run() { + try { + getFileBackend().copyImageToPrivateStorage(message, uri); + if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) { + getPgpEngine().encrypt(message, callback); + } else { + callback.success(message); + } + } catch (final FileBackend.FileCopyException e) { + callback.error(e.getResId(), message); + } + } + }); + } + + public Conversation find(Bookmark bookmark) { return find(bookmark.getAccount(), bookmark.getJid()); } @@ -401,6 +474,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa @Override public int onStartCommand(Intent intent, int flags, int startId) { final String action = intent == null ? null : intent.getAction(); + boolean interactive = false; if (action != null) { switch (action) { case ConnectivityManager.CONNECTIVITY_ACTION: @@ -410,9 +484,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa break; case ACTION_MERGE_PHONE_CONTACTS: if (mRestoredFromDatabase) { - PhoneHelper.loadPhoneContacts(getApplicationContext(), - new CopyOnWriteArrayList<Bundle>(), - this); + loadPhoneContacts(); } return START_STICKY; case Intent.ACTION_SHUTDOWN: @@ -427,19 +499,31 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa break; case ACTION_TRY_AGAIN: resetAllAttemptCounts(false); + interactive = true; break; case ACTION_DISABLE_ACCOUNT: try { String jid = intent.getStringExtra("account"); Account account = jid == null ? null : findAccountByJid(Jid.fromString(jid)); if (account != null) { - account.setOption(Account.OPTION_DISABLED,true); + account.setOption(Account.OPTION_DISABLED, true); updateAccount(account); } } catch (final InvalidJidException ignored) { break; } break; + case AudioManager.RINGER_MODE_CHANGED_ACTION: + if (xaOnSilentMode()) { + refreshAllPresences(); + } + break; + case Intent.ACTION_SCREEN_OFF: + case Intent.ACTION_SCREEN_ON: + if (awayWhenScreenOff()) { + refreshAllPresences(); + } + break; } } this.wakeLock.acquire(); @@ -462,36 +546,42 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa long lastReceived = account.getXmppConnection().getLastPacketReceived(); long lastSent = account.getXmppConnection().getLastPingSent(); long pingInterval = "ui".equals(action) ? Config.PING_MIN_INTERVAL * 1000 : Config.PING_MAX_INTERVAL * 1000; - long msToNextPing = (Math.max(lastReceived,lastSent) + pingInterval) - SystemClock.elapsedRealtime(); + long msToNextPing = (Math.max(lastReceived, lastSent) + pingInterval) - SystemClock.elapsedRealtime(); long pingTimeoutIn = (lastSent + Config.PING_TIMEOUT * 1000) - SystemClock.elapsedRealtime(); if (lastSent > lastReceived) { if (pingTimeoutIn < 0) { Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": ping timeout"); - this.reconnectAccount(account, true); + this.reconnectAccount(account, true, interactive); } else { int secs = (int) (pingTimeoutIn / 1000); - this.scheduleWakeUpCall(secs,account.getUuid().hashCode()); + this.scheduleWakeUpCall(secs, account.getUuid().hashCode()); } } else if (msToNextPing <= 0) { account.getXmppConnection().sendPing(); - Logging.d(Config.LOGTAG, account.getJid().toBareJid()+" send ping"); - this.scheduleWakeUpCall(Config.PING_TIMEOUT,account.getUuid().hashCode()); + Logging.d(Config.LOGTAG, account.getJid().toBareJid() + " send ping"); + this.scheduleWakeUpCall(Config.PING_TIMEOUT, account.getUuid().hashCode()); } else { this.scheduleWakeUpCall((int) (msToNextPing / 1000), account.getUuid().hashCode()); } } else if (account.getStatus() == Account.State.OFFLINE) { - reconnectAccount(account,true); + reconnectAccount(account, true, interactive); } else if (account.getStatus() == Account.State.CONNECTING) { - long timeout = Config.CONNECT_TIMEOUT - ((SystemClock.elapsedRealtime() - account.getXmppConnection().getLastConnect()) / 1000); + long secondsSinceLastConnect = (SystemClock.elapsedRealtime() - account.getXmppConnection().getLastConnect()) / 1000; + long secondsSinceLastDisco = (SystemClock.elapsedRealtime() - account.getXmppConnection().getLastDiscoStarted()) / 1000; + long discoTimeout = Config.CONNECT_DISCO_TIMEOUT - secondsSinceLastDisco; + long timeout = Config.CONNECT_TIMEOUT - secondsSinceLastConnect; if (timeout < 0) { Logging.d(Config.LOGTAG, account.getJid() + ": time out during connect reconnecting"); - reconnectAccount(account, true); + reconnectAccount(account, true, interactive); + } else if (discoTimeout < 0) { + account.getXmppConnection().sendDiscoTimeout(); + scheduleWakeUpCall((int) Math.min(timeout,discoTimeout), account.getUuid().hashCode()); } else { - scheduleWakeUpCall((int) timeout,account.getUuid().hashCode()); + scheduleWakeUpCall((int) Math.min(timeout,discoTimeout), account.getUuid().hashCode()); } } else { if (account.getXmppConnection().getTimeToNextAttempt() <= 0) { - reconnectAccount(account, true); + reconnectAccount(account, true, interactive); } } @@ -501,10 +591,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } } - /*PowerManager pm = (PowerManager) this.getSystemService(Context.POWER_SERVICE); - if (!pm.isScreenOn()) { - removeStaleListeners(); - }*/ if (wakeLock.isHeld()) { try { wakeLock.release(); @@ -514,9 +600,50 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa return START_STICKY; } + private boolean xaOnSilentMode() { + return getPreferences().getBoolean("xa_on_silent_mode", false); + } + + private boolean awayWhenScreenOff() { + return getPreferences().getBoolean("away_when_screen_off", false); + } + + private String getCompressPicturesPreference() { + return getPreferences().getString("picture_compression", "auto"); + } + + private int getTargetPresence() { + if (xaOnSilentMode() && isPhoneSilenced()) { + return Presences.XA; + } else if (awayWhenScreenOff() && !isInteractive()) { + return Presences.AWAY; + } else { + return Presences.ONLINE; + } + } + + @SuppressLint("NewApi") + @SuppressWarnings("deprecation") + public boolean isInteractive() { + final PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); + + final boolean isScreenOn; + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + isScreenOn = pm.isScreenOn(); + } else { + isScreenOn = pm.isInteractive(); + } + return isScreenOn; + } + + private boolean isPhoneSilenced() { + AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); + return audioManager.getRingerMode() == AudioManager.RINGER_MODE_SILENT; + } + private void resetAllAttemptCounts(boolean reallyAll) { Logging.d(Config.LOGTAG, "resetting all attepmt counts"); - for(Account account : accounts) { + for (Account account : accounts) { if (account.hasErrorStatus() || reallyAll) { final XmppConnection connection = account.getXmppConnection(); if (connection != null) { @@ -528,7 +655,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa public boolean hasInternetConnection() { ConnectivityManager cm = (ConnectivityManager) getApplicationContext() - .getSystemService(Context.CONNECTIVITY_SERVICE); + .getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); return activeNetwork != null && activeNetwork.isConnected(); } @@ -559,25 +686,77 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa PRNGFixes.apply(); this.mRandom = new SecureRandom(); updateMemorizingTrustmanager(); + final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); + final int cacheSize = maxMemory / 8; + this.mBitmapCache = new LruCache<String, Bitmap>(cacheSize) { + @Override + protected int sizeOf(final String key, final Bitmap bitmap) { + return bitmap.getByteCount() / 1024; + } + }; this.databaseBackend = DatabaseBackend.getInstance(getApplicationContext()); this.accounts = databaseBackend.getAccounts(); - for (final Account account : this.accounts) { - account.initAccountServices(this); - } restoreFromDatabase(); getContentResolver().registerContentObserver(ContactsContract.Contacts.CONTENT_URI, true, contactObserver); this.fileObserver.startWatching(); - this.pgpServiceConnection = new OpenPgpServiceConnection(getApplicationContext(), "org.sufficientlysecure.keychain"); + + this.pgpServiceConnection = new OpenPgpServiceConnection(getApplicationContext(), "org.sufficientlysecure.keychain", new OpenPgpServiceConnection.OnBound() { + @Override + public void onBound(IOpenPgpService2 service) { + for (Account account : accounts) { + if (account.getPgpDecryptionService() != null) { + account.getPgpDecryptionService().onOpenPgpServiceBound(); + } + } + } + + @Override + public void onError(Exception e) { } + }); this.pgpServiceConnection.bindToService(); this.pm = (PowerManager) getSystemService(Context.POWER_SERVICE); - this.wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,"XmppConnectionService"); + this.wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "XmppConnectionService"); toggleForegroundService(); updateUnreadCountBadge(); UiUpdateHelper.initXmppConnectionService(this); + toggleScreenEventReceiver(); + } + + @Override + public void onTrimMemory(int level) { + super.onTrimMemory(level); + if (level >= TRIM_MEMORY_COMPLETE) { + Log.d(Config.LOGTAG, "clear cache due to low memory"); + getBitmapCache().evictAll(); + } + } + + @Override + public void onDestroy() { + try { + unregisterReceiver(this.mEventReceiver); + } catch (IllegalArgumentException e) { + //ignored + } + super.onDestroy(); + } + + public void toggleScreenEventReceiver() { + if (awayWhenScreenOff()) { + final IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON); + filter.addAction(Intent.ACTION_SCREEN_OFF); + registerReceiver(this.mEventReceiver, filter); + } else { + try { + unregisterReceiver(this.mEventReceiver); + } catch (IllegalArgumentException e) { + //ignored + } + } } public void toggleForegroundService() { @@ -600,7 +779,13 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa for (final Account account : accounts) { databaseBackend.writeRoster(account.getRoster()); if (account.getXmppConnection() != null) { - disconnect(account, false); + new Thread(new Runnable() { + @Override + public void run() { + disconnect(account, false); + } + }).start(); + } } Context context = getApplicationContext(); @@ -612,7 +797,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa stopSelf(); } - protected void scheduleWakeUpCall(int seconds, int requestCode) { + public void scheduleWakeUpCall(int seconds, int requestCode) { final long timeToWake = SystemClock.elapsedRealtime() + (seconds < 0 ? 1 : seconds + 1) * 1000; Context context = getApplicationContext(); @@ -635,6 +820,10 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa connection.setOnBindListener(this.mOnBindListener); connection.setOnMessageAcknowledgeListener(this.mOnMessageAcknowledgedListener); connection.addOnAdvancedStreamFeaturesAvailableListener(this.mMessageArchiveService); + AxolotlService axolotlService = account.getAxolotlService(); + if (axolotlService != null) { + connection.addOnAdvancedStreamFeaturesAvailableListener(axolotlService); + } return connection; } @@ -645,37 +834,40 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } - private void sendFileMessage(final Message message) { + private void sendFileMessage(final Message message, final boolean delay) { Logging.d(Config.LOGTAG, "send file message"); final Account account = message.getConversation().getAccount(); final XmppConnection connection = account.getXmppConnection(); if (connection != null && connection.getFeatures().httpUpload()) { - mHttpConnectionManager.createNewUploadConnection(message); + mHttpConnectionManager.createNewUploadConnection(message, delay); } else { mJingleConnectionManager.createNewConnection(message); } } public void sendMessage(final Message message) { - sendMessage(message, false); + sendMessage(message, false, false); } - private void sendMessage(final Message message, final boolean resend) { + private void sendMessage(final Message message, final boolean resend, final boolean delay) { final Account account = message.getConversation().getAccount(); final Conversation conversation = message.getConversation(); account.deactivateGracePeriod(); MessagePacket packet = null; - boolean saveInDb = true; + final boolean addToConversation = conversation.getMode() != Conversation.MODE_MULTI + || account.getServerIdentity() != XmppConnection.Identity.SLACK; + boolean saveInDb = addToConversation; message.setStatus(Message.STATUS_WAITING); if (!resend && message.getEncryption() != Message.ENCRYPTION_OTR) { message.getConversation().endOtrIfNeeded(); - message.getConversation().findUnsentMessagesWithOtrEncryption(new Conversation.OnMessageFound() { - @Override - public void onMessageFound(Message message) { - markMessage(message,Message.STATUS_SEND_FAILED); - } - }); + message.getConversation().findUnsentMessagesWithEncryption(Message.ENCRYPTION_OTR, + new Conversation.OnMessageFound() { + @Override + public void onMessageFound(Message message) { + markMessage(message, Message.STATUS_SEND_FAILED); + } + }); } if (account.isOnlineAndConnected()) { @@ -683,24 +875,24 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa case Message.ENCRYPTION_NONE: if (message.needsUploading()) { if (account.httpUploadAvailable() || message.fixCounterpart()) { - this.sendFileMessage(message); + this.sendFileMessage(message, delay); } else { break; } } else { - packet = mMessageGenerator.generateChat(message,resend); + packet = mMessageGenerator.generateChat(message); } break; case Message.ENCRYPTION_PGP: case Message.ENCRYPTION_DECRYPTED: if (message.needsUploading()) { if (account.httpUploadAvailable() || message.fixCounterpart()) { - this.sendFileMessage(message); + this.sendFileMessage(message, delay); } else { break; } } else { - packet = mMessageGenerator.generatePgpChat(message,resend); + packet = mMessageGenerator.generatePgpChat(message); } break; case Message.ENCRYPTION_OTR: @@ -714,7 +906,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa if (message.needsUploading()) { mJingleConnectionManager.createNewConnection(message); } else { - packet = mMessageGenerator.generateOtrChat(message,resend); + packet = mMessageGenerator.generateOtrChat(message); } } else if (otrSession == null) { if (message.fixCounterpart()) { @@ -724,6 +916,24 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } break; + case Message.ENCRYPTION_AXOLOTL: + message.setAxolotlFingerprint(account.getAxolotlService().getOwnFingerprint()); + if (message.needsUploading()) { + if (account.httpUploadAvailable() || message.fixCounterpart()) { + this.sendFileMessage(message, delay); + } else { + break; + } + } else { + XmppAxolotlMessage axolotlMessage = account.getAxolotlService().fetchAxolotlMessageFromCache(message); + if (axolotlMessage == null) { + account.getAxolotlService().preparePayloadMessage(message, delay); + } else { + packet = mMessageGenerator.generateAxolotlChat(message, axolotlMessage); + } + } + break; + } if (packet != null) { if (account.getXmppConnection().getFeatures().sm() || conversation.getMode() == Conversation.MODE_MULTI) { @@ -733,7 +943,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } } else { - switch(message.getEncryption()) { + switch (message.getEncryption()) { case Message.ENCRYPTION_DECRYPTED: if (!message.needsUploading()) { String pgpBody = message.getEncryptedBody(); @@ -751,25 +961,33 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa conversation.startOtrSession(message.getCounterpart().getResourcepart(), false); } break; + case Message.ENCRYPTION_AXOLOTL: + message.setAxolotlFingerprint(account.getAxolotlService().getOwnFingerprint()); + break; } } if (resend) { - if (packet != null) { + if (packet != null && addToConversation) { if (account.getXmppConnection().getFeatures().sm() || conversation.getMode() == Conversation.MODE_MULTI) { - markMessage(message,Message.STATUS_UNSEND); + markMessage(message, Message.STATUS_UNSEND); } else { - markMessage(message,Message.STATUS_SEND); + markMessage(message, Message.STATUS_SEND); } } } else { - conversation.add(message); - if (saveInDb && (message.getEncryption() == Message.ENCRYPTION_NONE || !ConversationsPlusPreferences.dontSaveEncrypted())) { + if (addToConversation) { + conversation.add(message); + } + if (saveInDb && (message.getEncryption() == Message.ENCRYPTION_NONE || saveEncryptedMessages())) { databaseBackend.createMessage(message); } updateConversationUi(); } if (packet != null) { + if (delay) { + mMessageGenerator.addDelay(packet, message.getTimeSent()); + } if (conversation.setOutgoingChatState(Config.DEFAULT_CHATSTATE)) { if (ConversationsPlusPreferences.chatStates()) { packet.addChild(ChatState.toElement(conversation.getOutgoingChatState())); @@ -784,13 +1002,13 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa @Override public void onMessageFound(Message message) { - resendMessage(message); + resendMessage(message, true); } }); } - public void resendMessage(final Message message) { - sendMessage(message, true); + public void resendMessage(final Message message, final boolean delay) { + sendMessage(message, true, delay); } public void fetchRosterFromServer(final Account account) { @@ -813,34 +1031,42 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa @Override public void onIqPacketReceived(final Account account, final IqPacket packet) { - final Element query = packet.query(); - final List<Bookmark> bookmarks = new CopyOnWriteArrayList<>(); - final Element storage = query.findChild("storage", - "storage:bookmarks"); - if (storage != null) { - for (final Element item : storage.getChildren()) { - if (item.getName().equals("conference")) { - final Bookmark bookmark = Bookmark.parse(item, account); - bookmarks.add(bookmark); - Conversation conversation = find(bookmark); - if (conversation != null) { - conversation.setBookmark(bookmark); - } else if (bookmark.autojoin() && bookmark.getJid() != null) { - conversation = findOrCreateConversation( - account, bookmark.getJid(), true); - conversation.setBookmark(bookmark); - joinMuc(conversation); + if (packet.getType() == IqPacket.TYPE.RESULT) { + final Element query = packet.query(); + final HashMap<Jid, Bookmark> bookmarks = new HashMap<>(); + final Element storage = query.findChild("storage", "storage:bookmarks"); + final boolean autojoin = respectAutojoin(); + if (storage != null) { + for (final Element item : storage.getChildren()) { + if (item.getName().equals("conference")) { + final Bookmark bookmark = Bookmark.parse(item, account); + Bookmark old = bookmarks.put(bookmark.getJid(), bookmark); + if (old != null && old.getBookmarkName() != null && bookmark.getBookmarkName() == null) { + bookmark.setBookmarkName(old.getBookmarkName()); + } + Conversation conversation = find(bookmark); + if (conversation != null) { + conversation.setBookmark(bookmark); + } else if (bookmark.autojoin() && bookmark.getJid() != null && autojoin) { + conversation = findOrCreateConversation( + account, bookmark.getJid(), true); + conversation.setBookmark(bookmark); + joinMuc(conversation); + } } } } + account.setBookmarks(new ArrayList<>(bookmarks.values())); + } else { + Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not fetch bookmarks"); } - account.setBookmarks(bookmarks); } }; sendIqPacket(account, iqPacket, callback); } public void pushBookmarks(Account account) { + Logging.d(Config.LOGTAG, account.getJid().toBareJid()+": pushing bookmarks"); IqPacket iqPacket = new IqPacket(IqPacket.TYPE.SET); Element query = iqPacket.query("jabber:iq:private"); Element storage = query.addChild("storage", "storage:bookmarks"); @@ -857,7 +1083,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa mPhoneContactMergerThread = new Thread(new Runnable() { @Override public void run() { - Logging.d(Config.LOGTAG,"start merging phone contacts with roster"); + Logging.d(Config.LOGTAG, "start merging phone contacts with roster"); for (Account account : accounts) { List<Contact> withSystemAccounts = account.getRoster().getWithSystemAccounts(); for (Bundle phoneContact : phoneContacts) { @@ -873,8 +1099,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } final Contact contact = account.getRoster().getContact(jid); String systemAccount = phoneContact.getInt("phoneid") - + "#" - + phoneContact.getString("lookup"); + + "#" + + phoneContact.getString("lookup"); contact.setSystemAccount(systemAccount); if (contact.setPhotoUri(phoneContact.getString("photouri"))) { AvatarService.getInstance().clear(contact); @@ -882,7 +1108,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa contact.setSystemName(phoneContact.getString("displayname")); withSystemAccounts.remove(contact); } - for(Contact contact : withSystemAccounts) { + for (Contact contact : withSystemAccounts) { contact.setSystemAccount(null); contact.setSystemName(null); if (contact.setPhotoUri(null)) { @@ -908,23 +1134,29 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa Account account = accountLookupTable.get(conversation.getAccountUuid()); conversation.setAccount(account); } - Runnable runnable =new Runnable() { + Runnable runnable = new Runnable() { @Override public void run() { - Logging.d(Config.LOGTAG,"restoring roster"); - for(Account account : accounts) { + Logging.d(Config.LOGTAG, "restoring roster"); + for (Account account : accounts) { databaseBackend.readRoster(account.getRoster()); + account.initAccountServices(XmppConnectionService.this); //roster needs to be loaded at this stage } ImageUtil.evictBitmapCache(); Looper.prepare(); - PhoneHelper.loadPhoneContacts(getApplicationContext(), - new CopyOnWriteArrayList<Bundle>(), - XmppConnectionService.this); - Logging.d(Config.LOGTAG,"restoring messages"); + loadPhoneContacts(); + Logging.d(Config.LOGTAG, "restoring messages"); for (Conversation conversation : conversations) { conversation.addAll(0, databaseBackend.getMessages(conversation, Config.PAGE_SIZE)); checkDeletedFiles(conversation); + conversation.findUnreadMessages(new Conversation.OnMessageFound() { + @Override + public void onMessageFound(Message message) { + mNotificationService.pushFromBacklog(message); + } + }); } + mNotificationService.finishBacklog(false); mRestoredFromDatabase = true; Logging.d(Config.LOGTAG,"restored all messages"); updateConversationUi(); @@ -934,6 +1166,12 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } + public void loadPhoneContacts() { + PhoneHelper.loadPhoneContacts(getApplicationContext(), + new CopyOnWriteArrayList<Bundle>(), + XmppConnectionService.this); + } + public List<Conversation> getConversations() { return this.conversations; } @@ -945,6 +1183,10 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa public void onMessageFound(Message message) { if (!FileBackend.isFileAvailable(message)) { message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED)); + final int s = message.getStatus(); + if (s == Message.STATUS_WAITING || s == Message.STATUS_OFFERED || s == Message.STATUS_UNSEND) { + markMessage(message, Message.STATUS_SEND_FAILED); + } } } }); @@ -956,7 +1198,12 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa if (message != null) { if (!FileBackend.isFileAvailable(message)) { message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED)); - updateConversationUi(); + final int s = message.getStatus(); + if (s == Message.STATUS_WAITING || s == Message.STATUS_OFFERED || s == Message.STATUS_UNSEND) { + markMessage(message, Message.STATUS_SEND_FAILED); + } else { + updateConversationUi(); + } } return; } @@ -996,12 +1243,12 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } public void loadMoreMessages(final Conversation conversation, final long timestamp, final OnMoreMessagesLoaded callback) { - Logging.d(Config.LOGTAG, "load more messages for " + conversation.getName() + " prior to " + MessageGenerator.getTimestamp(timestamp)); - if (XmppConnectionService.this.getMessageArchiveService().queryInProgress(conversation,callback)) { + if (XmppConnectionService.this.getMessageArchiveService().queryInProgress(conversation, callback)) { Logging.d("mam", "Query in progress"); return; } //TODO Create a separate class for this runnable to store if messages are getting loaded or not. Not really a good idea to do this in the callback. + Logging.d(Config.LOGTAG, "load more messages for " + conversation.getName() + " prior to " + MessageGenerator.getTimestamp(timestamp)); Runnable runnable = new Runnable() { @Override public void run() { @@ -1009,30 +1256,32 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa if (null != callback) { callback.setLoadingInProgress(); // Tell the callback that the loading is in progress } - final Account account = conversation.getAccount(); - List<Message> messages = databaseBackend.getMessages(conversation, 50, timestamp); + final Account account = conversation.getAccount(); + List<Message> messages = databaseBackend.getMessages(conversation, 50, timestamp); Logging.d("mam", "runnable load more messages"); - if (messages.size() > 0) { + if (messages.size() > 0) { Logging.d("mam", "At least one message"); - conversation.addAll(0, messages); - checkDeletedFiles(conversation); - callback.onMoreMessagesLoaded(messages.size(), conversation); - } else if (conversation.hasMessagesLeftOnServer() - && account.isOnlineAndConnected() - && account.getXmppConnection().getFeatures().mam()) { + conversation.addAll(0, messages); + checkDeletedFiles(conversation); + callback.onMoreMessagesLoaded(messages.size(), conversation); + } else if (conversation.hasMessagesLeftOnServer() + && account.isOnlineAndConnected()) { Logging.d("mam", "mam activate, account online and connected and messages left on server"); - MessageArchiveService.Query query = getMessageArchiveService().query(conversation, 0, timestamp - 1); - if (query != null) { - query.setCallback(callback); - } - callback.informUser(R.string.fetching_history_from_server); - } else { + if ((conversation.getMode() == Conversation.MODE_SINGLE && account.getXmppConnection().getFeatures().mam()) + || (conversation.getMode() == Conversation.MODE_MULTI && conversation.getMucOptions().mamSupport())) { + MessageArchiveService.Query query = getMessageArchiveService().query(conversation, 0, timestamp - 1); + if (query != null) { + query.setCallback(callback); + } + callback.informUser(R.string.fetching_history_from_server); + } + } else { Logging.d("mam", ((!conversation.hasMessagesLeftOnServer()) ? "no" : "") + " more messages left on server, mam " + ((account.getXmppConnection().getFeatures().mam()) ? "" : "not") + " activated, account is " + ((account.isOnlineAndConnected()) ? "" : "not") + " online or connected)"); callback.onMoreMessagesLoaded(0, conversation); callback.informUser(R.string.no_more_history_on_server); } + } } - } }; ConversationsPlusApplication.executeDatabaseOperation(runnable); } @@ -1130,7 +1379,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa if (conversation.getMode() == Conversation.MODE_MULTI) { if (conversation.getAccount().getStatus() == Account.State.ONLINE) { Bookmark bookmark = conversation.getBookmark(); - if (bookmark != null && bookmark.autojoin()) { + if (bookmark != null && bookmark.autojoin() && respectAutojoin()) { bookmark.setAutojoin(false); pushBookmarks(bookmark.getAccount()); } @@ -1138,6 +1387,13 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa leaveMuc(conversation); } else { conversation.endOtrIfNeeded(); + if (conversation.getContact().getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) { + Log.d(Config.LOGTAG, "Canceling presence request from " + conversation.getJid().toString()); + sendPresencePacket( + conversation.getAccount(), + mPresenceGenerator.stopPresenceUpdatesTo(conversation.getContact()) + ); + } } this.databaseBackend.updateConversation(conversation); this.conversations.remove(conversation); @@ -1153,10 +1409,68 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa updateAccountUi(); } + public void createAccountFromKey(final String alias, final OnAccountCreated callback) { + new Thread(new Runnable() { + @Override + public void run() { + try { + X509Certificate[] chain = KeyChain.getCertificateChain(XmppConnectionService.this, alias); + Pair<Jid, String> info = CryptoHelper.extractJidAndName(chain[0]); + if (findAccountByJid(info.first) == null) { + Account account = new Account(info.first, ""); + account.setPrivateKeyAlias(alias); + account.setOption(Account.OPTION_DISABLED, true); + account.setDisplayName(info.second); + createAccount(account); + callback.onAccountCreated(account); + if (Config.X509_VERIFICATION) { + try { + getMemorizingTrustManager().getNonInteractive().checkClientTrusted(chain, "RSA"); + } catch (CertificateException e) { + callback.informUser(R.string.certificate_chain_is_not_trusted); + } + } + } else { + callback.informUser(R.string.account_already_exists); + } + } catch (Exception e) { + e.printStackTrace(); + callback.informUser(R.string.unable_to_parse_certificate); + } + } + }).start(); + + } + + public void updateKeyInAccount(final Account account, final String alias) { + Log.d(Config.LOGTAG, "update key in account " + alias); + try { + X509Certificate[] chain = KeyChain.getCertificateChain(XmppConnectionService.this, alias); + Pair<Jid, String> info = CryptoHelper.extractJidAndName(chain[0]); + if (account.getJid().toBareJid().equals(info.first)) { + account.setPrivateKeyAlias(alias); + account.setDisplayName(info.second); + databaseBackend.updateAccount(account); + if (Config.X509_VERIFICATION) { + try { + getMemorizingTrustManager().getNonInteractive().checkClientTrusted(chain, "RSA"); + } catch (CertificateException e) { + showErrorToastInUi(R.string.certificate_chain_is_not_trusted); + } + account.getAxolotlService().regenerateKeys(true); + } + } else { + showErrorToastInUi(R.string.jid_does_not_match_certificate); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + public void updateAccount(final Account account) { this.statusListener.onStatusChanged(account); databaseBackend.updateAccount(account); - reconnectAccount(account, false); + reconnectAccountInBackground(account); updateAccountUi(); getNotificationService().updateErrorNotification(); } @@ -1192,7 +1506,13 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa if (account.getXmppConnection() != null) { this.disconnect(account, true); } - databaseBackend.deleteAccount(account); + Runnable runnable = new Runnable() { + @Override + public void run() { + databaseBackend.deleteAccount(account); + } + }; + ConversationsPlusApplication.executeDatabaseOperation.execute(runnable); this.accounts.remove(account); updateAccountUi(); getNotificationService().updateErrorNotification(); @@ -1277,6 +1597,31 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } + public void setOnCaptchaRequestedListener(OnCaptchaRequested listener) { + synchronized (this) { + if (checkListeners()) { + switchToForeground(); + } + this.mOnCaptchaRequested = listener; + if (this.captchaRequestedListenerCount < 2) { + this.captchaRequestedListenerCount++; + } + } + } + + public void removeOnCaptchaRequestedListener() { + synchronized (this) { + this.captchaRequestedListenerCount--; + if (this.captchaRequestedListenerCount <= 0) { + this.mOnCaptchaRequested = null; + this.captchaRequestedListenerCount = 0; + if (checkListeners()) { + switchToBackground(); + } + } + } + } + public void setOnRosterUpdateListener(final OnRosterUpdate listener) { synchronized (this) { if (checkListeners()) { @@ -1327,6 +1672,31 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } + public void setOnKeyStatusUpdatedListener(final OnKeyStatusUpdated listener) { + synchronized (this) { + if (checkListeners()) { + switchToForeground(); + } + this.mOnKeyStatusUpdated = listener; + if (this.keyStatusUpdatedListenerCount < 2) { + this.keyStatusUpdatedListenerCount++; + } + } + } + + public void removeOnNewKeysAvailableListener() { + synchronized (this) { + this.keyStatusUpdatedListenerCount--; + if (this.keyStatusUpdatedListenerCount <= 0) { + this.keyStatusUpdatedListenerCount = 0; + this.mOnKeyStatusUpdated = null; + if (checkListeners()) { + switchToBackground(); + } + } + } + } + public void setOnMucRosterUpdateListener(OnMucRosterUpdate listener) { synchronized (this) { if (checkListeners()) { @@ -1356,11 +1726,16 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa return (this.mOnAccountUpdate == null && this.mOnConversationUpdate == null && this.mOnRosterUpdate == null + && this.mOnCaptchaRequested == null && this.mOnUpdateBlocklist == null - && this.mOnShowErrorToast == null); + && this.mOnShowErrorToast == null + && this.mOnKeyStatusUpdated == null); } private void switchToForeground() { + for (Conversation conversation : getConversations()) { + conversation.setIncomingChatState(ChatState.ACTIVE); + } for (Account account : getAccounts()) { if (account.getStatus() == Account.State.ONLINE) { XmppConnection connection = account.getXmppConnection(); @@ -1381,9 +1756,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } } - for(Conversation conversation : getConversations()) { - conversation.setIncomingChatState(ChatState.ACTIVE); - } this.mNotificationService.setIsInForeground(false); Logging.d(Config.LOGTAG, "app switched into background"); } @@ -1391,45 +1763,74 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa private void connectMultiModeConversations(Account account) { List<Conversation> conversations = getConversations(); for (Conversation conversation : conversations) { - if ((conversation.getMode() == Conversation.MODE_MULTI) - && (conversation.getAccount() == account)) { - conversation.resetMucOptions(); - joinMuc(conversation); + if (conversation.getMode() == Conversation.MODE_MULTI && conversation.getAccount() == account) { + joinMuc(conversation, true, null); } } } public void joinMuc(Conversation conversation) { + joinMuc(conversation, false, null); + } + + private void joinMuc(Conversation conversation, boolean now, final OnConferenceJoined onConferenceJoined) { Account account = conversation.getAccount(); account.pendingConferenceJoins.remove(conversation); account.pendingConferenceLeaves.remove(conversation); - if (account.getStatus() == Account.State.ONLINE) { - final String nick = conversation.getMucOptions().getProposedNick(); - final Jid joinJid = conversation.getMucOptions().createJoinJid(nick); - if (joinJid == null) { - return; //safety net - } - Logging.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": joining conversation " + joinJid.toString()); - PresencePacket packet = new PresencePacket(); - packet.setFrom(conversation.getAccount().getJid()); - packet.setTo(joinJid); - Element x = packet.addChild("x", "http://jabber.org/protocol/muc"); - if (conversation.getMucOptions().getPassword() != null) { - x.addChild("password").setContent(conversation.getMucOptions().getPassword()); - } - x.addChild("history").setAttribute("since", PresenceGenerator.getTimestamp(conversation.getLastMessageTransmitted())); - String sig = account.getPgpSignature(); - if (sig != null) { - packet.addChild("status").setContent("online"); - packet.addChild("x", "jabber:x:signed").setContent(sig); - } - sendPresencePacket(account, packet); - fetchConferenceConfiguration(conversation); - if (!joinJid.equals(conversation.getJid())) { - conversation.setContactJid(joinJid); - databaseBackend.updateConversation(conversation); - } - conversation.setHasMessagesLeftOnServer(false); + if (account.getStatus() == Account.State.ONLINE || now) { + conversation.resetMucOptions(); + fetchConferenceConfiguration(conversation, new OnConferenceConfigurationFetched() { + + private void join(Conversation conversation) { + Account account = conversation.getAccount(); + final String nick = conversation.getMucOptions().getProposedNick(); + final Jid joinJid = conversation.getMucOptions().createJoinJid(nick); + Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": joining conversation " + joinJid.toString()); + PresencePacket packet = new PresencePacket(); + packet.setFrom(conversation.getAccount().getJid()); + packet.setTo(joinJid); + Element x = packet.addChild("x", "http://jabber.org/protocol/muc"); + if (conversation.getMucOptions().getPassword() != null) { + x.addChild("password").setContent(conversation.getMucOptions().getPassword()); + } + + if (conversation.getMucOptions().mamSupport()) { + // Use MAM instead of the limited muc history to get history + x.addChild("history").setAttribute("maxchars", "0"); + } else { + // Fallback to muc history + x.addChild("history").setAttribute("since", PresenceGenerator.getTimestamp(conversation.getLastMessageTransmitted())); + } + String sig = account.getPgpSignature(); + if (sig != null) { + packet.addChild("x", "jabber:x:signed").setContent(sig); + } + sendPresencePacket(account, packet); + if (onConferenceJoined != null) { + onConferenceJoined.onConferenceJoined(conversation); + } + if (!joinJid.equals(conversation.getJid())) { + conversation.setContactJid(joinJid); + databaseBackend.updateConversation(conversation); + } + conversation.setHasMessagesLeftOnServer(false); + if (conversation.getMucOptions().mamSupport()) { + getMessageArchiveService().catchupMUC(conversation); + } + } + + @Override + public void onConferenceConfigurationFetched(Conversation conversation) { + join(conversation); + } + + @Override + public void onFetchFailed(final Conversation conversation, Element error) { + join(conversation); + fetchConferenceConfiguration(conversation); + } + }); + } else { account.pendingConferenceJoins.add(conversation); } @@ -1439,7 +1840,9 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa if (conversation.getMode() == Conversation.MODE_MULTI) { conversation.getMucOptions().setPassword(password); if (conversation.getBookmark() != null) { - conversation.getBookmark().setAutojoin(true); + if (respectAutojoin()) { + conversation.getBookmark().setAutojoin(true); + } pushBookmarks(conversation.getAccount()); } databaseBackend.updateConversation(conversation); @@ -1497,10 +1900,14 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } public void leaveMuc(Conversation conversation) { + leaveMuc(conversation, false); + } + + private void leaveMuc(Conversation conversation, boolean now) { Account account = conversation.getAccount(); account.pendingConferenceJoins.remove(conversation); account.pendingConferenceLeaves.remove(conversation); - if (account.getStatus() == Account.State.ONLINE) { + if (account.getStatus() == Account.State.ONLINE || now) { PresencePacket packet = new PresencePacket(); packet.setTo(conversation.getJid()); packet.setFrom(conversation.getAccount().getJid()); @@ -1548,34 +1955,37 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa String name = new BigInteger(75, getRNG()).toString(32); Jid jid = Jid.fromParts(name, server, null); final Conversation conversation = findOrCreateConversation(account, jid, true); - joinMuc(conversation); - Bundle options = new Bundle(); - options.putString("muc#roomconfig_persistentroom", "1"); - options.putString("muc#roomconfig_membersonly", "1"); - options.putString("muc#roomconfig_publicroom", "0"); - options.putString("muc#roomconfig_whois", "anyone"); - pushConferenceConfiguration(conversation, options, new OnConferenceOptionsPushed() { + joinMuc(conversation, true, new OnConferenceJoined() { @Override - public void onPushSucceeded() { - for (Jid invite : jids) { - invite(conversation, invite); - } - if (account.countPresences() > 1) { - directInvite(conversation, account.getJid().toBareJid()); - } - if (callback != null) { - callback.success(conversation); - } - } + public void onConferenceJoined(final Conversation conversation) { + Bundle options = new Bundle(); + options.putString("muc#roomconfig_persistentroom", "1"); + options.putString("muc#roomconfig_membersonly", "1"); + options.putString("muc#roomconfig_publicroom", "0"); + options.putString("muc#roomconfig_whois", "anyone"); + pushConferenceConfiguration(conversation, options, new OnConferenceOptionsPushed() { + @Override + public void onPushSucceeded() { + for (Jid invite : jids) { + invite(conversation, invite); + } + if (account.countPresences() > 1) { + directInvite(conversation, account.getJid().toBareJid()); + } + if (callback != null) { + callback.success(conversation); + } + } - @Override - public void onPushFailed() { - if (callback != null) { - callback.error(R.string.conference_creation_failed, conversation); - } + @Override + public void onPushFailed() { + if (callback != null) { + callback.error(R.string.conference_creation_failed, conversation); + } + } + }); } }); - } catch (InvalidJidException e) { if (callback != null) { callback.error(R.string.conference_creation_failed, null); @@ -1589,15 +1999,20 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } public void fetchConferenceConfiguration(final Conversation conversation) { + fetchConferenceConfiguration(conversation, null); + } + + public void fetchConferenceConfiguration(final Conversation conversation, final OnConferenceConfigurationFetched callback) { IqPacket request = new IqPacket(IqPacket.TYPE.GET); request.setTo(conversation.getJid().toBareJid()); request.query("http://jabber.org/protocol/disco#info"); sendIqPacket(conversation.getAccount(), request, new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() != IqPacket.TYPE.ERROR) { + if (packet.getType() == IqPacket.TYPE.RESULT) { ArrayList<String> features = new ArrayList<>(); - for (Element child : packet.query().getChildren()) { + Element query = packet.query(); + for (Element child : query.getChildren()) { if (child != null && child.getName().equals("feature")) { String var = child.getAttribute("var"); if (var != null) { @@ -1605,8 +2020,19 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } } + Element form = query.findChild("x", "jabber:x:data"); + if (form != null) { + conversation.getMucOptions().updateFormData(Data.parse(form)); + } conversation.getMucOptions().updateFeatures(features); + if (callback != null) { + callback.onConferenceConfigurationFetched(conversation); + } updateConversationUi(); + } else if (packet.getType() == IqPacket.TYPE.ERROR) { + if (callback != null) { + callback.onFetchFailed(conversation, packet.getError()); + } } } }); @@ -1619,11 +2045,11 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa sendIqPacket(conversation.getAccount(), request, new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() != IqPacket.TYPE.ERROR) { + if (packet.getType() == IqPacket.TYPE.RESULT) { Data data = Data.parse(packet.query().findChild("x", "jabber:x:data")); for (Field field : data.getFields()) { - if (options.containsKey(field.getName())) { - field.setValue(options.getString(field.getName())); + if (options.containsKey(field.getFieldName())) { + field.setValue(options.getString(field.getFieldName())); } } data.submit(); @@ -1633,12 +2059,10 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa sendIqPacket(account, set, new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.RESULT) { - if (callback != null) { + if (callback != null) { + if (packet.getType() == IqPacket.TYPE.RESULT) { callback.onPushSucceeded(); - } - } else { - if (callback != null) { + } else { callback.onPushFailed(); } } @@ -1707,7 +2131,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa }); } - public void disconnect(Account account, boolean force) { + private void disconnect(Account account, boolean force) { if ((account.getStatus() == Account.State.ONLINE) || (account.getStatus() == Account.State.DISABLED)) { if (!force) { @@ -1715,7 +2139,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa for (Conversation conversation : conversations) { if (conversation.getAccount() == account) { if (conversation.getMode() == Conversation.MODE_MULTI) { - leaveMuc(conversation); + leaveMuc(conversation, true); } else { if (conversation.endOtrIfNeeded()) { Logging.d(Config.LOGTAG, account.getJid().toBareJid() @@ -1767,7 +2191,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa account.getJid().toBareJid() + " otr session established with " + conversation.getJid() + "/" + otrSession.getSessionID().getUserID()); - conversation.findUnsentMessagesWithOtrEncryption(new Conversation.OnMessageFound() { + conversation.findUnsentMessagesWithEncryption(Message.ENCRYPTION_OTR, new Conversation.OnMessageFound() { @Override public void onMessageFound(Message message) { @@ -1780,8 +2204,9 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa if (message.needsUploading()) { mJingleConnectionManager.createNewConnection(message); } else { - MessagePacket outPacket = mMessageGenerator.generateOtrChat(message, true); + MessagePacket outPacket = mMessageGenerator.generateOtrChat(message); if (outPacket != null) { + mMessageGenerator.addDelay(outPacket, message.getTimeSent()); message.setStatus(Message.STATUS_SEND); databaseBackend.updateMessage(message); sendMessagePacket(account, outPacket); @@ -1801,8 +2226,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa MessagePacket packet = new MessagePacket(); packet.setType(MessagePacket.TYPE_CHAT); packet.setFrom(account.getJid()); - packet.addChild("private", "urn:xmpp:carbons:2"); - packet.addChild("no-copy", "urn:xmpp:hints"); + MessageGenerator.addMessageHints(packet); packet.setAttribute("to", otrSession.getSessionID().getAccountID() + "/" + otrSession.getSessionID().getUserID()); try { @@ -1842,6 +2266,221 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } + public void publishAvatar(final Account account, + final Uri image, + final UiCallback<Avatar> callback) { + final Bitmap.CompressFormat format = Config.AVATAR_FORMAT; + final int size = Config.AVATAR_SIZE; + final Avatar avatar = getFileBackend().getPepAvatar(image, size, format); + if (avatar != null) { + avatar.height = size; + avatar.width = size; + if (format.equals(Bitmap.CompressFormat.WEBP)) { + avatar.type = "image/webp"; + } else if (format.equals(Bitmap.CompressFormat.JPEG)) { + avatar.type = "image/jpeg"; + } else if (format.equals(Bitmap.CompressFormat.PNG)) { + avatar.type = "image/png"; + } + if (!getFileBackend().save(avatar)) { + callback.error(R.string.error_saving_avatar, avatar); + return; + } + final IqPacket packet = this.mIqGenerator.publishAvatar(avatar); + this.sendIqPacket(account, packet, new OnIqPacketReceived() { + + @Override + public void onIqPacketReceived(Account account, IqPacket result) { + if (result.getType() == IqPacket.TYPE.RESULT) { + final IqPacket packet = XmppConnectionService.this.mIqGenerator + .publishAvatarMetadata(avatar); + sendIqPacket(account, packet, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket result) { + if (result.getType() == IqPacket.TYPE.RESULT) { + if (account.setAvatar(avatar.getFilename())) { + getAvatarService().clear(account); + databaseBackend.updateAccount(account); + } + callback.success(avatar); + } else { + callback.error( + R.string.error_publish_avatar_server_reject, + avatar); + } + } + }); + } else { + callback.error( + R.string.error_publish_avatar_server_reject, + avatar); + } + } + }); + } else { + callback.error(R.string.error_publish_avatar_converting, null); + } + } + + public void fetchAvatar(Account account, Avatar avatar) { + fetchAvatar(account, avatar, null); + } + + public void fetchAvatar(Account account, final Avatar avatar, final UiCallback<Avatar> callback) { + final String KEY = generateFetchKey(account, avatar); + synchronized (this.mInProgressAvatarFetches) { + if (this.mInProgressAvatarFetches.contains(KEY)) { + return; + } else { + switch (avatar.origin) { + case PEP: + this.mInProgressAvatarFetches.add(KEY); + fetchAvatarPep(account, avatar, callback); + break; + case VCARD: + this.mInProgressAvatarFetches.add(KEY); + fetchAvatarVcard(account, avatar, callback); + break; + } + } + } + } + + private void fetchAvatarPep(Account account, final Avatar avatar, final UiCallback<Avatar> callback) { + IqPacket packet = this.mIqGenerator.retrievePepAvatar(avatar); + sendIqPacket(account, packet, new OnIqPacketReceived() { + + @Override + public void onIqPacketReceived(Account account, IqPacket result) { + synchronized (mInProgressAvatarFetches) { + mInProgressAvatarFetches.remove(generateFetchKey(account, avatar)); + } + final String ERROR = account.getJid().toBareJid() + + ": fetching avatar for " + avatar.owner + " failed "; + if (result.getType() == IqPacket.TYPE.RESULT) { + avatar.image = mIqParser.avatarData(result); + if (avatar.image != null) { + if (getFileBackend().save(avatar)) { + if (account.getJid().toBareJid().equals(avatar.owner)) { + if (account.setAvatar(avatar.getFilename())) { + databaseBackend.updateAccount(account); + } + getAvatarService().clear(account); + updateConversationUi(); + updateAccountUi(); + } else { + Contact contact = account.getRoster() + .getContact(avatar.owner); + contact.setAvatar(avatar); + getAvatarService().clear(contact); + updateConversationUi(); + updateRosterUi(); + } + if (callback != null) { + callback.success(avatar); + } + Log.d(Config.LOGTAG, account.getJid().toBareJid() + + ": succesfuly fetched pep avatar for " + avatar.owner); + return; + } + } else { + + Log.d(Config.LOGTAG, ERROR + "(parsing error)"); + } + } else { + Element error = result.findChild("error"); + if (error == null) { + Log.d(Config.LOGTAG, ERROR + "(server error)"); + } else { + Log.d(Config.LOGTAG, ERROR + error.toString()); + } + } + if (callback != null) { + callback.error(0, null); + } + + } + }); + } + + private void fetchAvatarVcard(final Account account, final Avatar avatar, final UiCallback<Avatar> callback) { + IqPacket packet = this.mIqGenerator.retrieveVcardAvatar(avatar); + this.sendIqPacket(account, packet, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + synchronized (mInProgressAvatarFetches) { + mInProgressAvatarFetches.remove(generateFetchKey(account, avatar)); + } + if (packet.getType() == IqPacket.TYPE.RESULT) { + Element vCard = packet.findChild("vCard", "vcard-temp"); + Element photo = vCard != null ? vCard.findChild("PHOTO") : null; + String image = photo != null ? photo.findChildContent("BINVAL") : null; + if (image != null) { + avatar.image = image; + if (getFileBackend().save(avatar)) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + + ": successfully fetched vCard avatar for " + avatar.owner); + if (avatar.owner.isBareJid()) { + Contact contact = account.getRoster() + .getContact(avatar.owner); + contact.setAvatar(avatar); + getAvatarService().clear(contact); + updateConversationUi(); + updateRosterUi(); + } else { + Conversation conversation = find(account, avatar.owner.toBareJid()); + if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) { + MucOptions.User user = conversation.getMucOptions().findUser(avatar.owner.getResourcepart()); + if (user != null) { + if (user.setAvatar(avatar)) { + getAvatarService().clear(user); + updateConversationUi(); + updateMucRosterUi(); + } + } + } + } + } + } + } + } + }); + } + + public void checkForAvatar(Account account, final UiCallback<Avatar> callback) { + IqPacket packet = this.mIqGenerator.retrieveAvatarMetaData(null); + this.sendIqPacket(account, packet, new OnIqPacketReceived() { + + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + if (packet.getType() == IqPacket.TYPE.RESULT) { + Element pubsub = packet.findChild("pubsub", + "http://jabber.org/protocol/pubsub"); + if (pubsub != null) { + Element items = pubsub.findChild("items"); + if (items != null) { + Avatar avatar = Avatar.parseMetadata(items); + if (avatar != null) { + avatar.owner = account.getJid().toBareJid(); + if (fileBackend.isAvatarCached(avatar)) { + if (account.setAvatar(avatar.getFilename())) { + databaseBackend.updateAccount(account); + } + getAvatarService().clear(account); + callback.success(avatar); + } else { + fetchAvatarPep(account, avatar, callback); + } + return; + } + } + } + } + callback.error(0, null); + } + }); + } + public void deleteContactOnServer(Contact contact) { contact.resetOption(Contact.Options.PREEMPTIVE_GRANT); contact.resetOption(Contact.Options.DIRTY_PUSH); @@ -1860,24 +2499,39 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa this.databaseBackend.updateConversation(conversation); } - public void reconnectAccount(final Account account, final boolean force) { + private void reconnectAccount(final Account account, final boolean force, final boolean interactive) { synchronized (account) { - if (account.getXmppConnection() != null) { + XmppConnection connection = account.getXmppConnection(); + if (connection != null) { disconnect(account, force); + } else { + connection = createConnection(account); + account.setXmppConnection(connection); } if (!account.isOptionSet(Account.OPTION_DISABLED)) { - - AvatarService.getInstance().clearFetchInProgress(account); - - if (account.getXmppConnection() == null) { - account.setXmppConnection(createConnection(account)); + synchronized (this.mInProgressAvatarFetches) { + for (Iterator<String> iterator = this.mInProgressAvatarFetches.iterator(); iterator.hasNext(); ) { + final String KEY = iterator.next(); + if (KEY.startsWith(account.getJid().toBareJid() + "_")) { + iterator.remove(); + } + } + } + if (!force) { + try { + Logging.d(Config.LOGTAG, "wait for disconnect"); + Thread.sleep(500); //sleep wait for disconnect + } catch (InterruptedException e) { + //ignored + } } - Thread thread = new Thread(account.getXmppConnection()); + Thread thread = new Thread(connection); + connection.setInteractive(interactive); thread.start(); - scheduleWakeUpCall(Config.CONNECT_TIMEOUT, account.getUuid().hashCode()); + scheduleWakeUpCall(Config.CONNECT_DISCO_TIMEOUT, account.getUuid().hashCode()); } else { account.getRoster().clearPresences(); - account.setXmppConnection(null); + connection.resetEverything(); } } } @@ -1886,7 +2540,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa new Thread(new Runnable() { @Override public void run() { - reconnectAccount(account,false); + reconnectAccount(account, false, true); } }).start(); } @@ -1922,7 +2576,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } for (Conversation conversation : getConversations()) { if (conversation.getJid().toBareJid().equals(recipient) && conversation.getAccount() == account) { - final Message message = conversation.findSentMessageWithUuid(uuid); + final Message message = conversation.findSentMessageWithUuidOrRemoteId(uuid); if (message != null) { markMessage(message, status); } @@ -1932,8 +2586,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa return null; } - public boolean markMessage(Conversation conversation, String uuid, - int status) { + public boolean markMessage(Conversation conversation, String uuid, int status) { if (uuid == null) { return false; } else { @@ -1958,9 +2611,42 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa updateConversationUi(); } + public SharedPreferences getPreferences() { + return PreferenceManager + .getDefaultSharedPreferences(getApplicationContext()); + } + + public boolean confirmMessages() { + return getPreferences().getBoolean("confirm_messages", true); + } + + public boolean sendChatStates() { + return getPreferences().getBoolean("chat_states", false); + } + + public boolean saveEncryptedMessages() { + return !getPreferences().getBoolean("dont_save_encrypted", false); + } + + private boolean respectAutojoin() { + return getPreferences().getBoolean("autojoin", true); + } + + public boolean indicateReceived() { + return getPreferences().getBoolean("indicate_received", false); + } + + public boolean useTorToConnect() { + return Config.FORCE_ORBOT || getPreferences().getBoolean("use_tor", false); + } + + public boolean showExtendedConnectionOptions() { + return getPreferences().getBoolean("show_connection_options", false); + } + public int unreadCount() { int count = 0; - for(Conversation conversation : getConversations()) { + for (Conversation conversation : getConversations()) { count += conversation.unreadCount(); } return count; @@ -1991,6 +2677,20 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } + public boolean displayCaptchaRequest(Account account, String id, Data data, Bitmap captcha) { + boolean rc = false; + if (mOnCaptchaRequested != null) { + DisplayMetrics metrics = getApplicationContext().getResources().getDisplayMetrics(); + Bitmap scaled = Bitmap.createScaledBitmap(captcha, (int) (captcha.getWidth() * metrics.scaledDensity), + (int) (captcha.getHeight() * metrics.scaledDensity), false); + + mOnCaptchaRequested.onCaptchaRequested(account, id, data, scaled); + rc = true; + } + + return rc; + } + public void updateBlocklistUi(final OnUpdateBlocklist.Status status) { if (mOnUpdateBlocklist != null) { mOnUpdateBlocklist.OnUpdateBlocklist(status); @@ -2003,6 +2703,12 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } + public void keyStatusUpdated(AxolotlService.FetchStatus report) { + if (mOnKeyStatusUpdated != null) { + mOnKeyStatusUpdated.onKeyStatusUpdated(report); + } + } + public Account findAccountByJid(final Jid accountJid) { for (Account account : this.accounts) { if (account.getJid().toBareJid().equals(accountJid.toBareJid())) { @@ -2023,7 +2729,18 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa public void markRead(final Conversation conversation) { mNotificationService.clear(conversation); - conversation.markRead(); + final List<Message> readMessages = conversation.markRead(); + if (readMessages.size() > 0) { + Runnable runnable = new Runnable() { + @Override + public void run() { + for (Message message : readMessages) { + databaseBackend.updateMessage(message); + } + } + }; + ConversationsPlusApplication.executeDatabaseOperation.execute(runnable); + } updateUnreadCountBadge(); } @@ -2079,6 +2796,10 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa return this.pm; } + public LruCache<String, Bitmap> getBitmapCache() { + return this.mBitmapCache; + } + public void syncRosterToDisk(final Account account) { Runnable runnable = new Runnable() { @@ -2129,15 +2850,36 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } public void sendPresencePacket(Account account, PresencePacket packet) { - XmppSendUtil.sendPresencePacket(account, packet); + XmppConnection connection = account.getXmppConnection(); + if (connection != null) { + connection.sendPresencePacket(packet); + } + } + + public void sendCreateAccountWithCaptchaPacket(Account account, String id, Data data) { + XmppConnection connection = account.getXmppConnection(); + if (connection != null) { + connection.sendCaptchaRegistryRequest(id, data); + } } public void sendIqPacket(final Account account, final IqPacket packet, final OnIqPacketReceived callback) { - XmppSendUtil.sendIqPacket(account, packet, callback); + final XmppConnection connection = account.getXmppConnection(); + if (connection != null) { + connection.sendIqPacket(packet, callback); + } } public void sendPresence(final Account account) { - sendPresencePacket(account, mPresenceGenerator.sendPresence(account)); + sendPresencePacket(account, mPresenceGenerator.selfPresence(account, getTargetPresence())); + } + + public void refreshAllPresences() { + for (Account account : getAccounts()) { + if (!account.isOptionSet(Account.OPTION_DISABLED)) { + sendPresence(account); + } + } } public void sendOfflinePresence(final Account account) { @@ -2201,8 +2943,9 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } for (final Message msg : messages) { + msg.setTime(System.currentTimeMillis()); markMessage(msg, Message.STATUS_WAITING); - this.resendMessage(msg); + this.resendMessage(msg, false); } } @@ -2214,12 +2957,13 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa * Therefore set this flag to true and try to get messages from server */ conversation.setHasMessagesLeftOnServer(true); - new Thread(new Runnable() { + Runnable runnable = new Runnable() { @Override public void run() { databaseBackend.deleteMessagesInConversation(conversation); } - }).start(); + }; + ConversationsPlusPreferences.dontTrustSystemCAs().execute(runnable); } public void sendBlockRequest(final Blockable blockable) { @@ -2253,54 +2997,88 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } - public interface OnMoreMessagesLoaded { - public void onMoreMessagesLoaded(int count, Conversation conversation); + public void publishDisplayName(Account account) { + String displayName = account.getDisplayName(); + if (displayName != null && !displayName.isEmpty()) { + IqPacket publish = mIqGenerator.publishNick(displayName); + sendIqPacket(account, publish, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + if (packet.getType() == IqPacket.TYPE.ERROR) { + Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could not publish nick"); + } + } + }); + } + } - public void informUser(int r); + public interface OnAccountCreated { + void onAccountCreated(Account account); - void setLoadingInProgress(); + void informUser(int r); + } - boolean isLoadingInProgress(); + public interface OnMoreMessagesLoaded { + void onMoreMessagesLoaded(int count, Conversation conversation); + + void informUser(int r); } public interface OnAccountPasswordChanged { - public void onPasswordChangeSucceeded(); + void onPasswordChangeSucceeded(); - public void onPasswordChangeFailed(); + void onPasswordChangeFailed(); } public interface OnAffiliationChanged { - public void onAffiliationChangedSuccessful(Jid jid); + void onAffiliationChangedSuccessful(Jid jid); - public void onAffiliationChangeFailed(Jid jid, int resId); + void onAffiliationChangeFailed(Jid jid, int resId); } public interface OnRoleChanged { - public void onRoleChangedSuccessful(String nick); + void onRoleChangedSuccessful(String nick); - public void onRoleChangeFailed(String nick, int resid); + void onRoleChangeFailed(String nick, int resid); } public interface OnConversationUpdate { - public void onConversationUpdate(); + void onConversationUpdate(); } public interface OnAccountUpdate { - public void onAccountUpdate(); + void onAccountUpdate(); + } + + public interface OnCaptchaRequested { + void onCaptchaRequested(Account account, + String id, + Data data, + Bitmap captcha); } public interface OnRosterUpdate { - public void onRosterUpdate(); + void onRosterUpdate(); } public interface OnMucRosterUpdate { - public void onMucRosterUpdate(); + void onMucRosterUpdate(); + } + + public interface OnConferenceConfigurationFetched { + void onConferenceConfigurationFetched(Conversation conversation); + + void onFetchFailed(Conversation conversation, Element error); + } + + public interface OnConferenceJoined { + void onConferenceJoined(Conversation conversation); } public interface OnConferenceOptionsPushed { - public void onPushSucceeded(); + void onPushSucceeded(); - public void onPushFailed(); + void onPushFailed(); } public interface OnShowErrorToast { diff --git a/src/main/java/eu/siacs/conversations/ui/AboutPreference.java b/src/main/java/eu/siacs/conversations/ui/AboutPreference.java index 6ad34257..b9e5c367 100644 --- a/src/main/java/eu/siacs/conversations/ui/AboutPreference.java +++ b/src/main/java/eu/siacs/conversations/ui/AboutPreference.java @@ -2,7 +2,6 @@ package eu.siacs.conversations.ui; import android.content.Context; import android.content.Intent; -import android.content.pm.PackageManager; import android.preference.Preference; import android.util.AttributeSet; diff --git a/src/main/java/eu/siacs/conversations/ui/BlocklistActivity.java b/src/main/java/eu/siacs/conversations/ui/BlocklistActivity.java index 13d7f4fc..5a85c17b 100644 --- a/src/main/java/eu/siacs/conversations/ui/BlocklistActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/BlocklistActivity.java @@ -35,7 +35,7 @@ public class BlocklistActivity extends AbstractSearchableListItemActivity implem @Override public void onBackendConnected() { for (final Account account : xmppConnectionService.getAccounts()) { - if (account.getJid().toString().equals(getIntent().getStringExtra("account"))) { + if (account.getJid().toString().equals(getIntent().getStringExtra(EXTRA_ACCOUNT))) { this.account = account; break; } @@ -55,16 +55,10 @@ public class BlocklistActivity extends AbstractSearchableListItemActivity implem } Collections.sort(getListItems()); } - runOnUiThread(new Runnable() { - @Override - public void run() { - getListItemAdapter().notifyDataSetChanged(); - } - }); + getListItemAdapter().notifyDataSetChanged(); } - @Override - public void OnUpdateBlocklist(final OnUpdateBlocklist.Status status) { + protected void refreshUiReal() { final Editable editable = getSearchEditText().getText(); if (editable != null) { filterContacts(editable.toString()); @@ -72,4 +66,9 @@ public class BlocklistActivity extends AbstractSearchableListItemActivity implem filterContacts(); } } + + @Override + public void OnUpdateBlocklist(final OnUpdateBlocklist.Status status) { + refreshUi(); + } } diff --git a/src/main/java/eu/siacs/conversations/ui/ChangePasswordActivity.java b/src/main/java/eu/siacs/conversations/ui/ChangePasswordActivity.java index aac435fd..f55c99f3 100644 --- a/src/main/java/eu/siacs/conversations/ui/ChangePasswordActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ChangePasswordActivity.java @@ -4,7 +4,6 @@ import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.EditText; -import android.widget.TextView; import android.widget.Toast; import eu.siacs.conversations.R; @@ -54,14 +53,7 @@ public class ChangePasswordActivity extends XmppActivity implements XmppConnecti @Override void onBackendConnected() { - try { - final String jid = getIntent() == null ? null : getIntent().getStringExtra("account"); - if (jid != null) { - this.mAccount = xmppConnectionService.findAccountByJid(Jid.fromString(jid)); - } - } catch (final InvalidJidException ignored) { - - } + this.mAccount = extractAccount(getIntent()); } @@ -107,4 +99,8 @@ public class ChangePasswordActivity extends XmppActivity implements XmppConnecti }); } + + public void refreshUiReal() { + + } } diff --git a/src/main/java/eu/siacs/conversations/ui/ChooseContactActivity.java b/src/main/java/eu/siacs/conversations/ui/ChooseContactActivity.java index c9e99ce5..c5357a5e 100644 --- a/src/main/java/eu/siacs/conversations/ui/ChooseContactActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ChooseContactActivity.java @@ -13,18 +13,22 @@ import android.widget.AbsListView.MultiChoiceModeListener; import android.widget.AdapterView; import android.widget.ListView; -import java.util.Set; -import java.util.HashSet; +import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.List; -import java.util.ArrayList; +import java.util.Set; +import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.ListItem; +import eu.siacs.conversations.xmpp.jid.Jid; public class ChooseContactActivity extends AbstractSearchableListItemActivity { + private List<String> mActivatedAccounts = new ArrayList<String>(); + private List<String> mKnownHosts; private Set<Contact> selected; private Set<String> filterContacts; @@ -109,11 +113,11 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity { final Intent data = new Intent(); final ListItem mListItem = getListItems().get(position); data.putExtra("contact", mListItem.getJid().toString()); - String account = request.getStringExtra("account"); + String account = request.getStringExtra(EXTRA_ACCOUNT); if (account == null && mListItem instanceof Contact) { account = ((Contact) mListItem).getAccount().getJid().toBareJid().toString(); } - data.putExtra("account", account); + data.putExtra(EXTRA_ACCOUNT, account); data.putExtra("conversation", request.getStringExtra("conversation")); data.putExtra("multiple", false); @@ -124,6 +128,15 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity { } + @Override + public boolean onCreateOptionsMenu(final Menu menu) { + super.onCreateOptionsMenu(menu); + final Intent i = getIntent(); + boolean showEnterJid = i != null && i.getBooleanExtra("show_enter_jid", false); + menu.findItem(R.id.action_create_contact).setVisible(showEnterJid); + return true; + } + protected void filterContacts(final String needle) { getListItems().clear(); for (final Account account : xmppConnectionService.getAccounts()) { @@ -149,4 +162,62 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity { return result.toArray(new String[result.size()]); } + + public void refreshUiReal() { + //nothing to do. This Activity doesn't implement any listeners + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.action_create_contact: + showEnterJidDialog(); + return true; + } + return super.onOptionsItemSelected(item); + } + + protected void showEnterJidDialog() { + EnterJidDialog dialog = new EnterJidDialog( + this, mKnownHosts, mActivatedAccounts, + getString(R.string.enter_contact), getString(R.string.select), + null, getIntent().getStringExtra(EXTRA_ACCOUNT), true + ); + + dialog.setOnEnterJidDialogPositiveListener(new EnterJidDialog.OnEnterJidDialogPositiveListener() { + @Override + public boolean onEnterJidDialogPositive(Jid accountJid, Jid contactJid) throws EnterJidDialog.JidError { + final Intent request = getIntent(); + final Intent data = new Intent(); + data.putExtra("contact", contactJid.toString()); + data.putExtra(EXTRA_ACCOUNT, accountJid.toString()); + data.putExtra("conversation", + request.getStringExtra("conversation")); + data.putExtra("multiple", false); + setResult(RESULT_OK, data); + finish(); + + return true; + } + }); + + dialog.show(); + } + + @Override + void onBackendConnected() { + filterContacts(); + + this.mActivatedAccounts.clear(); + for (Account account : xmppConnectionService.getAccounts()) { + if (account.getStatus() != Account.State.DISABLED) { + if (Config.DOMAIN_LOCK != null) { + this.mActivatedAccounts.add(account.getJid().getLocalpart()); + } else { + this.mActivatedAccounts.add(account.getJid().toBareJid().toString()); + } + } + } + this.mKnownHosts = xmppConnectionService.getKnownHosts(); + } } diff --git a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java index 6f30d962..e42d3e61 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java @@ -19,6 +19,7 @@ import android.widget.Button; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; +import android.widget.TableLayout; import android.widget.TextView; import android.widget.Toast; @@ -27,7 +28,9 @@ import org.openintents.openpgp.util.OpenPgpUtils; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; +import java.util.concurrent.atomic.AtomicInteger; +import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.PgpEngine; import eu.siacs.conversations.entities.Account; @@ -38,8 +41,8 @@ import eu.siacs.conversations.entities.MucOptions; import eu.siacs.conversations.entities.MucOptions.User; import eu.siacs.conversations.services.AvatarService; import eu.siacs.conversations.services.XmppConnectionService; -import eu.siacs.conversations.services.XmppConnectionService.OnMucRosterUpdate; import eu.siacs.conversations.services.XmppConnectionService.OnConversationUpdate; +import eu.siacs.conversations.services.XmppConnectionService.OnMucRosterUpdate; import eu.siacs.conversations.xmpp.jid.Jid; public class ConferenceDetailsActivity extends XmppActivity implements OnConversationUpdate, OnMucRosterUpdate, XmppConnectionService.OnAffiliationChanged, XmppConnectionService.OnRoleChanged, XmppConnectionService.OnConferenceOptionsPushed { @@ -61,7 +64,11 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers private LinearLayout membersView; private LinearLayout mMoreDetails; private TextView mConferenceType; + private TableLayout mConferenceInfoTable; + private TextView mConferenceInfoMam; + private TextView mNotifyStatusText; private ImageButton mChangeConferenceSettingsButton; + private ImageButton mNotifyStatusButton; private Button mInviteButton; private String uuid = null; private User mSelectedUser = null; @@ -96,17 +103,76 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers } }; + + private OnClickListener mNotifyStatusClickListener = new OnClickListener() { + @Override + public void onClick(View v) { + AlertDialog.Builder builder = new AlertDialog.Builder(ConferenceDetailsActivity.this); + builder.setTitle(R.string.pref_notification_settings); + String[] choices = { + getString(R.string.notify_on_all_messages), + getString(R.string.notify_only_when_highlighted), + getString(R.string.notify_never) + }; + final AtomicInteger choice; + if (mConversation.getLongAttribute(Conversation.ATTRIBUTE_MUTED_TILL,0) == Long.MAX_VALUE) { + choice = new AtomicInteger(2); + } else { + choice = new AtomicInteger(mConversation.alwaysNotify() ? 0 : 1); + } + builder.setSingleChoiceItems(choices, choice.get(), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + choice.set(which); + } + }); + builder.setNegativeButton(R.string.cancel, null); + builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (choice.get() == 2) { + mConversation.setMutedTill(Long.MAX_VALUE); + } else { + mConversation.setMutedTill(0); + mConversation.setAttribute(Conversation.ATTRIBUTE_ALWAYS_NOTIFY,String.valueOf(choice.get() == 0)); + } + xmppConnectionService.updateConversation(mConversation); + updateView(); + } + }); + builder.create().show(); + } + }; + private OnClickListener mChangeConferenceSettings = new OnClickListener() { @Override public void onClick(View v) { final MucOptions mucOptions = mConversation.getMucOptions(); AlertDialog.Builder builder = new AlertDialog.Builder(ConferenceDetailsActivity.this); builder.setTitle(R.string.conference_options); - String[] options = {getString(R.string.members_only), - getString(R.string.non_anonymous)}; - final boolean[] values = new boolean[options.length]; - values[0] = mucOptions.membersOnly(); - values[1] = mucOptions.nonanonymous(); + final String[] options; + final boolean[] values; + if (mAdvancedMode) { + options = new String[]{ + getString(R.string.members_only), + getString(R.string.moderated), + getString(R.string.non_anonymous) + }; + values = new boolean[]{ + mucOptions.membersOnly(), + mucOptions.moderated(), + mucOptions.nonanonymous() + }; + } else { + options = new String[]{ + getString(R.string.members_only), + getString(R.string.non_anonymous) + }; + values = new boolean[]{ + mucOptions.membersOnly(), + mucOptions.nonanonymous() + }; + } builder.setMultiChoiceItems(options,values,new DialogInterface.OnMultiChoiceClickListener() { @Override public void onClick(DialogInterface dialog, int which, boolean isChecked) { @@ -124,7 +190,12 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers } Bundle options = new Bundle(); options.putString("muc#roomconfig_membersonly", values[0] ? "1" : "0"); - options.putString("muc#roomconfig_whois", values[1] ? "anyone" : "moderators"); + if (values.length == 2) { + options.putString("muc#roomconfig_whois", values[1] ? "anyone" : "moderators"); + } else if (values.length == 3) { + options.putString("muc#roomconfig_moderatedroom", values[1] ? "1" : "0"); + options.putString("muc#roomconfig_whois", values[2] ? "anyone" : "moderators"); + } options.putString("muc#roomconfig_persistentroom", "1"); xmppConnectionService.pushConferenceConfiguration(mConversation, options, @@ -171,7 +242,6 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers mMoreDetails.setVisibility(View.GONE); mChangeConferenceSettingsButton = (ImageButton) findViewById(R.id.change_conference_button); mChangeConferenceSettingsButton.setOnClickListener(this.mChangeConferenceSettings); - mConferenceType = (TextView) findViewById(R.id.muc_conference_type); mInviteButton = (Button) findViewById(R.id.invite); mInviteButton.setOnClickListener(inviteListener); mConferenceType = (TextView) findViewById(R.id.muc_conference_type); @@ -193,6 +263,13 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers }); } }); + this.mAdvancedMode = getPreferences().getBoolean("advanced_muc_mode", false); + this.mConferenceInfoTable = (TableLayout) findViewById(R.id.muc_info_more); + mConferenceInfoTable.setVisibility(this.mAdvancedMode ? View.VISIBLE : View.GONE); + this.mConferenceInfoMam = (TextView) findViewById(R.id.muc_info_mam); + this.mNotifyStatusButton = (ImageButton) findViewById(R.id.notification_status_button); + this.mNotifyStatusButton.setOnClickListener(this.mNotifyStatusClickListener); + this.mNotifyStatusText = (TextView) findViewById(R.id.notification_status_text); } @Override @@ -215,6 +292,8 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers case R.id.action_advanced_mode: this.mAdvancedMode = !menuItem.isChecked(); menuItem.setChecked(this.mAdvancedMode); + getPreferences().edit().putBoolean("advanced_muc_mode", mAdvancedMode).commit(); + mConferenceInfoTable.setVisibility(this.mAdvancedMode ? View.VISIBLE : View.GONE); invalidateOptionsMenu(); updateView(); break; @@ -236,6 +315,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers MenuItem menuItemSaveBookmark = menu.findItem(R.id.action_save_as_bookmark); MenuItem menuItemDeleteBookmark = menu.findItem(R.id.action_delete_bookmark); MenuItem menuItemAdvancedMode = menu.findItem(R.id.action_advanced_mode); + MenuItem menuItemChangeSubject = menu.findItem(R.id.action_edit_subject); menuItemAdvancedMode.setChecked(mAdvancedMode); if (mConversation == null) { return true; @@ -248,6 +328,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers menuItemDeleteBookmark.setVisible(false); menuItemSaveBookmark.setVisible(true); } + menuItemChangeSubject.setVisible(mConversation.getMucOptions().canChangeSubject()); return true; } @@ -266,14 +347,17 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers final User self = mConversation.getMucOptions().getSelf(); this.mSelectedUser = user; String name; + final Contact contact = user.getContact(); + if (contact != null) { + name = contact.getDisplayName(); + } else if (user.getJid() != null){ + name = user.getJid().toBareJid().toString(); + } else { + name = user.getName(); + } + menu.setHeaderTitle(name); if (user.getJid() != null) { - final Contact contact = user.getContact(); - if (contact != null) { - name = contact.getDisplayName(); - } else { - name = user.getJid().toBareJid().toString(); - } - menu.setHeaderTitle(name); + MenuItem showContactDetails = menu.findItem(R.id.action_contact_details); MenuItem startConversation = menu.findItem(R.id.start_conversation); MenuItem giveMembership = menu.findItem(R.id.give_membership); MenuItem removeMembership = menu.findItem(R.id.remove_membership); @@ -282,6 +366,9 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers MenuItem removeFromRoom = menu.findItem(R.id.remove_from_room); MenuItem banFromConference = menu.findItem(R.id.ban_from_conference); startConversation.setVisible(true); + if (contact != null) { + showContactDetails.setVisible(true); + } if (self.getAffiliation().ranks(MucOptions.Affiliation.ADMIN) && self.getAffiliation().outranks(user.getAffiliation())) { if (mAdvancedMode) { @@ -300,15 +387,24 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers removeAdminPrivileges.setVisible(true); } } + } else { + MenuItem sendPrivateMessage = menu.findItem(R.id.send_private_message); + sendPrivateMessage.setVisible(true); } } - super.onCreateContextMenu(menu,v,menuInfo); + super.onCreateContextMenu(menu, v, menuInfo); } @Override public boolean onContextItemSelected(MenuItem item) { switch (item.getItemId()) { + case R.id.action_contact_details: + Contact contact = mSelectedUser.getContact(); + if (contact != null) { + switchToContactDetails(contact); + } + return true; case R.id.start_conversation: startConversation(mSelectedUser); return true; @@ -331,6 +427,9 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers xmppConnectionService.changeAffiliationInConference(mConversation,mSelectedUser.getJid(), MucOptions.Affiliation.OUTCAST,this); xmppConnectionService.changeRoleInConference(mConversation,mSelectedUser.getName(), MucOptions.Role.NONE,this); return true; + case R.id.send_private_message: + privateMsgInMuc(mConversation,mSelectedUser.getName()); + return true; default: return super.onContextItemSelected(item); } @@ -369,7 +468,8 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers if (!mConversation.getJid().isBareJid()) { bookmark.setNick(mConversation.getJid().getResourcepart()); } - bookmark.setAutojoin(true); + bookmark.setBookmarkName(mConversation.getMucOptions().getSubject()); + bookmark.setAutojoin(getPreferences().getBoolean("autojoin",true)); account.getBookmarks().add(bookmark); xmppConnectionService.pushBookmarks(account); mConversation.setBookmark(bookmark); @@ -404,9 +504,14 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers private void updateView() { final MucOptions mucOptions = mConversation.getMucOptions(); final User self = mucOptions.getSelf(); - mAccountJid.setText(getString(R.string.using_account, mConversation - .getAccount().getJid().toBareJid())); - mYourPhoto.setImageBitmap(AvatarService.getInstance().get(mConversation.getAccount(), getPixel(48))); + String account; + if (Config.DOMAIN_LOCK != null) { + account = mConversation.getAccount().getJid().getLocalpart(); + } else { + account = mConversation.getAccount().getJid().toBareJid().toString(); + } + mAccountJid.setText(getString(R.string.using_account, account)); + mYourPhoto.setImageBitmap(avatarService().get(mConversation.getAccount(), getPixel(48))); setTitle(mConversation.getName()); mFullJid.setText(mConversation.getJid().toBareJid().toString()); mYourNick.setText(mucOptions.getActualNick()); @@ -425,16 +530,36 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers } else { mConferenceType.setText(R.string.public_conference); } + if (mucOptions.mamSupport()) { + mConferenceInfoMam.setText(R.string.server_info_available); + } else { + mConferenceInfoMam.setText(R.string.server_info_unavailable); + } if (self.getAffiliation().ranks(MucOptions.Affiliation.OWNER)) { mChangeConferenceSettingsButton.setVisibility(View.VISIBLE); } else { mChangeConferenceSettingsButton.setVisibility(View.GONE); } } + + long mutedTill = mConversation.getLongAttribute(Conversation.ATTRIBUTE_MUTED_TILL,0); + if (mutedTill == Long.MAX_VALUE) { + mNotifyStatusText.setText(R.string.notify_never); + mNotifyStatusButton.setImageResource(R.drawable.ic_notifications_off_grey600_24dp); + } else if (System.currentTimeMillis() < mutedTill) { + mNotifyStatusText.setText(R.string.notify_paused); + mNotifyStatusButton.setImageResource(R.drawable.ic_notifications_paused_grey600_24dp); + } else if (mConversation.alwaysNotify()) { + mNotifyStatusButton.setImageResource(R.drawable.ic_notifications_grey600_24dp); + mNotifyStatusText.setText(R.string.notify_on_all_messages); + } else { + mNotifyStatusButton.setImageResource(R.drawable.ic_notifications_none_grey600_24dp); + mNotifyStatusText.setText(R.string.notify_only_when_highlighted); + } + LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); membersView.removeAllViews(); - final ArrayList<User> users = new ArrayList<>(); - users.addAll(mConversation.getMucOptions().getUsers()); + final ArrayList<User> users = mucOptions.getUsers(); Collections.sort(users,new Comparator<User>() { @Override public int compare(User lhs, User rhs) { @@ -466,20 +591,17 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers }); tvKey.setText(OpenPgpUtils.convertKeyIdToHex(user.getPgpKeyId())); } - Bitmap bm; Contact contact = user.getContact(); if (contact != null) { - bm = AvatarService.getInstance().get(contact, getPixel(48)); tvDisplayName.setText(contact.getDisplayName()); tvStatus.setText(user.getName() + " \u2022 " + getStatus(user)); } else { - bm = AvatarService.getInstance().get(user.getName(), getPixel(48)); tvDisplayName.setText(user.getName()); tvStatus.setText(getStatus(user)); } ImageView iv = (ImageView) view.findViewById(R.id.contact_photo); - iv.setImageBitmap(bm); + iv.setImageBitmap(avatarService().get(user, getPixel(48), false)); membersView.addView(view); if (mConversation.getMucOptions().canInvite()) { mInviteButton.setVisibility(View.VISIBLE); diff --git a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java index e408e56a..2a8bb1ce 100644 --- a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java @@ -6,12 +6,15 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentSender.SendIntentException; +import android.content.SharedPreferences; import android.net.Uri; import android.os.Bundle; +import android.preference.PreferenceManager; import android.provider.ContactsContract; import android.provider.ContactsContract.CommonDataKinds; import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.Intents; +import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; @@ -25,30 +28,35 @@ import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.QuickContactBadge; import android.widget.TextView; +import android.widget.Toast; import org.openintents.openpgp.util.OpenPgpUtils; +import java.security.cert.X509Certificate; import java.util.List; import de.thedevstack.conversationsplus.ConversationsPlusPreferences; import de.thedevstack.conversationsplus.ui.listeners.ShowResourcesListDialogListener; +import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.PgpEngine; +import eu.siacs.conversations.crypto.axolotl.AxolotlService; +import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.ListItem; -import eu.siacs.conversations.services.AvatarService; import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate; import eu.siacs.conversations.services.XmppConnectionService.OnRosterUpdate; import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.UIHelper; +import eu.siacs.conversations.xmpp.OnKeyStatusUpdated; import eu.siacs.conversations.xmpp.OnUpdateBlocklist; import eu.siacs.conversations.xmpp.XmppConnection; import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; -public class ContactDetailsActivity extends XmppActivity implements OnAccountUpdate, OnRosterUpdate, OnUpdateBlocklist { +public class ContactDetailsActivity extends XmppActivity implements OnAccountUpdate, OnRosterUpdate, OnUpdateBlocklist, OnKeyStatusUpdated { public static final String ACTION_VIEW_CONTACT = "view_contact"; private Contact contact; @@ -110,6 +118,7 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd private LinearLayout keys; private LinearLayout tags; private boolean showDynamicTags; + private String messageFingerprint; private DialogInterface.OnClickListener addToPhonebook = new DialogInterface.OnClickListener() { @@ -160,6 +169,11 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd } @Override + public void OnUpdateBlocklist(final Status status) { + refreshUi(); + } + + @Override protected void refreshUiReal() { invalidateOptionsMenu(); populateView(); @@ -179,7 +193,7 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd super.onCreate(savedInstanceState); if (getIntent().getAction().equals(ACTION_VIEW_CONTACT)) { try { - this.accountJid = Jid.fromString(getIntent().getExtras().getString("account")); + this.accountJid = Jid.fromString(getIntent().getExtras().getString(EXTRA_ACCOUNT)); } catch (final InvalidJidException ignored) { } try { @@ -187,6 +201,7 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd } catch (final InvalidJidException ignored) { } } + this.messageFingerprint = getIntent().getStringExtra("fingerprint"); setContentView(R.layout.activity_contact_details); contactJidTv = (TextView) findViewById(R.id.details_contactjid); @@ -351,8 +366,14 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd } else { contactJidTv.setText(contact.getJid().toString()); } + String account; + if (Config.DOMAIN_LOCK != null) { + account = contact.getAccount().getJid().getLocalpart(); + } else { + account = contact.getAccount().getJid().toBareJid().toString(); + } contactJidTv.setOnClickListener(new ShowResourcesListDialogListener(ContactDetailsActivity.this, contact)); - accountJidTv.setText(getString(R.string.using_account, contact.getAccount().getJid().toBareJid())); + accountJidTv.setText(getString(R.string.using_account, account)); badge.setImageBitmap(AvatarService.getInstance().get(contact, getPixel(72))); badge.setOnClickListener(this.onBadgeClick); @@ -364,13 +385,13 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd View view = inflater.inflate(R.layout.contact_key, keys, false); TextView key = (TextView) view.findViewById(R.id.key); TextView keyType = (TextView) view.findViewById(R.id.key_type); - ImageButton remove = (ImageButton) view + ImageButton removeButton = (ImageButton) view .findViewById(R.id.button_remove); - remove.setVisibility(View.VISIBLE); + removeButton.setVisibility(View.VISIBLE); keyType.setText("OTR Fingerprint"); key.setText(CryptoHelper.prettifyFingerprint(otrFingerprint)); keys.addView(view); - remove.setOnClickListener(new OnClickListener() { + removeButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { @@ -378,6 +399,15 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd } }); } + for (final String fingerprint : contact.getAccount().getAxolotlService().getFingerprintsForContact(contact)) { + boolean highlight = fingerprint.equals(messageFingerprint); + hasKeys |= addFingerprintRow(keys, contact.getAccount(), fingerprint, highlight, new OnClickListener() { + @Override + public void onClick(View v) { + onOmemoKeyClicked(contact.getAccount(), fingerprint); + } + }); + } if (contact.getPgpKeyId() != 0) { hasKeys = true; View view = inflater.inflate(R.layout.contact_key, keys, false); @@ -428,6 +458,40 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd } } + private void onOmemoKeyClicked(Account account, String fingerprint) { + final XmppAxolotlSession.Trust trust = account.getAxolotlService().getFingerprintTrust(fingerprint); + if (trust != null && trust == XmppAxolotlSession.Trust.TRUSTED_X509) { + X509Certificate x509Certificate = account.getAxolotlService().getFingerprintCertificate(fingerprint); + if (x509Certificate != null) { + showCertificateInformationDialog(CryptoHelper.extractCertificateInformation(x509Certificate)); + } else { + Toast.makeText(this,R.string.certificate_not_found, Toast.LENGTH_SHORT).show(); + } + } + } + + private void showCertificateInformationDialog(Bundle bundle) { + View view = getLayoutInflater().inflate(R.layout.certificate_information, null); + final String not_available = getString(R.string.certicate_info_not_available); + TextView subject_cn = (TextView) view.findViewById(R.id.subject_cn); + TextView subject_o = (TextView) view.findViewById(R.id.subject_o); + TextView issuer_cn = (TextView) view.findViewById(R.id.issuer_cn); + TextView issuer_o = (TextView) view.findViewById(R.id.issuer_o); + TextView sha1 = (TextView) view.findViewById(R.id.sha1); + + subject_cn.setText(bundle.getString("subject_cn", not_available)); + subject_o.setText(bundle.getString("subject_o", not_available)); + issuer_cn.setText(bundle.getString("issuer_cn", not_available)); + issuer_o.setText(bundle.getString("issuer_o", not_available)); + sha1.setText(bundle.getString("sha1", not_available)); + + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.certificate_information); + builder.setView(view); + builder.setPositiveButton(R.string.ok, null); + builder.create().show(); + } + protected void confirmToDeleteFingerprint(final String fingerprint) { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(R.string.delete_fingerprint); @@ -462,14 +526,7 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd } @Override - public void OnUpdateBlocklist(final Status status) { - runOnUiThread(new Runnable() { - - @Override - public void run() { - invalidateOptionsMenu(); - populateView(); - } - }); + public void onKeyStatusUpdated(AxolotlService.FetchStatus report) { + refreshUi(); } } diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java index f6c90274..1f163b82 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java @@ -10,14 +10,21 @@ import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.content.IntentSender.SendIntentException; +import android.content.pm.PackageManager; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.provider.MediaStore; +import android.provider.Settings; import android.support.v4.widget.SlidingPaneLayout; import android.support.v4.widget.SlidingPaneLayout.PanelSlideListener; +import android.util.Log; +import android.util.Pair; +import android.view.Gravity; +import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; +import android.view.Surface; import android.view.View; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; @@ -33,17 +40,24 @@ import de.thedevstack.conversationsplus.ConversationsPlusPreferences; import de.thedevstack.conversationsplus.ui.dialogs.UserDecisionDialog; import de.thedevstack.conversationsplus.ui.listeners.ResizePictureUserDecisionListener; import de.timroes.android.listview.EnhancedListView; +import org.openintents.openpgp.util.OpenPgpApi; import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import de.timroes.android.listview.EnhancedListView; +import eu.siacs.conversations.Config; import eu.siacs.conversations.R; +import eu.siacs.conversations.crypto.axolotl.AxolotlService; +import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Blockable; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Message; +import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.persistance.FileBackend; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate; @@ -52,6 +66,8 @@ import eu.siacs.conversations.services.XmppConnectionService.OnRosterUpdate; import eu.siacs.conversations.ui.adapter.ConversationAdapter; import eu.siacs.conversations.utils.ExceptionHelper; import eu.siacs.conversations.xmpp.OnUpdateBlocklist; +import eu.siacs.conversations.xmpp.jid.InvalidJidException; +import eu.siacs.conversations.xmpp.jid.Jid; public class ConversationActivity extends XmppActivity implements OnAccountUpdate, OnConversationUpdate, OnRosterUpdate, OnUpdateBlocklist, XmppConnectionService.OnShowErrorToast { @@ -63,15 +79,20 @@ public class ConversationActivity extends XmppActivity public static final String MESSAGE = "messageUuid"; public static final String TEXT = "text"; public static final String NICK = "nick"; + public static final String PRIVATE_MESSAGE = "pm"; public static final int REQUEST_SEND_MESSAGE = 0x0201; public static final int REQUEST_DECRYPT_PGP = 0x0202; public static final int REQUEST_ENCRYPT_MESSAGE = 0x0207; + public static final int REQUEST_TRUST_KEYS_TEXT = 0x0208; + public static final int REQUEST_TRUST_KEYS_MENU = 0x0209; + public static final int REQUEST_START_DOWNLOAD = 0x0210; public static final int ATTACHMENT_CHOICE_CHOOSE_IMAGE = 0x0301; public static final int ATTACHMENT_CHOICE_TAKE_PHOTO = 0x0302; public static final int ATTACHMENT_CHOICE_CHOOSE_FILE = 0x0303; public static final int ATTACHMENT_CHOICE_RECORD_VOICE = 0x0304; public static final int ATTACHMENT_CHOICE_LOCATION = 0x0305; + public static final int ATTACHMENT_CHOICE_INVALID = 0x0306; private static final String STATE_OPEN_CONVERSATION = "state_open_conversation"; private static final String STATE_PANEL_OPEN = "state_panel_open"; private static final String STATE_PENDING_URI = "state_pending_uri"; @@ -81,6 +102,10 @@ public class ConversationActivity extends XmppActivity final private List<Uri> mPendingImageUris = new ArrayList<>(); final private List<Uri> mPendingFileUris = new ArrayList<>(); private Uri mPendingGeoUri = null; + private boolean forbidProcessingPendings = false; + private Message mPendingDownloadableMessage = null; + + private boolean conversationWasSelectedByKeyboard = false; private View mContentView; @@ -92,10 +117,9 @@ public class ConversationActivity extends XmppActivity private ArrayAdapter<Conversation> listAdapter; - private Toast prepareFileToast; - private boolean mActivityPaused = false; - private boolean mRedirected = true; + private AtomicBoolean mRedirected = new AtomicBoolean(false); + private Pair<Integer, Intent> mPostponedActivityResult; public Conversation getSelectedConversation() { return this.mSelectedConversation; @@ -131,8 +155,7 @@ public class ConversationActivity extends XmppActivity public boolean isConversationsOverviewHideable() { if (mContentView instanceof SlidingPaneLayout) { - SlidingPaneLayout mSlidingPaneLayout = (SlidingPaneLayout) mContentView; - return mSlidingPaneLayout.isSlideable(); + return true; } else { return false; } @@ -180,10 +203,11 @@ public class ConversationActivity extends XmppActivity @Override public void onItemClick(AdapterView<?> arg0, View clickedView, - int position, long arg3) { + int position, long arg3) { if (getSelectedConversation() != conversationList.get(position)) { setSelectedConversation(conversationList.get(position)); ConversationActivity.this.mConversationFragment.reInit(getSelectedConversation()); + conversationWasSelectedByKeyboard = false; } hideConversationsOverview(); openConversation(); @@ -192,64 +216,67 @@ public class ConversationActivity extends XmppActivity listView.setDismissCallback(new EnhancedListView.OnDismissCallback() { - @Override - public EnhancedListView.Undoable onDismiss(final EnhancedListView enhancedListView, final int position) { - - final int index = listView.getFirstVisiblePosition(); - View v = listView.getChildAt(0); - final int top = (v == null) ? 0 : (v.getTop() - listView.getPaddingTop()); - - swipedConversation = listAdapter.getItem(position); - listAdapter.remove(swipedConversation); - swipedConversation.markRead(); - xmppConnectionService.getNotificationService().clear(swipedConversation); - - final boolean formerlySelected = (getSelectedConversation() == swipedConversation); - if (position == 0 && listAdapter.getCount() == 0) { - endConversation(swipedConversation, false, true); - return null; - } else if (formerlySelected) { - setSelectedConversation(listAdapter.getItem(0)); - ConversationActivity.this.mConversationFragment - .reInit(getSelectedConversation()); - } - - return new EnhancedListView.Undoable() { - - @Override - public void undo() { - listAdapter.insert(swipedConversation, position); - if (formerlySelected) { - setSelectedConversation(swipedConversation); - ConversationActivity.this.mConversationFragment - .reInit(getSelectedConversation()); - } - swipedConversation = null; - listView.setSelectionFromTop(index + (listView.getChildCount() < position ? 1 : 0), top); - } - - @Override - public void discard() { - if (!swipedConversation.isRead() - && swipedConversation.getMode() == Conversation.MODE_SINGLE) { - swipedConversation = null; - return; - } - endConversation(swipedConversation, false, false); - swipedConversation = null; - } - - @Override - public String getTitle() { - if (swipedConversation.getMode() == Conversation.MODE_MULTI) { - return getResources().getString(R.string.title_undo_swipe_out_muc); - } else { - return getResources().getString(R.string.title_undo_swipe_out_conversation); - } - } - }; - } - }); + @Override + public EnhancedListView.Undoable onDismiss(final EnhancedListView enhancedListView, final int position) { + + final int index = listView.getFirstVisiblePosition(); + View v = listView.getChildAt(0); + final int top = (v == null) ? 0 : (v.getTop() - listView.getPaddingTop()); + + try { + swipedConversation = listAdapter.getItem(position); + } catch (IndexOutOfBoundsException e) { + return null; + } + listAdapter.remove(swipedConversation); + xmppConnectionService.markRead(swipedConversation); + + final boolean formerlySelected = (getSelectedConversation() == swipedConversation); + if (position == 0 && listAdapter.getCount() == 0) { + endConversation(swipedConversation, false, true); + return null; + } else if (formerlySelected) { + setSelectedConversation(listAdapter.getItem(0)); + ConversationActivity.this.mConversationFragment + .reInit(getSelectedConversation()); + } + + return new EnhancedListView.Undoable() { + + @Override + public void undo() { + listAdapter.insert(swipedConversation, position); + if (formerlySelected) { + setSelectedConversation(swipedConversation); + ConversationActivity.this.mConversationFragment + .reInit(getSelectedConversation()); + } + swipedConversation = null; + listView.setSelectionFromTop(index + (listView.getChildCount() < position ? 1 : 0), top); + } + + @Override + public void discard() { + if (!swipedConversation.isRead() + && swipedConversation.getMode() == Conversation.MODE_SINGLE) { + swipedConversation = null; + return; + } + endConversation(swipedConversation, false, false); + swipedConversation = null; + } + + @Override + public String getTitle() { + if (swipedConversation.getMode() == Conversation.MODE_MULTI) { + return getResources().getString(R.string.title_undo_swipe_out_muc); + } else { + return getResources().getString(R.string.title_undo_swipe_out_conversation); + } + } + }; + } + }); listView.enableSwipeToDismiss(); listView.setSwipeDirection(EnhancedListView.SwipeDirection.START); listView.setSwipingLayout(R.id.swipeable_item); @@ -277,7 +304,7 @@ public class ConversationActivity extends XmppActivity hideKeyboard(); if (xmppConnectionServiceBound) { xmppConnectionService.getNotificationService() - .setOpenConversation(null); + .setOpenConversation(null); } closeContextMenu(); } @@ -301,12 +328,12 @@ public class ConversationActivity extends XmppActivity public void switchToConversation(Conversation conversation) { setSelectedConversation(conversation); runOnUiThread(new Runnable() { - @Override - public void run() { - ConversationActivity.this.mConversationFragment.reInit(getSelectedConversation()); - openConversation(); - } - }); + @Override + public void run() { + ConversationActivity.this.mConversationFragment.reInit(getSelectedConversation()); + openConversation(); + } + }); } private void updateActionBarTitle() { @@ -381,7 +408,7 @@ public class ConversationActivity extends XmppActivity } else { menuAdd.setVisible(!isConversationsOverviewHideable()); if (this.getSelectedConversation() != null) { - if (this.getSelectedConversation().getNextEncryption(ConversationsPlusPreferences.forceEncryption()) != Message.ENCRYPTION_NONE) { + if (this.getSelectedConversation().getNextEncryption() != Message.ENCRYPTION_NONE) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { menuSecure.setIcon(R.drawable.ic_lock_white_24dp); } else { @@ -390,8 +417,9 @@ public class ConversationActivity extends XmppActivity } if (this.getSelectedConversation().getMode() == Conversation.MODE_MULTI) { menuContactDetails.setVisible(false); - menuAttach.setVisible(getSelectedConversation().getAccount().httpUploadAvailable()); + menuAttach.setVisible(getSelectedConversation().getAccount().httpUploadAvailable() && getSelectedConversation().getMucOptions().participating()); menuInviteContact.setVisible(getSelectedConversation().getMucOptions().canInvite()); + menuSecure.setVisible(!Config.HIDE_PGP_IN_UI && !Config.X509_VERIFICATION); //if pgp is hidden conferences have no choice of encryption } else { menuMucDetails.setVisible(false); } @@ -405,7 +433,7 @@ public class ConversationActivity extends XmppActivity return true; } - private void selectPresenceToAttachFile(final int attachmentChoice, final int encryption) { + protected void selectPresenceToAttachFile(final int attachmentChoice, final int encryption) { final Conversation conversation = getSelectedConversation(); final Account account = conversation.getAccount(); final OnPresenceSelected callback = new OnPresenceSelected() { @@ -419,7 +447,7 @@ public class ConversationActivity extends XmppActivity case ATTACHMENT_CHOICE_CHOOSE_IMAGE: intent.setAction(Intent.ACTION_GET_CONTENT); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { - intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE,true); + intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); } intent.setType("image/*"); chooser = true; @@ -469,7 +497,7 @@ public class ConversationActivity extends XmppActivity private Intent getInstallApkIntent(final String packageId) { Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setData(Uri.parse("market://details?id="+packageId)); + intent.setData(Uri.parse("market://details?id=" + packageId)); if (intent.resolveActivity(getPackageManager()) != null) { return intent; } else { @@ -479,6 +507,11 @@ public class ConversationActivity extends XmppActivity } public void attachFile(final int attachmentChoice) { + if (attachmentChoice != ATTACHMENT_CHOICE_LOCATION) { + if (!hasStoragePermission(attachmentChoice)) { + return; + } + } switch (attachmentChoice) { case ATTACHMENT_CHOICE_LOCATION: ConversationsPlusPreferences.applyRecentlyUsedQuickAction("location"); @@ -494,23 +527,23 @@ public class ConversationActivity extends XmppActivity break; } final Conversation conversation = getSelectedConversation(); - final int encryption = conversation.getNextEncryption(ConversationsPlusPreferences.forceEncryption()); + final int encryption = conversation.getNextEncryption(); + final int mode = conversation.getMode(); if (encryption == Message.ENCRYPTION_PGP) { if (hasPgp()) { - if (conversation.getContact().getPgpKeyId() != 0) { + if (mode == Conversation.MODE_SINGLE && conversation.getContact().getPgpKeyId() != 0) { xmppConnectionService.getPgpEngine().hasKey( conversation.getContact(), new UiCallback<Contact>() { @Override - public void userInputRequried(PendingIntent pi, - Contact contact) { - ConversationActivity.this.runIntent(pi,attachmentChoice); + public void userInputRequried(PendingIntent pi, Contact contact) { + ConversationActivity.this.runIntent(pi, attachmentChoice); } @Override public void success(Contact contact) { - selectPresenceToAttachFile(attachmentChoice,encryption); + selectPresenceToAttachFile(attachmentChoice, encryption); } @Override @@ -518,21 +551,31 @@ public class ConversationActivity extends XmppActivity displayErrorDialog(error); } }); + } else if (mode == Conversation.MODE_MULTI && conversation.getMucOptions().pgpKeysInUse()) { + if (!conversation.getMucOptions().everybodyHasKeys()) { + Toast warning = Toast + .makeText(this, + R.string.missing_public_keys, + Toast.LENGTH_LONG); + warning.setGravity(Gravity.CENTER_VERTICAL, 0, 0); + warning.show(); + } + selectPresenceToAttachFile(attachmentChoice, encryption); } else { final ConversationFragment fragment = (ConversationFragment) getFragmentManager() - .findFragmentByTag("conversation"); + .findFragmentByTag("conversation"); if (fragment != null) { fragment.showNoPGPKeyDialog(false, new OnClickListener() { @Override public void onClick(DialogInterface dialog, - int which) { + int which) { conversation - .setNextEncryption(Message.ENCRYPTION_NONE); + .setNextEncryption(Message.ENCRYPTION_NONE); xmppConnectionService.databaseBackend - .updateConversation(conversation); - selectPresenceToAttachFile(attachmentChoice,Message.ENCRYPTION_NONE); + .updateConversation(conversation); + selectPresenceToAttachFile(attachmentChoice, Message.ENCRYPTION_NONE); } }); } @@ -541,7 +584,40 @@ public class ConversationActivity extends XmppActivity showInstallPgpDialog(); } } else { - selectPresenceToAttachFile(attachmentChoice, encryption); + if (encryption != Message.ENCRYPTION_AXOLOTL || !trustKeysIfNeeded(REQUEST_TRUST_KEYS_MENU, attachmentChoice)) { + selectPresenceToAttachFile(attachmentChoice, encryption); + } + } + } + + @Override + public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { + if (grantResults.length > 0) + if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { + if (requestCode == REQUEST_START_DOWNLOAD) { + if (this.mPendingDownloadableMessage != null) { + startDownloadable(this.mPendingDownloadableMessage); + } + } else { + attachFile(requestCode); + } + } else { + Toast.makeText(this, R.string.no_storage_permission, Toast.LENGTH_SHORT).show(); + } + } + + public void startDownloadable(Message message) { + if (!hasStoragePermission(ConversationActivity.REQUEST_START_DOWNLOAD)) { + this.mPendingDownloadableMessage = message; + return; + } + Transferable transferable = message.getTransferable(); + if (transferable != null) { + if (!transferable.start()) { + Toast.makeText(this, R.string.not_connected_try_again, Toast.LENGTH_SHORT).show(); + } + } else if (message.treatAsDownloadable() != Message.Decision.NEVER) { + xmppConnectionService.getHttpConnectionManager().createNewDownloadConnection(message, true); } } @@ -616,6 +692,12 @@ public class ConversationActivity extends XmppActivity this.mConversationFragment.reInit(getSelectedConversation()); } else { setSelectedConversation(null); + if (mRedirected.compareAndSet(false, true)) { + Intent intent = new Intent(this, StartConversationActivity.class); + intent.putExtra("init", true); + startActivity(intent); + finish(); + } } } } @@ -627,23 +709,23 @@ public class ConversationActivity extends XmppActivity View dialogView = getLayoutInflater().inflate( R.layout.dialog_clear_history, null); final CheckBox endConversationCheckBox = (CheckBox) dialogView - .findViewById(R.id.end_conversation_checkbox); + .findViewById(R.id.end_conversation_checkbox); builder.setView(dialogView); builder.setNegativeButton(getString(R.string.cancel), null); builder.setPositiveButton(getString(R.string.delete_messages), - new OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - ConversationActivity.this.xmppConnectionService.clearConversationHistory(conversation); - if (endConversationCheckBox.isChecked()) { - endConversation(conversation); - } else { - updateConversationList(); - ConversationActivity.this.mConversationFragment.updateMessages(); - } - } - }); + new OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + ConversationActivity.this.xmppConnectionService.clearConversationHistory(conversation); + if (endConversationCheckBox.isChecked()) { + endConversation(conversation); + } else { + updateConversationList(); + ConversationActivity.this.mConversationFragment.updateMessages(); + } + } + }); builder.create().show(); } @@ -703,7 +785,7 @@ public class ConversationActivity extends XmppActivity Intent intent = new Intent(ConversationActivity.this, VerifyOTRActivity.class); intent.setAction(VerifyOTRActivity.ACTION_VERIFY_CONTACT); intent.putExtra("contact", conversation.getContact().getJid().toBareJid().toString()); - intent.putExtra("account", conversation.getAccount().getJid().toBareJid().toString()); + intent.putExtra(EXTRA_ACCOUNT, conversation.getAccount().getJid().toBareJid().toString()); switch (menuItem.getItemId()) { case R.id.scan_fingerprint: intent.putExtra("mode", VerifyOTRActivity.MODE_SCAN_FINGERPRINT); @@ -729,7 +811,7 @@ public class ConversationActivity extends XmppActivity } PopupMenu popup = new PopupMenu(this, menuItemView); final ConversationFragment fragment = (ConversationFragment) getFragmentManager() - .findFragmentByTag("conversation"); + .findFragmentByTag("conversation"); if (fragment != null) { popup.setOnMenuItemClickListener(new OnMenuItemClickListener() { @@ -746,16 +828,22 @@ public class ConversationActivity extends XmppActivity break; case R.id.encryption_choice_pgp: if (hasPgp()) { - if (conversation.getAccount().getKeys().has("pgp_signature")) { + if (conversation.getAccount().getPgpSignature() != null) { conversation.setNextEncryption(Message.ENCRYPTION_PGP); item.setChecked(true); } else { - announcePgp(conversation.getAccount(),conversation); + announcePgp(conversation.getAccount(), conversation); } } else { showInstallPgpDialog(); } break; + case R.id.encryption_choice_axolotl: + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(conversation.getAccount()) + + "Enabled axolotl for Contact " + conversation.getContact().getJid()); + conversation.setNextEncryption(Message.ENCRYPTION_AXOLOTL); + item.setChecked(true); + break; default: conversation.setNextEncryption(Message.ENCRYPTION_NONE); break; @@ -763,6 +851,7 @@ public class ConversationActivity extends XmppActivity xmppConnectionService.databaseBackend.updateConversation(conversation); fragment.updateChatMsgHint(); invalidateOptionsMenu(); + refreshUi(); return true; } }); @@ -770,15 +859,17 @@ public class ConversationActivity extends XmppActivity MenuItem otr = popup.getMenu().findItem(R.id.encryption_choice_otr); MenuItem none = popup.getMenu().findItem(R.id.encryption_choice_none); MenuItem pgp = popup.getMenu().findItem(R.id.encryption_choice_pgp); - boolean forceEncryption = ConversationsPlusPreferences.forceEncryption(); + MenuItem axolotl = popup.getMenu().findItem(R.id.encryption_choice_axolotl); + pgp.setVisible(!Config.HIDE_PGP_IN_UI && !Config.X509_VERIFICATION); + none.setVisible(!Config.FORCE_E2E_ENCRYPTION); + otr.setVisible(!Config.X509_VERIFICATION); if (conversation.getMode() == Conversation.MODE_MULTI) { - otr.setEnabled(false); - } else { - if (forceEncryption) { - none.setVisible(false); - } + otr.setVisible(false); + axolotl.setVisible(false); + } else if (!conversation.getAccount().getAxolotlService().isContactAxolotlCapable(conversation.getContact())) { + axolotl.setEnabled(false); } - switch (conversation.getNextEncryption(forceEncryption)) { + switch (conversation.getNextEncryption()) { case Message.ENCRYPTION_NONE: none.setChecked(true); break; @@ -788,6 +879,9 @@ public class ConversationActivity extends XmppActivity case Message.ENCRYPTION_PGP: pgp.setChecked(true); break; + case Message.ENCRYPTION_AXOLOTL: + axolotl.setChecked(true); + break; default: none.setChecked(true); break; @@ -799,27 +893,26 @@ public class ConversationActivity extends XmppActivity protected void muteConversationDialog(final Conversation conversation) { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(R.string.disable_notifications); - final int[] durations = getResources().getIntArray( - R.array.mute_options_durations); + final int[] durations = getResources().getIntArray(R.array.mute_options_durations); builder.setItems(R.array.mute_options_descriptions, - new OnClickListener() { - - @Override - public void onClick(final DialogInterface dialog, final int which) { - final long till; - if (durations[which] == -1) { - till = Long.MAX_VALUE; - } else { - till = System.currentTimeMillis() + (durations[which] * 1000); - } - conversation.setMutedTill(till); - ConversationActivity.this.xmppConnectionService.databaseBackend - .updateConversation(conversation); - updateConversationList(); - ConversationActivity.this.mConversationFragment.updateMessages(); - invalidateOptionsMenu(); - } - }); + new OnClickListener() { + + @Override + public void onClick(final DialogInterface dialog, final int which) { + final long till; + if (durations[which] == -1) { + till = Long.MAX_VALUE; + } else { + till = System.currentTimeMillis() + (durations[which] * 1000); + } + conversation.setMutedTill(till); + ConversationActivity.this.xmppConnectionService.databaseBackend + .updateConversation(conversation); + updateConversationList(); + ConversationActivity.this.mConversationFragment.updateMessages(); + invalidateOptionsMenu(); + } + }); builder.create().show(); } @@ -841,10 +934,119 @@ public class ConversationActivity extends XmppActivity } @Override + public boolean onKeyUp(int key, KeyEvent event) { + int rotation = getWindowManager().getDefaultDisplay().getRotation(); + final int upKey; + final int downKey; + switch (rotation) { + case Surface.ROTATION_90: + upKey = KeyEvent.KEYCODE_DPAD_LEFT; + downKey = KeyEvent.KEYCODE_DPAD_RIGHT; + break; + case Surface.ROTATION_180: + upKey = KeyEvent.KEYCODE_DPAD_DOWN; + downKey = KeyEvent.KEYCODE_DPAD_UP; + break; + case Surface.ROTATION_270: + upKey = KeyEvent.KEYCODE_DPAD_RIGHT; + downKey = KeyEvent.KEYCODE_DPAD_LEFT; + break; + default: + upKey = KeyEvent.KEYCODE_DPAD_UP; + downKey = KeyEvent.KEYCODE_DPAD_DOWN; + } + final boolean modifier = event.isCtrlPressed() || event.isAltPressed(); + if (modifier && key == KeyEvent.KEYCODE_TAB && isConversationsOverviewHideable()) { + toggleConversationsOverview(); + return true; + } else if (modifier && key == downKey) { + if (isConversationsOverviewHideable() && !isConversationsOverviewVisable()) { + showConversationsOverview(); + ; + } + return selectDownConversation(); + } else if (modifier && key == upKey) { + if (isConversationsOverviewHideable() && !isConversationsOverviewVisable()) { + showConversationsOverview(); + } + return selectUpConversation(); + } else if (modifier && key == KeyEvent.KEYCODE_1) { + return openConversationByIndex(0); + } else if (modifier && key == KeyEvent.KEYCODE_2) { + return openConversationByIndex(1); + } else if (modifier && key == KeyEvent.KEYCODE_3) { + return openConversationByIndex(2); + } else if (modifier && key == KeyEvent.KEYCODE_4) { + return openConversationByIndex(3); + } else if (modifier && key == KeyEvent.KEYCODE_5) { + return openConversationByIndex(4); + } else if (modifier && key == KeyEvent.KEYCODE_6) { + return openConversationByIndex(5); + } else if (modifier && key == KeyEvent.KEYCODE_7) { + return openConversationByIndex(6); + } else if (modifier && key == KeyEvent.KEYCODE_8) { + return openConversationByIndex(7); + } else if (modifier && key == KeyEvent.KEYCODE_9) { + return openConversationByIndex(8); + } else if (modifier && key == KeyEvent.KEYCODE_0) { + return openConversationByIndex(9); + } else { + return super.onKeyUp(key, event); + } + } + + private void toggleConversationsOverview() { + if (isConversationsOverviewVisable()) { + hideConversationsOverview(); + if (mConversationFragment != null) { + mConversationFragment.setFocusOnInputField(); + } + } else { + showConversationsOverview(); + } + } + + private boolean selectUpConversation() { + if (this.mSelectedConversation != null) { + int index = this.conversationList.indexOf(this.mSelectedConversation); + if (index > 0) { + return openConversationByIndex(index - 1); + } + } + return false; + } + + private boolean selectDownConversation() { + if (this.mSelectedConversation != null) { + int index = this.conversationList.indexOf(this.mSelectedConversation); + if (index != -1 && index < this.conversationList.size() - 1) { + return openConversationByIndex(index + 1); + } + } + return false; + } + + private boolean openConversationByIndex(int index) { + try { + this.conversationWasSelectedByKeyboard = true; + setSelectedConversation(this.conversationList.get(index)); + this.mConversationFragment.reInit(getSelectedConversation()); + if (index > listView.getLastVisiblePosition() - 1 || index < listView.getFirstVisiblePosition() + 1) { + this.listView.setSelection(index); + } + openConversation(); + return true; + } catch (IndexOutOfBoundsException e) { + return false; + } + } + + @Override protected void onNewIntent(final Intent intent) { if (xmppConnectionServiceBound) { if (intent != null && VIEW_CONVERSATION.equals(intent.getType())) { handleViewConversationIntent(intent); + setIntent(new Intent()); } } else { setIntent(intent); @@ -854,7 +1056,7 @@ public class ConversationActivity extends XmppActivity @Override public void onStart() { super.onStart(); - this.mRedirected = false; + this.mRedirected.set(false); if (this.xmppConnectionServiceBound) { this.onBackendConnected(); } @@ -896,17 +1098,26 @@ public class ConversationActivity extends XmppActivity public void onSaveInstanceState(final Bundle savedInstanceState) { Conversation conversation = getSelectedConversation(); if (conversation != null) { - savedInstanceState.putString(STATE_OPEN_CONVERSATION, - conversation.getUuid()); + savedInstanceState.putString(STATE_OPEN_CONVERSATION, conversation.getUuid()); + } else { + savedInstanceState.remove(STATE_OPEN_CONVERSATION); } - savedInstanceState.putBoolean(STATE_PANEL_OPEN, - isConversationsOverviewVisable()); + savedInstanceState.putBoolean(STATE_PANEL_OPEN, isConversationsOverviewVisable()); if (this.mPendingImageUris.size() >= 1) { savedInstanceState.putString(STATE_PENDING_URI, this.mPendingImageUris.get(0).toString()); + } else { + savedInstanceState.remove(STATE_PENDING_URI); } super.onSaveInstanceState(savedInstanceState); } + private void clearPending() { + mPendingImageUris.clear(); + mPendingFileUris.clear(); + mPendingGeoUri = null; + mPostponedActivityResult = null; + } + @Override void onBackendConnected() { this.xmppConnectionService.getNotificationService().setIsInForeground(true); @@ -918,20 +1129,23 @@ public class ConversationActivity extends XmppActivity } if (xmppConnectionService.getAccounts().size() == 0) { - if (!mRedirected) { - this.mRedirected = true; - startActivity(new Intent(this, EditAccountActivity.class)); + if (mRedirected.compareAndSet(false, true)) { + if (Config.X509_VERIFICATION) { + startActivity(new Intent(this, ManageAccountActivity.class)); + } else { + startActivity(new Intent(this, EditAccountActivity.class)); + } finish(); } } else if (conversationList.size() <= 0) { - if (!mRedirected) { - this.mRedirected = true; + if (mRedirected.compareAndSet(false, true)) { Intent intent = new Intent(this, StartConversationActivity.class); - intent.putExtra("init",true); + intent.putExtra("init", true); startActivity(intent); finish(); } } else if (getIntent() != null && VIEW_CONVERSATION.equals(getIntent().getType())) { + clearPending(); handleViewConversationIntent(getIntent()); } else if (selectConversationByUuid(mOpenConverstaion)) { if (mPanelOpen) { @@ -939,32 +1153,46 @@ public class ConversationActivity extends XmppActivity } else { if (isConversationsOverviewHideable()) { openConversation(); + updateActionBarTitle(true); } } this.mConversationFragment.reInit(getSelectedConversation()); mOpenConverstaion = null; } else if (getSelectedConversation() == null) { showConversationsOverview(); - mPendingImageUris.clear(); - mPendingFileUris.clear(); - mPendingGeoUri = null; + clearPending(); setSelectedConversation(conversationList.get(0)); this.mConversationFragment.reInit(getSelectedConversation()); + } else { + this.mConversationFragment.messageListAdapter.updatePreferences(); + this.mConversationFragment.messagesView.invalidateViews(); + this.mConversationFragment.setupIme(); } - for(Iterator<Uri> i = mPendingImageUris.iterator(); i.hasNext(); i.remove()) { - attachImageToConversation(getSelectedConversation(),i.next()); + if (this.mPostponedActivityResult != null) { + this.onActivityResult(mPostponedActivityResult.first, RESULT_OK, mPostponedActivityResult.second); } - for(Iterator<Uri> i = mPendingFileUris.iterator(); i.hasNext(); i.remove()) { - attachFileToConversation(getSelectedConversation(),i.next()); + if (!forbidProcessingPendings) { + for (Iterator<Uri> i = mPendingImageUris.iterator(); i.hasNext(); i.remove()) { + Uri foo = i.next(); + attachImageToConversation(getSelectedConversation(), foo); + } + + for (Iterator<Uri> i = mPendingFileUris.iterator(); i.hasNext(); i.remove()) { + attachFileToConversation(getSelectedConversation(), i.next()); + } + + if (mPendingGeoUri != null) { + attachLocationToConversation(getSelectedConversation(), mPendingGeoUri); + mPendingGeoUri = null; + } } + forbidProcessingPendings = false; - if (mPendingGeoUri != null) { - attachLocationToConversation(getSelectedConversation(), mPendingGeoUri); - mPendingGeoUri = null; + if (!ExceptionHelper.checkForCrash(this, this.xmppConnectionService)) { + openBatteryOptimizationDialogIfNeeded(); } - ExceptionHelper.checkForCrash(this, this.xmppConnectionService); setIntent(new Intent()); } @@ -973,10 +1201,21 @@ public class ConversationActivity extends XmppActivity final String downloadUuid = intent.getStringExtra(MESSAGE); final String text = intent.getStringExtra(TEXT); final String nick = intent.getStringExtra(NICK); + final boolean pm = intent.getBooleanExtra(PRIVATE_MESSAGE, false); if (selectConversationByUuid(uuid)) { this.mConversationFragment.reInit(getSelectedConversation()); if (nick != null) { - this.mConversationFragment.highlightInConference(nick); + if (pm) { + Jid jid = getSelectedConversation().getJid(); + try { + Jid next = Jid.fromParts(jid.getLocalpart(), jid.getDomainpart(), nick); + this.mConversationFragment.privateMessageWith(next); + } catch (final InvalidJidException ignored) { + //do nothing + } + } else { + this.mConversationFragment.highlightInConference(nick); + } } else { this.mConversationFragment.appendText(text); } @@ -988,7 +1227,7 @@ public class ConversationActivity extends XmppActivity if (downloadUuid != null) { final Message message = mSelectedConversation.findMessageWithFileAndUuid(downloadUuid); if (message != null) { - mConversationFragment.messageListAdapter.startDownloadable(message); + startDownloadable(message); } } } @@ -1016,10 +1255,13 @@ public class ConversationActivity extends XmppActivity @SuppressLint("NewApi") private static List<Uri> extractUriFromIntent(final Intent intent) { List<Uri> uris = new ArrayList<>(); + if (intent == null) { + return uris; + } Uri uri = intent.getData(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2 && uri == null) { ClipData clipData = intent.getClipData(); - for(int i = 0; i < clipData.getItemCount(); ++i) { + for (int i = 0; i < clipData.getItemCount(); ++i) { uris.add(clipData.getItemAt(i).getUri()); } } else { @@ -1029,26 +1271,46 @@ public class ConversationActivity extends XmppActivity } @Override - protected void onActivityResult(int requestCode, int resultCode, - final Intent data) { + protected void onActivityResult(int requestCode, int resultCode, final Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode == RESULT_OK) { if (requestCode == REQUEST_DECRYPT_PGP) { - mConversationFragment.hideSnackbar(); - mConversationFragment.updateMessages(); + mConversationFragment.onActivityResult(requestCode, resultCode, data); + } else if (requestCode == REQUEST_CHOOSE_PGP_ID) { + // the user chose OpenPGP for encryption and selected his key in the PGP provider + if (xmppConnectionServiceBound) { + if (data.getExtras().containsKey(OpenPgpApi.EXTRA_SIGN_KEY_ID)) { + // associate selected PGP keyId with the account + mSelectedConversation.getAccount().setPgpSignId(data.getExtras().getLong(OpenPgpApi.EXTRA_SIGN_KEY_ID)); + // we need to announce the key as described in XEP-027 + announcePgp(mSelectedConversation.getAccount(), null); + } else { + choosePgpSignId(mSelectedConversation.getAccount()); + } + this.mPostponedActivityResult = null; + } else { + this.mPostponedActivityResult = new Pair<>(requestCode, data); + } + } else if (requestCode == REQUEST_ANNOUNCE_PGP) { + if (xmppConnectionServiceBound) { + announcePgp(mSelectedConversation.getAccount(), mSelectedConversation); + this.mPostponedActivityResult = null; + } else { + this.mPostponedActivityResult = new Pair<>(requestCode, data); + } } else if (requestCode == ATTACHMENT_CHOICE_CHOOSE_IMAGE) { mPendingImageUris.clear(); mPendingImageUris.addAll(extractUriFromIntent(data)); if (xmppConnectionServiceBound) { - for(Iterator<Uri> i = mPendingImageUris.iterator(); i.hasNext(); i.remove()) { - attachImageToConversation(getSelectedConversation(),i.next()); + for (Iterator<Uri> i = mPendingImageUris.iterator(); i.hasNext(); i.remove()) { + attachImageToConversation(getSelectedConversation(), i.next()); } } } else if (requestCode == ATTACHMENT_CHOICE_CHOOSE_FILE || requestCode == ATTACHMENT_CHOICE_RECORD_VOICE) { mPendingFileUris.clear(); mPendingFileUris.addAll(extractUriFromIntent(data)); if (xmppConnectionServiceBound) { - for(Iterator<Uri> i = mPendingFileUris.iterator(); i.hasNext(); i.remove()) { + for (Iterator<Uri> i = mPendingFileUris.iterator(); i.hasNext(); i.remove()) { attachFileToConversation(getSelectedConversation(), i.next()); } } @@ -1066,78 +1328,105 @@ public class ConversationActivity extends XmppActivity mPendingImageUris.clear(); } } else if (requestCode == ATTACHMENT_CHOICE_LOCATION) { - double latitude = data.getDoubleExtra("latitude",0); - double longitude = data.getDoubleExtra("longitude",0); - this.mPendingGeoUri = Uri.parse("geo:"+String.valueOf(latitude)+","+String.valueOf(longitude)); + double latitude = data.getDoubleExtra("latitude", 0); + double longitude = data.getDoubleExtra("longitude", 0); + this.mPendingGeoUri = Uri.parse("geo:" + String.valueOf(latitude) + "," + String.valueOf(longitude)); if (xmppConnectionServiceBound) { attachLocationToConversation(getSelectedConversation(), mPendingGeoUri); this.mPendingGeoUri = null; } + } else if (requestCode == REQUEST_TRUST_KEYS_TEXT || requestCode == REQUEST_TRUST_KEYS_MENU) { + this.forbidProcessingPendings = !xmppConnectionServiceBound; + if (xmppConnectionServiceBound) { + mConversationFragment.onActivityResult(requestCode, resultCode, data); + this.mPostponedActivityResult = null; + } else { + this.mPostponedActivityResult = new Pair<>(requestCode, data); + } + } } else { - mPendingImageUris.clear(); - mPendingFileUris.clear(); + if (requestCode == ATTACHMENT_CHOICE_TAKE_PHOTO) { + mPendingImageUri = null; + } } } private void attachLocationToConversation(Conversation conversation, Uri uri) { - if (conversation == null) { - return; - } - xmppConnectionService.attachLocationToConversation(conversation, uri, new UiCallback<Message>() { + xmppConnectionService.attachLocationToConversation(conversation,uri, new UiCallback<Message>() { - @Override - public void success(Message message) { - xmppConnectionService.sendMessage(message); - } + @Override + public void success(Message message) { + xmppConnectionService.sendMessage(message); + } - @Override - public void error(int errorCode, Message object) { + @Override + public void error(int errorCode, Message object) { - } + } - @Override - public void userInputRequried(PendingIntent pi, Message object) { + @Override + public void userInputRequried(PendingIntent pi, Message object) { - } - }); + } + }); } private void attachFileToConversation(Conversation conversation, Uri uri) { if (conversation == null) { return; } - prepareFileToast = Toast.makeText(getApplicationContext(),getText(R.string.preparing_file), Toast.LENGTH_LONG); + final Toast prepareFileToast = Toast.makeText(getApplicationContext(),getText(R.string.preparing_file), Toast.LENGTH_LONG); prepareFileToast.show(); xmppConnectionService.attachFileToConversation(conversation, uri, new UiCallback<Message>() { - @Override - public void success(Message message) { - hidePrepareFileToast(); - xmppConnectionService.sendMessage(message); - } + @Override + public void success(Message message) { + hidePrepareFileToast(prepareFileToast); + xmppConnectionService.sendMessage(message); + } - @Override - public void error(int errorCode, Message message) { - displayErrorDialog(errorCode); - } + @Override + public void error(int errorCode, Message message) { + hidePrepareFileToast(prepareFileToast); + displayErrorDialog(errorCode); + } - @Override - public void userInputRequried(PendingIntent pi, Message message) { + @Override + public void userInputRequried(PendingIntent pi, Message message) { - } - }); + } + }); } private void attachImageToConversation(Conversation conversation, Uri uri) { if (conversation == null) { return; } - ResizePictureUserDecisionListener userDecisionListener = new ResizePictureUserDecisionListener(this, conversation, uri, xmppConnectionService); - UserDecisionDialog userDecisionDialog = new UserDecisionDialog(this, R.string.userdecision_question_resize_picture, userDecisionListener); - userDecisionDialog.decide(ConversationsPlusPreferences.resizePicture()); + final Toast prepareFileToast = Toast.makeText(getApplicationContext(),getText(R.string.preparing_image), Toast.LENGTH_LONG); + prepareFileToast.show(); + xmppConnectionService.attachImageToConversation(conversation, uri, + new UiCallback<Message>() { + + @Override + public void userInputRequried(PendingIntent pi, Message object) { + hidePrepareFileToast(prepareFileToast); + } + + @Override + public void success(Message message) { + hidePrepareFileToast(prepareFileToast); + xmppConnectionService.sendMessage(message); + } + + @Override + public void error(int error, Message message) { + hidePrepareFileToast(prepareFileToast); + displayErrorDialog(error); + } + }); } - private void hidePrepareFileToast() { + private void hidePrepareFileToast(final Toast prepareFileToast) { if (prepareFileToast != null) { runOnUiThread(new Runnable() { @@ -1176,7 +1465,7 @@ public class ConversationActivity extends XmppActivity @Override public void userInputRequried(PendingIntent pi, - Message message) { + Message message) { ConversationActivity.this.runIntent(pi, ConversationActivity.REQUEST_SEND_MESSAGE); } @@ -1194,26 +1483,50 @@ public class ConversationActivity extends XmppActivity }); } + public boolean useSendButtonToIndicateStatus() { + return getPreferences().getBoolean("send_button_status", false); + } + + public boolean indicateReceived() { + return getPreferences().getBoolean("indicate_received", false); + } + + public boolean useWhiteBackground() { + return getPreferences().getBoolean("use_white_background",false); + } + + protected boolean trustKeysIfNeeded(int requestCode) { + return trustKeysIfNeeded(requestCode, ATTACHMENT_CHOICE_INVALID); + } + + protected boolean trustKeysIfNeeded(int requestCode, int attachmentChoice) { + AxolotlService axolotlService = mSelectedConversation.getAccount().getAxolotlService(); + Contact contact = mSelectedConversation.getContact(); + boolean hasUndecidedOwn = !axolotlService.getKeysWithTrust(XmppAxolotlSession.Trust.UNDECIDED).isEmpty(); + boolean hasUndecidedContact = !axolotlService.getKeysWithTrust(XmppAxolotlSession.Trust.UNDECIDED,contact).isEmpty(); + boolean hasPendingKeys = !axolotlService.findDevicesWithoutSession(mSelectedConversation).isEmpty(); + boolean hasNoTrustedKeys = axolotlService.getNumTrustedKeys(mSelectedConversation.getContact()) == 0; + if(hasUndecidedOwn || hasUndecidedContact || hasPendingKeys || hasNoTrustedKeys) { + axolotlService.createSessionsIfNeeded(mSelectedConversation); + Intent intent = new Intent(getApplicationContext(), TrustKeysActivity.class); + intent.putExtra("contact", mSelectedConversation.getContact().getJid().toBareJid().toString()); + intent.putExtra(EXTRA_ACCOUNT, mSelectedConversation.getAccount().getJid().toBareJid().toString()); + intent.putExtra("choice", attachmentChoice); + intent.putExtra("has_no_trusted", hasNoTrustedKeys); + startActivityForResult(intent, requestCode); + return true; + } else { + return false; + } + } + @Override protected void refreshUiReal() { updateConversationList(); - if (xmppConnectionService != null && xmppConnectionService.getAccounts().size() == 0) { - if (!mRedirected) { - this.mRedirected = true; - startActivity(new Intent(this, EditAccountActivity.class)); - finish(); - } - } else if (conversationList.size() == 0) { - if (!mRedirected) { - this.mRedirected = true; - Intent intent = new Intent(this, StartConversationActivity.class); - intent.putExtra("init",true); - startActivity(intent); - finish(); - } - } else { + if (conversationList.size() > 0) { ConversationActivity.this.mConversationFragment.updateMessages(); updateActionBarTitle(); + invalidateOptionsMenu(); } } @@ -1235,18 +1548,16 @@ public class ConversationActivity extends XmppActivity @Override public void OnUpdateBlocklist(Status status) { this.refreshUi(); - runOnUiThread(new Runnable() { - @Override - public void run() { - invalidateOptionsMenu(); - } - }); } public void unblockConversation(final Blockable conversation) { xmppConnectionService.sendUnblockRequest(conversation); } + public boolean enterIsSend() { + return getPreferences().getBoolean("enter_is_send",false); + } + @Override public void onShowErrorToast(final int resId) { runOnUiThread(new Runnable() { @@ -1256,4 +1567,8 @@ public class ConversationActivity extends XmppActivity } }); } + + public boolean highlightSelectedConversations() { + return !isConversationsOverviewHideable() || this.conversationWasSelectedByKeyboard; + } } diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index 670d2c38..568189a8 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -1,5 +1,6 @@ package eu.siacs.conversations.ui; +import android.app.Activity; import android.app.AlertDialog; import android.app.Fragment; import android.app.PendingIntent; @@ -10,6 +11,7 @@ import android.content.Intent; import android.content.IntentSender; import android.content.IntentSender.SendIntentException; import android.os.Bundle; +import android.support.annotation.Nullable; import android.text.InputType; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; @@ -22,6 +24,8 @@ import android.view.View.OnClickListener; import android.view.ViewGroup; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; +import android.widget.AbsListView; +import android.widget.AbsListView.OnScrollListener; import android.widget.AdapterView; import android.widget.AdapterView.AdapterContextMenuInfo; import android.widget.ImageButton; @@ -38,6 +42,7 @@ import com.orangegangsters.github.swipyrefreshlayout.library.SwipyRefreshLayout; import net.java.otr4j.session.SessionStatus; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.NoSuchElementException; import java.util.concurrent.ConcurrentLinkedQueue; @@ -49,6 +54,7 @@ import eu.siacs.conversations.ui.listeners.ConversationSwipeRefreshListener; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.PgpEngine; +import eu.siacs.conversations.crypto.axolotl.AxolotlService; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; @@ -112,7 +118,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa }; protected ListView messagesView; protected SwipyRefreshLayout swipeLayout; - final protected List<Message> messageList = new ArrayList<>(); + final protected List<Message> messageList = new ArrayList<>(); protected MessageAdapter messageListAdapter; private EditMessage mEditMessage; private ImageButton mSendButton; @@ -122,21 +128,137 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa private RelativeLayout snackbar; private TextView snackbarMessage; private TextView snackbarAction; - private IntentSender askForPassphraseIntent = null; + private boolean messagesLoaded = true; + private Toast messageLoaderToast; + + private OnScrollListener mOnScrollListener = new OnScrollListener() { + + @Override + public void onScrollStateChanged(AbsListView view, int scrollState) { + // TODO Auto-generated method stub + + } + + private int getIndexOf(String uuid, List<Message> messages) { + if (uuid == null) { + return 0; + } + for(int i = 0; i < messages.size(); ++i) { + if (uuid.equals(messages.get(i).getUuid())) { + return i; + } else { + Message next = messages.get(i); + while(next != null && next.wasMergedIntoPrevious()) { + if (uuid.equals(next.getUuid())) { + return i; + } + next = next.next(); + } + + } + } + return 0; + } + + @Override + public void onScroll(AbsListView view, int firstVisibleItem, + int visibleItemCount, int totalItemCount) { + synchronized (ConversationFragment.this.messageList) { + if (firstVisibleItem < 5 && messagesLoaded && messageList.size() > 0) { + long timestamp = ConversationFragment.this.messageList.get(0).getTimeSent(); + messagesLoaded = false; + activity.xmppConnectionService.loadMoreMessages(conversation, timestamp, new XmppConnectionService.OnMoreMessagesLoaded() { + @Override + public void onMoreMessagesLoaded(final int c, Conversation conversation) { + if (ConversationFragment.this.conversation != conversation) { + return; + } + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + final int oldPosition = messagesView.getFirstVisiblePosition(); + Message message = messageList.get(oldPosition); + String uuid = message != null ? message.getUuid() : null; + View v = messagesView.getChildAt(0); + final int pxOffset = (v == null) ? 0 : v.getTop(); + ConversationFragment.this.conversation.populateWithMessages(ConversationFragment.this.messageList); + updateStatusMessages(); + messageListAdapter.notifyDataSetChanged(); + int pos = getIndexOf(uuid,messageList); + messagesView.setSelectionFromTop(pos, pxOffset); + messagesLoaded = true; + if (messageLoaderToast != null) { + messageLoaderToast.cancel(); + } + } + }); + } + + @Override + public void informUser(final int resId) { + + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + if (messageLoaderToast != null) { + messageLoaderToast.cancel(); + } + if (ConversationFragment.this.conversation != conversation) { + return; + } + messageLoaderToast = Toast.makeText(activity, resId, Toast.LENGTH_LONG); + messageLoaderToast.show(); + } + }); + + } + }); + + } + } + } + }; + private final int KEYCHAIN_UNLOCK_NOT_REQUIRED = 0; + private final int KEYCHAIN_UNLOCK_REQUIRED = 1; + private final int KEYCHAIN_UNLOCK_PENDING = 2; + private int keychainUnlock = KEYCHAIN_UNLOCK_NOT_REQUIRED; protected OnClickListener clickToDecryptListener = new OnClickListener() { @Override public void onClick(View v) { - if (activity.hasPgp() && askForPassphraseIntent != null) { - try { - getActivity().startIntentSenderForResult( - askForPassphraseIntent, - ConversationActivity.REQUEST_DECRYPT_PGP, null, 0, - 0, 0); - askForPassphraseIntent = null; - } catch (SendIntentException e) { - // + if (keychainUnlock == KEYCHAIN_UNLOCK_REQUIRED + && activity.hasPgp() && !conversation.getAccount().getPgpDecryptionService().isRunning()) { + keychainUnlock = KEYCHAIN_UNLOCK_PENDING; + updateSnackBar(conversation); + Message message = getLastPgpDecryptableMessage(); + if (message != null) { + activity.xmppConnectionService.getPgpEngine().decrypt(message, new UiCallback<Message>() { + @Override + public void success(Message object) { + conversation.getAccount().getPgpDecryptionService().onKeychainUnlocked(); + keychainUnlock = KEYCHAIN_UNLOCK_NOT_REQUIRED; + } + + @Override + public void error(int errorCode, Message object) { + keychainUnlock = KEYCHAIN_UNLOCK_NOT_REQUIRED; + } + + @Override + public void userInputRequried(PendingIntent pi, Message object) { + try { + activity.startIntentSenderForResult(pi.getIntentSender(), + ConversationActivity.REQUEST_DECRYPT_PGP, null, 0, 0, 0); + } catch (SendIntentException e) { + keychainUnlock = KEYCHAIN_UNLOCK_NOT_REQUIRED; + updatePgpMessages(); + } + } + }); } + } else { + keychainUnlock = KEYCHAIN_UNLOCK_NOT_REQUIRED; + updatePgpMessages(); } } }; @@ -147,8 +269,6 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa activity.verifyOtrSessionDialog(conversation, v); } }; - private ConcurrentLinkedQueue<Message> mEncryptedMessages = new ConcurrentLinkedQueue<>(); - private boolean mDecryptJobRunning = false; private OnEditorActionListener mEditorActionListener = new OnEditorActionListener() { @Override @@ -156,7 +276,9 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa if (actionId == EditorInfo.IME_ACTION_SEND) { InputMethodManager imm = (InputMethodManager) v.getContext() .getSystemService(Context.INPUT_METHOD_SERVICE); - imm.hideSoftInputFromWindow(v.getWindowToken(), 0); + if (imm.isFullscreenMode()) { + imm.hideSoftInputFromWindow(v.getWindowToken(), 0); + } sendMessage(); return true; } else { @@ -217,38 +339,55 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa if (body.length() == 0 || this.conversation == null) { return; } - boolean forceEncryption = ConversationsPlusPreferences.forceEncryption(); - Message message = new Message(conversation, body, conversation.getNextEncryption(forceEncryption)); + Message message = new Message(conversation, body, conversation.getNextEncryption()); if (conversation.getMode() == Conversation.MODE_MULTI) { if (conversation.getNextCounterpart() != null) { message.setCounterpart(conversation.getNextCounterpart()); message.setType(Message.TYPE_PRIVATE); } } - if (conversation.getNextEncryption(forceEncryption) == Message.ENCRYPTION_OTR) { - sendOtrMessage(message); - } else if (conversation.getNextEncryption(forceEncryption) == Message.ENCRYPTION_PGP) { - sendPgpMessage(message); - } else { - sendPlainTextMessage(message); + switch (conversation.getNextEncryption()) { + case Message.ENCRYPTION_OTR: + sendOtrMessage(message); + break; + case Message.ENCRYPTION_PGP: + sendPgpMessage(message); + break; + case Message.ENCRYPTION_AXOLOTL: + if(!activity.trustKeysIfNeeded(ConversationActivity.REQUEST_TRUST_KEYS_TEXT)) { + sendAxolotlMessage(message); + } + break; + default: + sendPlainTextMessage(message); } } public void updateChatMsgHint() { - if (conversation.getMode() == Conversation.MODE_MULTI - && conversation.getNextCounterpart() != null) { + final boolean multi = conversation.getMode() == Conversation.MODE_MULTI; + if (multi && conversation.getNextCounterpart() != null) { this.mEditMessage.setHint(getString( - R.string.send_private_message_to, - conversation.getNextCounterpart().getResourcepart())); + R.string.send_private_message_to, + conversation.getNextCounterpart().getResourcepart())); + } else if (multi && !conversation.getMucOptions().participating()) { + this.mEditMessage.setHint(R.string.you_are_not_participating); } else { - switch (conversation.getNextEncryption(ConversationsPlusPreferences.forceEncryption())) { + switch (conversation.getNextEncryption()) { case Message.ENCRYPTION_NONE: mEditMessage - .setHint(getString(R.string.send_plain_text_message)); + .setHint(getString(R.string.send_unencrypted_message)); break; case Message.ENCRYPTION_OTR: mEditMessage.setHint(getString(R.string.send_otr_message)); break; + case Message.ENCRYPTION_AXOLOTL: + AxolotlService axolotlService = conversation.getAccount().getAxolotlService(); + if (axolotlService != null && axolotlService.trustedSessionVerified(conversation)) { + mEditMessage.setHint(getString(R.string.send_omemo_x509_message)); + } else { + mEditMessage.setHint(getString(R.string.send_omemo_message)); + } + break; case Message.ENCRYPTION_PGP: mEditMessage.setHint(getString(R.string.send_pgp_message)); break; @@ -259,21 +398,26 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa } } - private void setupIme() { - if (ConversationsPlusPreferences.displayEnterKey()) { + public void setupIme() { + if (activity == null) { + return; + } else if (activity.usingEnterKey() && ConversationsPlusPreferences.enterIsSend()) { + mEditMessage.setInputType(mEditMessage.getInputType() & (~InputType.TYPE_TEXT_FLAG_MULTI_LINE)); + mEditMessage.setInputType(mEditMessage.getInputType() & (~InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE)); + } else if (activity.usingEnterKey()) { + mEditMessage.setInputType(mEditMessage.getInputType() | InputType.TYPE_TEXT_FLAG_MULTI_LINE); mEditMessage.setInputType(mEditMessage.getInputType() & (~InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE)); } else { + mEditMessage.setInputType(mEditMessage.getInputType() | InputType.TYPE_TEXT_FLAG_MULTI_LINE); mEditMessage.setInputType(mEditMessage.getInputType() | InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE); } } @Override - public View onCreateView(final LayoutInflater inflater, - ViewGroup container, Bundle savedInstanceState) { + public View onCreateView(final LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final View view = inflater.inflate(R.layout.fragment_conversation, container, false); view.setOnClickListener(null); mEditMessage = (EditMessage) view.findViewById(R.id.textinput); - setupIme(); mEditMessage.setOnClickListener(new OnClickListener() { @Override @@ -394,6 +538,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa snackbarAction = (TextView) view.findViewById(R.id.snackbar_action); messagesView = (ListView) view.findViewById(R.id.messages_view); + messagesView.setOnScrollListener(mOnScrollListener); messagesView.setTranscriptMode(ListView.TRANSCRIPT_MODE_NORMAL); messageListAdapter = new MessageAdapter((ConversationActivity) getActivity(), this.messageList); messageListAdapter.setOnContactPictureClicked(new OnContactPictureClicked() { @@ -403,19 +548,20 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa if (message.getStatus() <= Message.STATUS_RECEIVED) { if (message.getConversation().getMode() == Conversation.MODE_MULTI) { if (message.getCounterpart() != null) { - if (!message.getCounterpart().isBareJid()) { - highlightInConference(message.getCounterpart().getResourcepart()); - } else { - highlightInConference(message.getCounterpart().toString()); + String user = message.getCounterpart().isBareJid() ? message.getCounterpart().toString() : message.getCounterpart().getResourcepart(); + if (!message.getConversation().getMucOptions().isUserInRoom(user)) { + Toast.makeText(activity,activity.getString(R.string.user_has_left_conference,user),Toast.LENGTH_SHORT).show(); } + highlightInConference(user); } } else { - activity.switchToContactDetails(message.getContact()); + activity.switchToContactDetails(message.getContact(), message.getAxolotlFingerprint()); } } else { Account account = message.getConversation().getAccount(); Intent intent = new Intent(activity, EditAccountActivity.class); intent.putExtra("jid", account.getJid().toBareJid().toString()); + intent.putExtra("fingerprint", message.getAxolotlFingerprint()); startActivity(intent); } } @@ -428,7 +574,14 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa if (message.getStatus() <= Message.STATUS_RECEIVED) { if (message.getConversation().getMode() == Conversation.MODE_MULTI) { if (message.getCounterpart() != null) { - privateMessageWith(message.getCounterpart()); + String user = message.getCounterpart().getResourcepart(); + if (user != null) { + if (message.getConversation().getMucOptions().isUserInRoom(user)) { + privateMessageWith(message.getCounterpart()); + } else { + Toast.makeText(activity, activity.getString(R.string.user_has_left_conference, user), Toast.LENGTH_SHORT).show(); + } + } } } } else { @@ -491,7 +644,8 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa || m.treatAsDownloadable() == Message.Decision.MUST) { copyUrl.setVisible(true); } - if (m.getType() == Message.TYPE_TEXT && m.getTransferable() == null && m.treatAsDownloadable() != Message.Decision.NEVER) { + if ((m.getType() == Message.TYPE_TEXT && m.getTransferable() == null && m.treatAsDownloadable() != Message.Decision.NEVER) + || (m.isFileOrImage() && m.getTransferable() instanceof TransferablePlaceholder && m.hasFileOnRemoteHost())){ downloadFile.setVisible(true); downloadFile.setTitle(activity.getString(R.string.download_x_file,UIHelper.getFileDescriptionString(activity, m))); } @@ -506,9 +660,6 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa @Override public boolean onContextItemSelected(MenuItem item) { switch (item.getItemId()) { - case R.id.msg_ctx_mnu_details: - new MessageDetailsDialog(getActivity(), selectedMessage).show(); - return true; case R.id.share_with: shareWith(selectedMessage); return true; @@ -539,11 +690,13 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa shareIntent.putExtra(Intent.EXTRA_TEXT, message.getBody()); shareIntent.setType("text/plain"); } else { - shareIntent.putExtra(Intent.EXTRA_STREAM, FileBackend.getJingleFileUri(message)); + shareIntent.putExtra(Intent.EXTRA_STREAM, + activity.xmppConnectionService.getFileBackend() + .getJingleFileUri(message)); shareIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); String mime = message.getMimeType(); if (mime == null) { - mime = "image/webp"; + mime = "*/*"; } shareIntent.setType(mime); } @@ -596,7 +749,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa private void downloadFile(Message message) { activity.xmppConnectionService.getHttpConnectionManager() - .createNewDownloadConnection(message); + .createNewDownloadConnection(message,true); } private void cancelTransmission(Message message) { @@ -631,7 +784,6 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa @Override public void onStop() { - mDecryptJobRunning = false; super.onStop(); if (this.conversation != null) { final String msg = mEditMessage.getText().toString(); @@ -652,9 +804,8 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa if (conversation == null) { return; } - this.activity = (ConversationActivity) getActivity(); - + setupIme(); if (this.conversation != null) { final String msg = mEditMessage.getText().toString(); this.conversation.setNextMessage(msg); @@ -664,19 +815,22 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa this.conversation.trim(); } - this.askForPassphraseIntent = null; + this.keychainUnlock = KEYCHAIN_UNLOCK_NOT_REQUIRED; this.conversation = conversation; - this.mDecryptJobRunning = false; - this.mEncryptedMessages.clear(); if (this.conversation.getMode() == Conversation.MODE_MULTI) { this.conversation.setNextCounterpart(null); } + boolean canWrite = this.conversation.getMode() == Conversation.MODE_SINGLE || this.conversation.getMucOptions().participating(); + this.mEditMessage.setEnabled(canWrite); + this.mSendButton.setEnabled(canWrite); this.mEditMessage.setKeyboardListener(null); this.mEditMessage.setText(""); this.mEditMessage.append(this.conversation.getNextMessage()); this.mEditMessage.setKeyboardListener(this); + messageListAdapter.updatePreferences(); this.messagesView.setAdapter(messageListAdapter); updateMessages(); + this.messagesLoaded = true; int size = this.messageList.size(); if (size > 0) { messagesView.setSelection(size - 1); @@ -713,21 +867,13 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa } }; - private OnClickListener mUnmuteClickListener = new OnClickListener() { - - @Override - public void onClick(final View v) { - activity.unmuteConversation(conversation); - } - }; - private OnClickListener mAnswerSmpClickListener = new OnClickListener() { @Override public void onClick(View view) { Intent intent = new Intent(activity, VerifyOTRActivity.class); intent.setAction(VerifyOTRActivity.ACTION_VERIFY_CONTACT); intent.putExtra("contact", conversation.getContact().getJid().toBareJid().toString()); - intent.putExtra("account", conversation.getAccount().getJid().toBareJid().toString()); + intent.putExtra(VerifyOTRActivity.EXTRA_ACCOUNT, conversation.getAccount().getJid().toBareJid().toString()); intent.putExtra("mode", VerifyOTRActivity.MODE_ANSWER_QUESTION); startActivity(intent); } @@ -748,7 +894,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa case MucOptions.ERROR_NICK_IN_USE: showSnackbar(R.string.nick_in_use, R.string.edit, clickToMuc); break; - case MucOptions.ERROR_UNKNOWN: + case MucOptions.ERROR_NO_RESPONSE: showSnackbar(R.string.conference_not_found, R.string.leave, leaveMuc); break; case MucOptions.ERROR_PASSWORD_REQUIRED: @@ -763,10 +909,13 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa case MucOptions.KICKED_FROM_ROOM: showSnackbar(R.string.conference_kicked, R.string.join, joinMuc); break; + case MucOptions.ERROR_UNKNOWN: + showSnackbar(R.string.conference_unknown_error, R.string.try_again, joinMuc); + break; default: break; } - } else if (askForPassphraseIntent != null) { + } else if (keychainUnlock == KEYCHAIN_UNLOCK_REQUIRED) { showSnackbar(R.string.openpgp_messages_found, R.string.decrypt, clickToDecryptListener); } else if (mode == Conversation.MODE_SINGLE && conversation.smpRequested()) { @@ -776,8 +925,6 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa && (conversation.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) && (!conversation.isOtrFingerprintVerified())) { showSnackbar(R.string.unknown_otr_fingerprint, R.string.verify, clickToVerify); - } else if (conversation.isMuted()) { - showSnackbar(R.string.notifications_disabled, R.string.enable, this.mUnmuteClickListener); } else { hideSnackbar(); } @@ -790,19 +937,9 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa } final ConversationActivity activity = (ConversationActivity) getActivity(); if (this.conversation != null) { - updateSnackBar(this.conversation); conversation.populateWithMessages(ConversationFragment.this.messageList); - for (final Message message : this.messageList) { - if (message.getEncryption() == Message.ENCRYPTION_PGP - && (message.getStatus() == Message.STATUS_RECEIVED || message - .getStatus() >= Message.STATUS_SEND) - && message.getTransferable() == null) { - if (!mEncryptedMessages.contains(message)) { - mEncryptedMessages.add(message); - } - } - } - decryptNext(); + updatePgpMessages(); + updateSnackBar(conversation); updateStatusMessages(); this.messageListAdapter.notifyDataSetChanged(); updateChatMsgHint(); @@ -814,46 +951,27 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa } } - private void decryptNext() { - Message next = this.mEncryptedMessages.peek(); - PgpEngine engine = activity.xmppConnectionService.getPgpEngine(); - - if (next != null && engine != null && !mDecryptJobRunning) { - mDecryptJobRunning = true; - engine.decrypt(next, new UiCallback<Message>() { - - @Override - public void userInputRequried(PendingIntent pi, Message message) { - mDecryptJobRunning = false; - askForPassphraseIntent = pi.getIntentSender(); - updateSnackBar(conversation); - } - - @Override - public void success(Message message) { - mDecryptJobRunning = false; - try { - mEncryptedMessages.remove(); - } catch (final NoSuchElementException ignored) { - - } - askForPassphraseIntent = null; - activity.xmppConnectionService.updateMessage(message); - } - - @Override - public void error(int error, Message message) { - message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED); - mDecryptJobRunning = false; - try { - mEncryptedMessages.remove(); - } catch (final NoSuchElementException ignored) { + public void updatePgpMessages() { + if (keychainUnlock != KEYCHAIN_UNLOCK_PENDING) { + if (getLastPgpDecryptableMessage() != null + && !conversation.getAccount().getPgpDecryptionService().isRunning()) { + keychainUnlock = KEYCHAIN_UNLOCK_REQUIRED; + } else { + keychainUnlock = KEYCHAIN_UNLOCK_NOT_REQUIRED; + } + } + } - } - activity.xmppConnectionService.updateConversationUi(); - } - }); + @Nullable + private Message getLastPgpDecryptableMessage() { + for (final Message message : this.messageList) { + if (message.getEncryption() == Message.ENCRYPTION_PGP + && (message.getStatus() == Message.STATUS_RECEIVED || message.getStatus() >= Message.STATUS_SEND) + && message.getTransferable() == null) { + return message; + } } + return null; } private void messageSent() { @@ -863,6 +981,10 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa updateChatMsgHint(); } + public void setFocusOnInputField() { + mEditMessage.requestFocus(); + } + enum SendButtonAction {TEXT, TAKE_PHOTO, SEND_LOCATION, RECORD_VOICE, CANCEL, CHOOSE_PICTURE} private int getSendButtonImageResource(SendButtonAction action, int status) { @@ -875,6 +997,9 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa case Presences.AWAY: return R.drawable.ic_send_text_away; case Presences.XA: + this.mSendButton + .setImageResource(R.drawable.ic_action_send_now_away); + break; case Presences.DND: return R.drawable.ic_send_text_dnd; default: @@ -1008,7 +1133,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa this.mSendButton.setImageResource(getSendButtonImageResource(action, status)); } - public void updateStatusMessages() { + protected void updateStatusMessages() { synchronized (this.messageList) { if (conversation.getMode() == Conversation.MODE_SINGLE) { ChatState state = conversation.getIncomingChatState(); @@ -1058,81 +1183,85 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa final ConversationActivity activity = (ConversationActivity) getActivity(); final XmppConnectionService xmppService = activity.xmppConnectionService; final Contact contact = message.getConversation().getContact(); - if (activity.hasPgp()) { - if (conversation.getMode() == Conversation.MODE_SINGLE) { - if (contact.getPgpKeyId() != 0) { - xmppService.getPgpEngine().hasKey(contact, - new UiCallback<Contact>() { - - @Override - public void userInputRequried(PendingIntent pi, - Contact contact) { - activity.runIntent( - pi, - ConversationActivity.REQUEST_ENCRYPT_MESSAGE); - } - - @Override - public void success(Contact contact) { - messageSent(); - activity.encryptTextMessage(message); - } - - @Override - public void error(int error, Contact contact) { + if (!activity.hasPgp()) { + activity.showInstallPgpDialog(); + return; + } + if (conversation.getAccount().getPgpSignature() == null) { + activity.announcePgp(conversation.getAccount(), conversation); + return; + } + if (conversation.getMode() == Conversation.MODE_SINGLE) { + if (contact.getPgpKeyId() != 0) { + xmppService.getPgpEngine().hasKey(contact, + new UiCallback<Contact>() { + + @Override + public void userInputRequried(PendingIntent pi, + Contact contact) { + activity.runIntent( + pi, + ConversationActivity.REQUEST_ENCRYPT_MESSAGE); + } - } - }); + @Override + public void success(Contact contact) { + messageSent(); + activity.encryptTextMessage(message); + } - } else { - showNoPGPKeyDialog(false, - new DialogInterface.OnClickListener() { + @Override + public void error(int error, Contact contact) { + System.out.println(); + } + }); - @Override - public void onClick(DialogInterface dialog, - int which) { - conversation - .setNextEncryption(Message.ENCRYPTION_NONE); - xmppService.databaseBackend - .updateConversation(conversation); - message.setEncryption(Message.ENCRYPTION_NONE); - xmppService.sendMessage(message); - messageSent(); - } - }); - } } else { - if (conversation.getMucOptions().pgpKeysInUse()) { - if (!conversation.getMucOptions().everybodyHasKeys()) { - Toast warning = Toast - .makeText(getActivity(), - R.string.missing_public_keys, - Toast.LENGTH_LONG); - warning.setGravity(Gravity.CENTER_VERTICAL, 0, 0); - warning.show(); - } - activity.encryptTextMessage(message); - messageSent(); - } else { - showNoPGPKeyDialog(true, - new DialogInterface.OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, - int which) { - conversation - .setNextEncryption(Message.ENCRYPTION_NONE); - message.setEncryption(Message.ENCRYPTION_NONE); - xmppService.databaseBackend - .updateConversation(conversation); - xmppService.sendMessage(message); - messageSent(); - } - }); - } + showNoPGPKeyDialog(false, + new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, + int which) { + conversation + .setNextEncryption(Message.ENCRYPTION_NONE); + xmppService.databaseBackend + .updateConversation(conversation); + message.setEncryption(Message.ENCRYPTION_NONE); + xmppService.sendMessage(message); + messageSent(); + } + }); } } else { - activity.showInstallPgpDialog(); + if (conversation.getMucOptions().pgpKeysInUse()) { + if (!conversation.getMucOptions().everybodyHasKeys()) { + Toast warning = Toast + .makeText(getActivity(), + R.string.missing_public_keys, + Toast.LENGTH_LONG); + warning.setGravity(Gravity.CENTER_VERTICAL, 0, 0); + warning.show(); + } + activity.encryptTextMessage(message); + messageSent(); + } else { + showNoPGPKeyDialog(true, + new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, + int which) { + conversation + .setNextEncryption(Message.ENCRYPTION_NONE); + message.setEncryption(Message.ENCRYPTION_NONE); + xmppService.databaseBackend + .updateConversation(conversation); + xmppService.sendMessage(message); + messageSent(); + } + }); + } } } @@ -1153,19 +1282,26 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa builder.create().show(); } + protected void sendAxolotlMessage(final Message message) { + final ConversationActivity activity = (ConversationActivity) getActivity(); + final XmppConnectionService xmppService = activity.xmppConnectionService; + xmppService.sendMessage(message); + messageSent(); + } + protected void sendOtrMessage(final Message message) { final ConversationActivity activity = (ConversationActivity) getActivity(); final XmppConnectionService xmppService = activity.xmppConnectionService; activity.selectPresence(message.getConversation(), - new OnPresenceSelected() { + new OnPresenceSelected() { - @Override - public void onPresenceSelected() { - message.setCounterpart(conversation.getNextCounterpart()); - xmppService.sendMessage(message); - messageSent(); - } - }); + @Override + public void onPresenceSelected() { + message.setCounterpart(conversation.getNextCounterpart()); + xmppService.sendMessage(message); + messageSent(); + } + }); } public void appendText(String text) { @@ -1195,6 +1331,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa if (status == Account.State.ONLINE && conversation.setOutgoingChatState(ChatState.COMPOSING)) { activity.xmppConnectionService.sendChatState(conversation); } + activity.hideConversationsOverview(); updateSendButton(); } @@ -1215,6 +1352,72 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa updateSendButton(); } + private int completionIndex = 0; + private int lastCompletionLength = 0; + private String incomplete; + private int lastCompletionCursor; + private boolean firstWord = false; + + @Override + public boolean onTabPressed(boolean repeated) { + if (conversation == null || conversation.getMode() == Conversation.MODE_SINGLE) { + return false; + } + if (repeated) { + completionIndex++; + } else { + lastCompletionLength = 0; + completionIndex = 0; + final String content = mEditMessage.getText().toString(); + lastCompletionCursor = mEditMessage.getSelectionEnd(); + int start = lastCompletionCursor > 0 ? content.lastIndexOf(" ",lastCompletionCursor-1) + 1 : 0; + firstWord = start == 0; + incomplete = content.substring(start,lastCompletionCursor); + } + List<String> completions = new ArrayList<>(); + for(MucOptions.User user : conversation.getMucOptions().getUsers()) { + if (user.getName().startsWith(incomplete)) { + completions.add(user.getName()+(firstWord ? ": " : " ")); + } + } + Collections.sort(completions); + if (completions.size() > completionIndex) { + String completion = completions.get(completionIndex).substring(incomplete.length()); + mEditMessage.getEditableText().delete(lastCompletionCursor,lastCompletionCursor + lastCompletionLength); + mEditMessage.getEditableText().insert(lastCompletionCursor, completion); + lastCompletionLength = completion.length(); + } else { + completionIndex = -1; + mEditMessage.getEditableText().delete(lastCompletionCursor,lastCompletionCursor + lastCompletionLength); + lastCompletionLength = 0; + } + return true; + } + + @Override + public void onActivityResult(int requestCode, int resultCode, + final Intent data) { + if (resultCode == Activity.RESULT_OK) { + if (requestCode == ConversationActivity.REQUEST_DECRYPT_PGP) { + activity.getSelectedConversation().getAccount().getPgpDecryptionService().onKeychainUnlocked(); + keychainUnlock = KEYCHAIN_UNLOCK_NOT_REQUIRED; + updatePgpMessages(); + } else if (requestCode == ConversationActivity.REQUEST_TRUST_KEYS_TEXT) { + final String body = mEditMessage.getText().toString(); + Message message = new Message(conversation, body, conversation.getNextEncryption()); + sendAxolotlMessage(message); + } else if (requestCode == ConversationActivity.REQUEST_TRUST_KEYS_MENU) { + int choice = data.getIntExtra("choice", ConversationActivity.ATTACHMENT_CHOICE_INVALID); + activity.selectPresenceToAttachFile(choice, conversation.getNextEncryption()); + } + } else { + if (requestCode == ConversationActivity.REQUEST_DECRYPT_PGP) { + keychainUnlock = KEYCHAIN_UNLOCK_NOT_REQUIRED; + updatePgpMessages(); + } + } + } + private void changeEmojiKeyboardIcon(ImageView iconToBeChanged, int drawableResourceId){ iconToBeChanged.setImageResource(drawableResourceId); } diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java index 0fbcfb90..f7d4564f 100644 --- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java @@ -1,8 +1,17 @@ package eu.siacs.conversations.ui; +import android.app.AlertDialog; +import android.app.AlertDialog.Builder; import android.app.PendingIntent; +import android.content.DialogInterface; import android.content.Intent; +import android.content.SharedPreferences; +import android.graphics.Bitmap; +import android.net.Uri; import android.os.Bundle; +import android.provider.Settings; +import android.security.KeyChain; +import android.security.KeyChainAliasCallback; import android.text.Editable; import android.text.TextWatcher; import android.view.Menu; @@ -24,20 +33,28 @@ import android.widget.TextView; import android.widget.Toast; import de.thedevstack.conversationsplus.ui.listeners.ShowResourcesListDialogListener; +import java.util.Set; +import eu.siacs.conversations.Config; import eu.siacs.conversations.R; +import eu.siacs.conversations.crypto.axolotl.AxolotlService; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.services.AvatarService; +import eu.siacs.conversations.services.XmppConnectionService.OnCaptchaRequested; +import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate; import eu.siacs.conversations.ui.adapter.KnownHostsAdapter; import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.UIHelper; +import eu.siacs.conversations.xmpp.OnKeyStatusUpdated; import eu.siacs.conversations.xmpp.XmppConnection.Features; +import eu.siacs.conversations.xmpp.forms.Data; import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; import eu.siacs.conversations.xmpp.pep.Avatar; -public class EditAccountActivity extends XmppActivity implements OnAccountUpdate{ +public class EditAccountActivity extends XmppActivity implements OnAccountUpdate, + OnKeyStatusUpdated, OnCaptchaRequested, KeyChainAliasCallback, XmppConnectionService.OnShowErrorToast { private AutoCompleteTextView mAccountJid; private EditText mPassword; @@ -45,9 +62,11 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate private CheckBox mRegisterNew; private Button mCancelButton; private Button mSaveButton; + private Button mDisableBatterOptimizations; private TableLayout mMoreTable; private LinearLayout mStats; + private RelativeLayout mBatteryOptimizations; private TextView mServerInfoSm; private TextView mServerInfoRosterVersion; private TextView mServerInfoCarbons; @@ -55,14 +74,29 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate private TextView mServerInfoCSI; private TextView mServerInfoBlocking; private TextView mServerInfoPep; + private TextView mServerInfoHttpUpload; private TextView mSessionEst; private TextView mOtrFingerprint; + private TextView mAxolotlFingerprint; + private TextView mAccountJidLabel; private ImageView mAvatar; private RelativeLayout mOtrFingerprintBox; + private RelativeLayout mAxolotlFingerprintBox; private ImageButton mOtrFingerprintToClipboardButton; + private ImageButton mAxolotlFingerprintToClipboardButton; + private ImageButton mRegenerateAxolotlKeyButton; + private LinearLayout keys; + private LinearLayout keysCard; + private LinearLayout mNamePort; + private EditText mHostname; + private EditText mPort; + private AlertDialog mCaptchaDialog = null; private Jid jidToEdit; + private boolean mInitMode = false; + private boolean mShowOptions = false; private Account mAccount; + private String messageFingerprint; private boolean mFetchingAvatar = false; @@ -70,22 +104,67 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate @Override public void onClick(final View v) { + if (mInitMode && mAccount != null) { + mAccount.setOption(Account.OPTION_DISABLED, false); + } if (mAccount != null && mAccount.getStatus() == Account.State.DISABLED && !accountInfoEdited()) { mAccount.setOption(Account.OPTION_DISABLED, false); xmppConnectionService.updateAccount(mAccount); return; } - final boolean registerNewAccount = mRegisterNew.isChecked(); + final boolean registerNewAccount = mRegisterNew.isChecked() && !Config.DISALLOW_REGISTRATION_IN_UI; + if (Config.DOMAIN_LOCK != null && mAccountJid.getText().toString().contains("@")) { + mAccountJid.setError(getString(R.string.invalid_username)); + mAccountJid.requestFocus(); + return; + } final Jid jid; try { - jid = Jid.fromString(mAccountJid.getText().toString()); + if (Config.DOMAIN_LOCK != null) { + jid = Jid.fromParts(mAccountJid.getText().toString(), Config.DOMAIN_LOCK, null); + } else { + jid = Jid.fromString(mAccountJid.getText().toString()); + } } catch (final InvalidJidException e) { - mAccountJid.setError(getString(R.string.invalid_jid)); + if (Config.DOMAIN_LOCK != null) { + mAccountJid.setError(getString(R.string.invalid_username)); + } else { + mAccountJid.setError(getString(R.string.invalid_jid)); + } mAccountJid.requestFocus(); return; } + String hostname = null; + int numericPort = 5222; + if (mShowOptions) { + hostname = mHostname.getText().toString(); + final String port = mPort.getText().toString(); + if (hostname.contains(" ")) { + mHostname.setError(getString(R.string.not_valid_hostname)); + mHostname.requestFocus(); + return; + } + try { + numericPort = Integer.parseInt(port); + if (numericPort < 0 || numericPort > 65535) { + mPort.setError(getString(R.string.not_a_valid_port)); + mPort.requestFocus(); + return; + } + + } catch (NumberFormatException e) { + mPort.setError(getString(R.string.not_a_valid_port)); + mPort.requestFocus(); + return; + } + } + if (jid.isDomainJid()) { - mAccountJid.setError(getString(R.string.invalid_jid)); + if (Config.DOMAIN_LOCK != null) { + mAccountJid.setError(getString(R.string.invalid_username)); + } else { + mAccountJid.setError(getString(R.string.invalid_jid)); + } mAccountJid.requestFocus(); return; } @@ -99,34 +178,33 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate } } if (mAccount != null) { - try { - mAccount.setUsername(jid.hasLocalpart() ? jid.getLocalpart() : ""); - mAccount.setServer(jid.getDomainpart()); - } catch (final InvalidJidException ignored) { - return; - } + mAccount.setJid(jid); + mAccount.setPort(numericPort); + mAccount.setHostname(hostname); mAccountJid.setError(null); mPasswordConfirm.setError(null); mAccount.setPassword(password); mAccount.setOption(Account.OPTION_REGISTER, registerNewAccount); xmppConnectionService.updateAccount(mAccount); } else { - try { - if (xmppConnectionService.findAccountByJid(Jid.fromString(mAccountJid.getText().toString())) != null) { - mAccountJid.setError(getString(R.string.account_already_exists)); - mAccountJid.requestFocus(); - return; - } - } catch (final InvalidJidException e) { + if (xmppConnectionService.findAccountByJid(jid) != null) { + mAccountJid.setError(getString(R.string.account_already_exists)); + mAccountJid.requestFocus(); return; } mAccount = new Account(jid.toBareJid(), password); + mAccount.setPort(numericPort); + mAccount.setHostname(hostname); mAccount.setOption(Account.OPTION_USETLS, true); mAccount.setOption(Account.OPTION_USECOMPRESSION, true); mAccount.setOption(Account.OPTION_REGISTER, registerNewAccount); xmppConnectionService.createAccount(mAccount); } - if (jidToEdit != null && !mAccount.isOptionSet(Account.OPTION_DISABLED)) { + mHostname.setError(null); + mPort.setError(null); + if (!mAccount.isOptionSet(Account.OPTION_DISABLED) + && !registerNewAccount + && !mInitMode) { finish(); } else { updateSaveButton(); @@ -142,35 +220,33 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate finish(); } }; - @Override - public void onAccountUpdate() { - runOnUiThread(new Runnable() { - @Override - public void run() { - invalidateOptionsMenu(); - if (mAccount != null - && mAccount.getStatus() != Account.State.ONLINE - && mFetchingAvatar) { - startActivity(new Intent(getApplicationContext(), - ManageAccountActivity.class)); - finish(); - } else if (jidToEdit == null && mAccount != null - && mAccount.getStatus() == Account.State.ONLINE) { - if (!mFetchingAvatar) { - mFetchingAvatar = true; - AvatarService.getInstance().checkForAvatar(mAccount, - mAvatarFetchCallback); - } - } else { - updateSaveButton(); - } - if (mAccount != null) { - updateAccountInformation(false); - } + public void refreshUiReal() { + invalidateOptionsMenu(); + if (mAccount != null + && mAccount.getStatus() != Account.State.ONLINE + && mFetchingAvatar) { + startActivity(new Intent(getApplicationContext(), + ManageAccountActivity.class)); + finish(); + } else if (mInitMode && mAccount != null && mAccount.getStatus() == Account.State.ONLINE) { + if (!mFetchingAvatar) { + mFetchingAvatar = true; + xmppConnectionService.checkForAvatar(mAccount, mAvatarFetchCallback); } - }); + } else { + updateSaveButton(); + } + if (mAccount != null) { + updateAccountInformation(false); + } } + + @Override + public void onAccountUpdate() { + refreshUi(); + } + private final UiCallback<Avatar> mAvatarFetchCallback = new UiCallback<Avatar>() { @Override @@ -209,9 +285,8 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate @Override public void onClick(final View view) { if (mAccount != null) { - final Intent intent = new Intent(getApplicationContext(), - PublishProfilePictureActivity.class); - intent.putExtra("account", mAccount.getJid().toBareJid().toString()); + final Intent intent = new Intent(getApplicationContext(), PublishProfilePictureActivity.class); + intent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().toBareJid().toString()); startActivity(intent); } } @@ -232,7 +307,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate } else { intent = new Intent(getApplicationContext(), PublishProfilePictureActivity.class); - intent.putExtra("account", mAccount.getJid().toBareJid().toString()); + intent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().toBareJid().toString()); intent.putExtra("setup", true); } startActivity(intent); @@ -241,8 +316,16 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate }); } + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == REQUEST_BATTERY_OP) { + updateAccountInformation(mAccount == null); + } + } + protected void updateSaveButton() { - if (accountInfoEdited() && jidToEdit != null) { + if (accountInfoEdited() && !mInitMode) { this.mSaveButton.setText(R.string.save); this.mSaveButton.setEnabled(true); this.mSaveButton.setTextColor(getPrimaryTextColor()); @@ -250,14 +333,14 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate this.mSaveButton.setEnabled(false); this.mSaveButton.setTextColor(getSecondaryTextColor()); this.mSaveButton.setText(R.string.account_status_connecting); - } else if (mAccount != null && mAccount.getStatus() == Account.State.DISABLED) { + } else if (mAccount != null && mAccount.getStatus() == Account.State.DISABLED && !mInitMode) { this.mSaveButton.setEnabled(true); this.mSaveButton.setTextColor(getPrimaryTextColor()); this.mSaveButton.setText(R.string.enable); } else { this.mSaveButton.setEnabled(true); this.mSaveButton.setTextColor(getPrimaryTextColor()); - if (jidToEdit != null) { + if (!mInitMode) { if (mAccount != null && mAccount.isOnlineAndConnected()) { this.mSaveButton.setText(R.string.save); if (!accountInfoEdited()) { @@ -274,15 +357,24 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate } protected boolean accountInfoEdited() { - return this.mAccount != null && (!this.mAccount.getJid().toBareJid().toString().equals( - this.mAccountJid.getText().toString()) - || !this.mAccount.getPassword().equals( - this.mPassword.getText().toString())); + if (this.mAccount == null) { + return false; + } + final String unmodified; + if (Config.DOMAIN_LOCK != null) { + unmodified = this.mAccount.getJid().getLocalpart(); + } else { + unmodified = this.mAccount.getJid().toBareJid().toString(); + } + return !unmodified.equals(this.mAccountJid.getText().toString()) || + !this.mAccount.getPassword().equals(this.mPassword.getText().toString()) || + !this.mAccount.getHostname().equals(this.mHostname.getText().toString()) || + !String.valueOf(this.mAccount.getPort()).equals(this.mPort.getText().toString()); } @Override protected String getShareableUri() { - if (mAccount!=null) { + if (mAccount != null) { return mAccount.getShareableUri(); } else { return ""; @@ -295,6 +387,11 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate setContentView(R.layout.activity_edit_account); this.mAccountJid = (AutoCompleteTextView) findViewById(R.id.account_jid); this.mAccountJid.addTextChangedListener(this.mTextWatcher); + this.mAccountJidLabel = (TextView) findViewById(R.id.account_jid_label); + if (Config.DOMAIN_LOCK != null) { + this.mAccountJidLabel.setText(R.string.username); + this.mAccountJid.setHint(R.string.username_hint); + } this.mPassword = (EditText) findViewById(R.id.account_password); this.mPassword.addTextChangedListener(this.mTextWatcher); this.mPasswordConfirm = (EditText) findViewById(R.id.account_password_confirm); @@ -302,6 +399,17 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate this.mAvatar.setOnClickListener(this.mAvatarClickListener); this.mRegisterNew = (CheckBox) findViewById(R.id.account_register_new); this.mStats = (LinearLayout) findViewById(R.id.stats); + this.mBatteryOptimizations = (RelativeLayout) findViewById(R.id.battery_optimization); + this.mDisableBatterOptimizations = (Button) findViewById(R.id.batt_op_disable); + this.mDisableBatterOptimizations.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); + Uri uri = Uri.parse("package:"+getPackageName()); + intent.setData(uri); + startActivityForResult(intent,REQUEST_BATTERY_OP); + } + }); this.mSessionEst = (TextView) findViewById(R.id.session_est); this.mServerInfoRosterVersion = (TextView) findViewById(R.id.server_info_roster_version); this.mServerInfoCarbons = (TextView) findViewById(R.id.server_info_carbons); @@ -310,9 +418,22 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate this.mServerInfoBlocking = (TextView) findViewById(R.id.server_info_blocking); this.mServerInfoSm = (TextView) findViewById(R.id.server_info_sm); this.mServerInfoPep = (TextView) findViewById(R.id.server_info_pep); + this.mServerInfoHttpUpload = (TextView) findViewById(R.id.server_info_http_upload); this.mOtrFingerprint = (TextView) findViewById(R.id.otr_fingerprint); this.mOtrFingerprintBox = (RelativeLayout) findViewById(R.id.otr_fingerprint_box); this.mOtrFingerprintToClipboardButton = (ImageButton) findViewById(R.id.action_copy_to_clipboard); + this.mAxolotlFingerprint = (TextView) findViewById(R.id.axolotl_fingerprint); + this.mAxolotlFingerprintBox = (RelativeLayout) findViewById(R.id.axolotl_fingerprint_box); + this.mAxolotlFingerprintToClipboardButton = (ImageButton) findViewById(R.id.action_copy_axolotl_to_clipboard); + this.mRegenerateAxolotlKeyButton = (ImageButton) findViewById(R.id.action_regenerate_axolotl_key); + this.keysCard = (LinearLayout) findViewById(R.id.other_device_keys_card); + this.keys = (LinearLayout) findViewById(R.id.other_device_keys); + this.mNamePort = (LinearLayout) findViewById(R.id.name_port); + this.mHostname = (EditText) findViewById(R.id.hostname); + this.mHostname.addTextChangedListener(mTextWatcher); + this.mPort = (EditText) findViewById(R.id.port); + this.mPort.setText("5222"); + this.mPort.addTextChangedListener(mTextWatcher); this.mSaveButton = (Button) findViewById(R.id.save_button); this.mCancelButton = (Button) findViewById(R.id.cancel_button); this.mSaveButton.setOnClickListener(this.mSaveButtonClickListener); @@ -321,7 +442,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate final OnCheckedChangeListener OnCheckedShowConfirmPassword = new OnCheckedChangeListener() { @Override public void onCheckedChanged(final CompoundButton buttonView, - final boolean isChecked) { + final boolean isChecked) { if (isChecked) { mPasswordConfirm.setVisibility(View.VISIBLE); } else { @@ -331,6 +452,9 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate } }; this.mRegisterNew.setOnCheckedChangeListener(OnCheckedShowConfirmPassword); + if (Config.DISALLOW_REGISTRATION_IN_UI) { + this.mRegisterNew.setVisibility(View.GONE); + } } @Override @@ -341,6 +465,11 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate final MenuItem showBlocklist = menu.findItem(R.id.action_show_block_list); final MenuItem showMoreInfo = menu.findItem(R.id.action_server_info_show_more); final MenuItem changePassword = menu.findItem(R.id.action_change_password_on_server); + final MenuItem clearDevices = menu.findItem(R.id.action_clear_devices); + final MenuItem renewCertificate = menu.findItem(R.id.action_renew_certificate); + + renewCertificate.setVisible(mAccount != null && mAccount.getPrivateKeyAlias() != null); + if (mAccount != null && mAccount.isOnlineAndConnected()) { if (!mAccount.getXmppConnection().getFeatures().blocking()) { showBlocklist.setVisible(false); @@ -348,11 +477,16 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate if (!mAccount.getXmppConnection().getFeatures().register()) { changePassword.setVisible(false); } + Set<Integer> otherDevices = mAccount.getAxolotlService().getOwnDeviceIds(); + if (otherDevices == null || otherDevices.isEmpty()) { + clearDevices.setVisible(false); + } } else { showQrCode.setVisible(false); showBlocklist.setVisible(false); showMoreInfo.setVisible(false); changePassword.setVisible(false); + clearDevices.setVisible(false); } return true; } @@ -366,7 +500,9 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate } catch (final InvalidJidException | NullPointerException ignored) { this.jidToEdit = null; } - if (this.jidToEdit != null) { + this.mInitMode = getIntent().getBooleanExtra("init", false) || this.jidToEdit == null; + this.messageFingerprint = getIntent().getStringExtra("fingerprint"); + if (!mInitMode) { this.mRegisterNew.setVisibility(View.GONE); if (getActionBar() != null) { getActionBar().setTitle(getString(R.string.account_details)); @@ -378,16 +514,26 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate } } } + SharedPreferences preferences = getPreferences(); + boolean useTor = Config.FORCE_ORBOT || preferences.getBoolean("use_tor", false); + this.mShowOptions = useTor || preferences.getBoolean("show_connection_options", false); + mHostname.setHint(useTor ? R.string.hostname_or_onion : R.string.hostname_example); + this.mNamePort.setVisibility(mShowOptions ? View.VISIBLE : View.GONE); } @Override protected void onBackendConnected() { - final KnownHostsAdapter mKnownHostsAdapter = new KnownHostsAdapter(this, - android.R.layout.simple_list_item_1, - xmppConnectionService.getKnownHosts()); if (this.jidToEdit != null) { this.mAccount = xmppConnectionService.findAccountByJid(jidToEdit); - updateAccountInformation(true); + if (this.mAccount != null) { + if (this.mAccount.getPrivateKeyAlias() != null) { + this.mPassword.setHint(R.string.authenticate_with_certificate); + if (this.mInitMode) { + this.mPassword.requestFocus(); + } + } + updateAccountInformation(true); + } } else if (this.xmppConnectionService.getAccounts().size() == 0) { if (getActionBar() != null) { getActionBar().setDisplayHomeAsUpEnabled(false); @@ -397,8 +543,14 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate this.mCancelButton.setEnabled(false); this.mCancelButton.setTextColor(getSecondaryTextColor()); } - this.mAccountJid.setAdapter(mKnownHostsAdapter); + if (Config.DOMAIN_LOCK == null) { + final KnownHostsAdapter mKnownHostsAdapter = new KnownHostsAdapter(this, + android.R.layout.simple_list_item_1, + xmppConnectionService.getKnownHosts()); + this.mAccountJid.setAdapter(mKnownHostsAdapter); + } updateSaveButton(); + invalidateOptionsMenu(); } @Override @@ -406,7 +558,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate switch (item.getItemId()) { case R.id.action_show_block_list: final Intent showBlocklistIntent = new Intent(this, BlocklistActivity.class); - showBlocklistIntent.putExtra("account", mAccount.getJid().toString()); + showBlocklistIntent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().toString()); startActivity(showBlocklistIntent); break; case R.id.action_server_info_show_more: @@ -415,21 +567,49 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate break; case R.id.action_change_password_on_server: final Intent changePasswordIntent = new Intent(this, ChangePasswordActivity.class); - changePasswordIntent.putExtra("account", mAccount.getJid().toString()); + changePasswordIntent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().toString()); startActivity(changePasswordIntent); break; + case R.id.action_clear_devices: + showWipePepDialog(); + break; + case R.id.action_renew_certificate: + renewCertificate(); + break; } return super.onOptionsItemSelected(item); } + private void renewCertificate() { + KeyChain.choosePrivateKeyAlias(this, this, null, null, null, -1, null); + } + + @Override + public void alias(String alias) { + if (alias != null) { + xmppConnectionService.updateKeyInAccount(mAccount, alias); + } + } + private void updateAccountInformation(boolean init) { if (init) { - this.mAccountJid.setText(this.mAccount.getJid().toBareJid().toString()); + this.mAccountJid.getEditableText().clear(); + if (Config.DOMAIN_LOCK != null) { + this.mAccountJid.getEditableText().append(this.mAccount.getJid().getLocalpart()); + } else { + this.mAccountJid.getEditableText().append(this.mAccount.getJid().toBareJid().toString()); + } this.mPassword.setText(this.mAccount.getPassword()); + this.mHostname.setText(""); + this.mHostname.getEditableText().append(this.mAccount.getHostname()); + this.mPort.setText(""); + this.mPort.getEditableText().append(String.valueOf(this.mAccount.getPort())); + this.mNamePort.setVisibility(mShowOptions ? View.VISIBLE : View.GONE); + } - if (this.jidToEdit != null) { + if (!mInitMode) { this.mAvatar.setVisibility(View.VISIBLE); - this.mAvatar.setImageBitmap(AvatarService.getInstance().get(this.mAccount, getPixel(72))); + this.mAvatar.setImageBitmap(avatarService().get(this.mAccount, getPixel(72))); } if (this.mAccount.isOptionSet(Account.OPTION_REGISTER)) { this.mRegisterNew.setVisibility(View.VISIBLE); @@ -450,8 +630,9 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate detailsAccountJid.setText(this.mAccount.getJid().toBareJid().toString()); } this.mStats.setVisibility(View.VISIBLE); + this.mBatteryOptimizations.setVisibility(showBatteryOptimizationWarning() ? View.VISIBLE : View.GONE); this.mSessionEst.setText(UIHelper.readableTimeDifferenceFull(this, this.mAccount.getXmppConnection() - .getLastSessionEstablished())); + .getLastSessionEstablished())); Features features = this.mAccount.getXmppConnection().getFeatures(); if (features.rosterVersioning()) { this.mServerInfoRosterVersion.setText(R.string.server_info_available); @@ -462,7 +643,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate this.mServerInfoCarbons.setText(R.string.server_info_available); } else { this.mServerInfoCarbons - .setText(R.string.server_info_unavailable); + .setText(R.string.server_info_unavailable); } if (features.mam()) { this.mServerInfoMam.setText(R.string.server_info_available); @@ -485,43 +666,227 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate this.mServerInfoSm.setText(R.string.server_info_unavailable); } if (features.pep()) { - this.mServerInfoPep.setText(R.string.server_info_available); + AxolotlService axolotlService = this.mAccount.getAxolotlService(); + if (axolotlService != null && axolotlService.isPepBroken()) { + this.mServerInfoPep.setText(R.string.server_info_broken); + } else { + this.mServerInfoPep.setText(R.string.server_info_available); + } } else { this.mServerInfoPep.setText(R.string.server_info_unavailable); } - final String fingerprint = this.mAccount.getOtrFingerprint(); - if (fingerprint != null) { + if (features.httpUpload()) { + this.mServerInfoHttpUpload.setText(R.string.server_info_available); + } else { + this.mServerInfoHttpUpload.setText(R.string.server_info_unavailable); + } + final String otrFingerprint = this.mAccount.getOtrFingerprint(); + if (otrFingerprint != null) { this.mOtrFingerprintBox.setVisibility(View.VISIBLE); - this.mOtrFingerprint.setText(CryptoHelper.prettifyFingerprint(fingerprint)); + this.mOtrFingerprint.setText(CryptoHelper.prettifyFingerprint(otrFingerprint)); this.mOtrFingerprintToClipboardButton - .setVisibility(View.VISIBLE); + .setVisibility(View.VISIBLE); this.mOtrFingerprintToClipboardButton - .setOnClickListener(new View.OnClickListener() { + .setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(final View v) { + @Override + public void onClick(final View v) { - if (copyTextToClipboard(fingerprint, R.string.otr_fingerprint)) { - Toast.makeText( - EditAccountActivity.this, - R.string.toast_message_otr_fingerprint, - Toast.LENGTH_SHORT).show(); + if (copyTextToClipboard(otrFingerprint, R.string.otr_fingerprint)) { + Toast.makeText( + EditAccountActivity.this, + R.string.toast_message_otr_fingerprint, + Toast.LENGTH_SHORT).show(); + } } - } - }); + }); } else { this.mOtrFingerprintBox.setVisibility(View.GONE); } + final String axolotlFingerprint = this.mAccount.getAxolotlService().getOwnFingerprint(); + if (axolotlFingerprint != null) { + this.mAxolotlFingerprintBox.setVisibility(View.VISIBLE); + this.mAxolotlFingerprint.setText(CryptoHelper.prettifyFingerprint(axolotlFingerprint.substring(2))); + this.mAxolotlFingerprintToClipboardButton + .setVisibility(View.VISIBLE); + this.mAxolotlFingerprintToClipboardButton + .setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(final View v) { + + if (copyTextToClipboard(axolotlFingerprint.substring(2), R.string.omemo_fingerprint)) { + Toast.makeText( + EditAccountActivity.this, + R.string.toast_message_omemo_fingerprint, + Toast.LENGTH_SHORT).show(); + } + } + }); + if (Config.SHOW_REGENERATE_AXOLOTL_KEYS_BUTTON) { + this.mRegenerateAxolotlKeyButton + .setVisibility(View.VISIBLE); + this.mRegenerateAxolotlKeyButton + .setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(final View v) { + showRegenerateAxolotlKeyDialog(); + } + }); + } + } else { + this.mAxolotlFingerprintBox.setVisibility(View.GONE); + } + final String ownFingerprint = mAccount.getAxolotlService().getOwnFingerprint(); + boolean hasKeys = false; + keys.removeAllViews(); + for (final String fingerprint : mAccount.getAxolotlService().getFingerprintsForOwnSessions()) { + if (ownFingerprint.equals(fingerprint)) { + continue; + } + boolean highlight = fingerprint.equals(messageFingerprint); + hasKeys |= addFingerprintRow(keys, mAccount, fingerprint, highlight, null); + } + if (hasKeys) { + keysCard.setVisibility(View.VISIBLE); + } else { + keysCard.setVisibility(View.GONE); + } } else { if (this.mAccount.errorStatus()) { - this.mAccountJid.setError(getString(this.mAccount.getStatus().getReadableId())); + final EditText errorTextField; + if (this.mAccount.getStatus() == Account.State.UNAUTHORIZED) { + errorTextField = this.mPassword; + } else if (mShowOptions + && this.mAccount.getStatus() == Account.State.SERVER_NOT_FOUND + && this.mHostname.getText().length() > 0) { + errorTextField = this.mHostname; + } else { + errorTextField = this.mAccountJid; + } + errorTextField.setError(getString(this.mAccount.getStatus().getReadableId())); if (init || !accountInfoEdited()) { - this.mAccountJid.requestFocus(); + errorTextField.requestFocus(); } } else { this.mAccountJid.setError(null); + this.mPassword.setError(null); + this.mHostname.setError(null); } this.mStats.setVisibility(View.GONE); } } + + public void showRegenerateAxolotlKeyDialog() { + Builder builder = new Builder(this); + builder.setTitle("Regenerate Key"); + builder.setIconAttribute(android.R.attr.alertDialogIcon); + builder.setMessage("Are you sure you want to regenerate your Identity Key? (This will also wipe all established sessions and contact Identity Keys)"); + builder.setNegativeButton(getString(R.string.cancel), null); + builder.setPositiveButton("Yes", + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + mAccount.getAxolotlService().regenerateKeys(false); + } + }); + builder.create().show(); + } + + public void showWipePepDialog() { + Builder builder = new Builder(this); + builder.setTitle(getString(R.string.clear_other_devices)); + builder.setIconAttribute(android.R.attr.alertDialogIcon); + builder.setMessage(getString(R.string.clear_other_devices_desc)); + builder.setNegativeButton(getString(R.string.cancel), null); + builder.setPositiveButton(getString(R.string.accept), + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + mAccount.getAxolotlService().wipeOtherPepDevices(); + } + }); + builder.create().show(); + } + + @Override + public void onKeyStatusUpdated(AxolotlService.FetchStatus report) { + refreshUi(); + } + + @Override + public void onCaptchaRequested(final Account account, final String id, final Data data, + final Bitmap captcha) { + final AlertDialog.Builder builder = new AlertDialog.Builder(this); + final ImageView view = new ImageView(this); + final LinearLayout layout = new LinearLayout(this); + final EditText input = new EditText(this); + + view.setImageBitmap(captcha); + view.setScaleType(ImageView.ScaleType.FIT_CENTER); + + input.setHint(getString(R.string.captcha_hint)); + + layout.setOrientation(LinearLayout.VERTICAL); + layout.addView(view); + layout.addView(input); + + builder.setTitle(getString(R.string.captcha_required)); + builder.setView(layout); + + builder.setPositiveButton(getString(R.string.ok), + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + String rc = input.getText().toString(); + data.put("username", account.getUsername()); + data.put("password", account.getPassword()); + data.put("ocr", rc); + data.submit(); + + if (xmppConnectionServiceBound) { + xmppConnectionService.sendCreateAccountWithCaptchaPacket( + account, id, data); + } + } + }); + builder.setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (xmppConnectionService != null) { + xmppConnectionService.sendCreateAccountWithCaptchaPacket(account, null, null); + } + } + }); + + builder.setOnCancelListener(new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + if (xmppConnectionService != null) { + xmppConnectionService.sendCreateAccountWithCaptchaPacket(account, null, null); + } + } + }); + + runOnUiThread(new Runnable() { + @Override + public void run() { + if ((mCaptchaDialog != null) && mCaptchaDialog.isShowing()) { + mCaptchaDialog.dismiss(); + } + mCaptchaDialog = builder.create(); + mCaptchaDialog.show(); + } + }); + } + + public void onShowErrorToast(final int resId) { + runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(EditAccountActivity.this, resId, Toast.LENGTH_SHORT).show(); + } + }); + } } diff --git a/src/main/java/eu/siacs/conversations/ui/EditMessage.java b/src/main/java/eu/siacs/conversations/ui/EditMessage.java index a7aa2024..4f273882 100644 --- a/src/main/java/eu/siacs/conversations/ui/EditMessage.java +++ b/src/main/java/eu/siacs/conversations/ui/EditMessage.java @@ -33,21 +33,32 @@ public class EditMessage extends EmojiconEditText { private boolean isUserTyping = false; + private boolean lastInputWasTab = false; + protected KeyboardListener keyboardListener; @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_ENTER) { + public boolean onKeyDown(int keyCode, KeyEvent e) { + if (keyCode == KeyEvent.KEYCODE_ENTER && !e.isShiftPressed()) { + lastInputWasTab = false; if (keyboardListener != null && keyboardListener.onEnterPressed()) { return true; } + } else if (keyCode == KeyEvent.KEYCODE_TAB && !e.isAltPressed() && !e.isCtrlPressed()) { + if (keyboardListener != null && keyboardListener.onTabPressed(this.lastInputWasTab)) { + lastInputWasTab = true; + return true; + } + } else { + lastInputWasTab = false; } - return super.onKeyDown(keyCode, event); + return super.onKeyDown(keyCode, e); } @Override public void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) { super.onTextChanged(text,start,lengthBefore,lengthAfter); + lastInputWasTab = false; if (this.mTypingHandler != null && this.keyboardListener != null) { this.mTypingHandler.removeCallbacks(mTypingTimeout); this.mTypingHandler.postDelayed(mTypingTimeout, Config.TYPING_TIMEOUT * 1000); @@ -70,10 +81,11 @@ public class EditMessage extends EmojiconEditText { } public interface KeyboardListener { - public boolean onEnterPressed(); - public void onTypingStarted(); - public void onTypingStopped(); - public void onTextDeleted(); + boolean onEnterPressed(); + void onTypingStarted(); + void onTypingStopped(); + void onTextDeleted(); + boolean onTabPressed(boolean repeated); } } diff --git a/src/main/java/eu/siacs/conversations/ui/EnterJidDialog.java b/src/main/java/eu/siacs/conversations/ui/EnterJidDialog.java new file mode 100644 index 00000000..bb55420d --- /dev/null +++ b/src/main/java/eu/siacs/conversations/ui/EnterJidDialog.java @@ -0,0 +1,125 @@ +package eu.siacs.conversations.ui; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface.OnClickListener; +import android.content.DialogInterface; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ArrayAdapter; +import android.widget.AutoCompleteTextView; +import android.widget.Spinner; + +import java.util.List; + +import eu.siacs.conversations.Config; +import eu.siacs.conversations.R; +import eu.siacs.conversations.ui.adapter.KnownHostsAdapter; +import eu.siacs.conversations.xmpp.jid.InvalidJidException; +import eu.siacs.conversations.xmpp.jid.Jid; + +public class EnterJidDialog { + public interface OnEnterJidDialogPositiveListener { + boolean onEnterJidDialogPositive(Jid account, Jid contact) throws EnterJidDialog.JidError; + } + + public static class JidError extends Exception { + final String msg; + + public JidError(final String msg) { + this.msg = msg; + } + + public String toString() { + return msg; + } + } + + protected final AlertDialog dialog; + protected View.OnClickListener dialogOnClick; + protected OnEnterJidDialogPositiveListener listener = null; + + public EnterJidDialog( + final Context context, List<String> knownHosts, final List<String> activatedAccounts, + final String title, final String positiveButton, + final String prefilledJid, final String account, boolean allowEditJid + ) { + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setTitle(title); + View dialogView = LayoutInflater.from(context).inflate(R.layout.enter_jid_dialog, null); + final Spinner spinner = (Spinner) dialogView.findViewById(R.id.account); + final AutoCompleteTextView jid = (AutoCompleteTextView) dialogView.findViewById(R.id.jid); + jid.setAdapter(new KnownHostsAdapter(context,android.R.layout.simple_list_item_1, knownHosts)); + if (prefilledJid != null) { + jid.append(prefilledJid); + if (!allowEditJid) { + jid.setFocusable(false); + jid.setFocusableInTouchMode(false); + jid.setClickable(false); + jid.setCursorVisible(false); + } + } + + + if (account == null) { + StartConversationActivity.populateAccountSpinner(context, activatedAccounts, spinner); + } else { + ArrayAdapter<String> adapter = new ArrayAdapter<>(context, + android.R.layout.simple_spinner_item, + new String[] { account }); + spinner.setEnabled(false); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + spinner.setAdapter(adapter); + } + + builder.setView(dialogView); + builder.setNegativeButton(R.string.cancel, null); + builder.setPositiveButton(positiveButton, null); + this.dialog = builder.create(); + + this.dialogOnClick = new View.OnClickListener() { + @Override + public void onClick(final View v) { + final Jid accountJid; + if (!spinner.isEnabled() && account == null) { + return; + } + try { + if (Config.DOMAIN_LOCK != null) { + accountJid = Jid.fromParts((String) spinner.getSelectedItem(), Config.DOMAIN_LOCK, null); + } else { + accountJid = Jid.fromString((String) spinner.getSelectedItem()); + } + } catch (final InvalidJidException e) { + return; + } + final Jid contactJid; + try { + contactJid = Jid.fromString(jid.getText().toString()); + } catch (final InvalidJidException e) { + jid.setError(context.getString(R.string.invalid_jid)); + return; + } + + if(listener != null) { + try { + if(listener.onEnterJidDialogPositive(accountJid, contactJid)) { + dialog.dismiss(); + } + } catch(JidError error) { + jid.setError(error.toString()); + } + } + } + }; + } + + public void setOnEnterJidDialogPositiveListener(OnEnterJidDialogPositiveListener listener) { + this.listener = listener; + } + + public void show() { + this.dialog.show(); + this.dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(this.dialogOnClick); + } +} diff --git a/src/main/java/eu/siacs/conversations/ui/ExportLogsPreference.java b/src/main/java/eu/siacs/conversations/ui/ExportLogsPreference.java new file mode 100644 index 00000000..a4e178bd --- /dev/null +++ b/src/main/java/eu/siacs/conversations/ui/ExportLogsPreference.java @@ -0,0 +1,29 @@ +package eu.siacs.conversations.ui; + +import android.content.Context; +import android.content.Intent; +import android.preference.Preference; +import android.util.AttributeSet; + +import eu.siacs.conversations.services.ExportLogsService; + +public class ExportLogsPreference extends Preference { + + public ExportLogsPreference(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public ExportLogsPreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public ExportLogsPreference(Context context) { + super(context); + } + + protected void onClick() { + final Intent startIntent = new Intent(getContext(), ExportLogsService.class); + getContext().startService(startIntent); + super.onClick(); + } +}
\ No newline at end of file diff --git a/src/main/java/eu/siacs/conversations/ui/ManageAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/ManageAccountActivity.java index 56dbc55e..a6fb0fea 100644 --- a/src/main/java/eu/siacs/conversations/ui/ManageAccountActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ManageAccountActivity.java @@ -1,18 +1,17 @@ package eu.siacs.conversations.ui; -import java.util.ArrayList; -import java.util.List; - -import eu.siacs.conversations.R; -import eu.siacs.conversations.entities.Account; -import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate; -import eu.siacs.conversations.ui.adapter.AccountAdapter; +import android.app.ActionBar; import android.app.AlertDialog; +import android.content.ActivityNotFoundException; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.os.Bundle; +import android.security.KeyChain; +import android.security.KeyChainAliasCallback; +import android.util.Pair; import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; import android.view.Menu; import android.view.MenuItem; import android.view.View; @@ -21,14 +20,36 @@ import android.widget.AdapterView; import android.widget.AdapterView.AdapterContextMenuInfo; import android.widget.AdapterView.OnItemClickListener; import android.widget.ListView; +import android.widget.Toast; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +import eu.siacs.conversations.Config; +import eu.siacs.conversations.R; +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.services.XmppConnectionService; +import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate; +import eu.siacs.conversations.ui.adapter.AccountAdapter; +import eu.siacs.conversations.xmpp.jid.InvalidJidException; +import eu.siacs.conversations.xmpp.jid.Jid; -public class ManageAccountActivity extends XmppActivity implements OnAccountUpdate { +import org.openintents.openpgp.util.OpenPgpApi; + +public class ManageAccountActivity extends XmppActivity implements OnAccountUpdate, KeyChainAliasCallback, XmppConnectionService.OnAccountCreated { + + private final String STATE_SELECTED_ACCOUNT = "selected_account"; protected Account selectedAccount = null; + protected Jid selectedAccountJid = null; protected final List<Account> accountList = new ArrayList<>(); protected ListView accountListView; protected AccountAdapter mAccountAdapter; + protected AtomicBoolean mInvokedAddAccount = new AtomicBoolean(false); + + protected Pair<Integer, Intent> mPostponedActivityResult = null; @Override public void onAccountUpdate() { @@ -41,6 +62,11 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda accountList.clear(); accountList.addAll(xmppConnectionService.getAccounts()); } + ActionBar actionBar = getActionBar(); + if (actionBar != null) { + actionBar.setHomeButtonEnabled(this.accountList.size() > 0); + actionBar.setDisplayHomeAsUpEnabled(this.accountList.size() > 0); + } invalidateOptionsMenu(); mAccountAdapter.notifyDataSetChanged(); } @@ -52,6 +78,17 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda setContentView(R.layout.manage_accounts); + if (savedInstanceState != null) { + String jid = savedInstanceState.getString(STATE_SELECTED_ACCOUNT); + if (jid != null) { + try { + this.selectedAccountJid = Jid.fromString(jid); + } catch (InvalidJidException e) { + this.selectedAccountJid = null; + } + } + } + accountListView = (ListView) findViewById(R.id.account_list); this.mAccountAdapter = new AccountAdapter(this, accountList); accountListView.setAdapter(this.mAccountAdapter); @@ -59,7 +96,7 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda @Override public void onItemClick(AdapterView<?> arg0, View view, - int position, long arg3) { + int position, long arg3) { switchToAccount(accountList.get(position)); } }); @@ -67,12 +104,19 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda } @Override - public void onCreateContextMenu(ContextMenu menu, View v, - ContextMenuInfo menuInfo) { + public void onSaveInstanceState(final Bundle savedInstanceState) { + if (selectedAccount != null) { + savedInstanceState.putString(STATE_SELECTED_ACCOUNT, selectedAccount.getJid().toBareJid().toString()); + } + super.onSaveInstanceState(savedInstanceState); + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { super.onCreateContextMenu(menu, v, menuInfo); ManageAccountActivity.this.getMenuInflater().inflate( R.menu.manageaccounts_context, menu); - AdapterView.AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo; + AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo; this.selectedAccount = accountList.get(acmi.position); if (this.selectedAccount.isOptionSet(Account.OPTION_DISABLED)) { menu.findItem(R.id.mgmt_account_disable).setVisible(false); @@ -80,21 +124,39 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda menu.findItem(R.id.mgmt_account_publish_avatar).setVisible(false); } else { menu.findItem(R.id.mgmt_account_enable).setVisible(false); + menu.findItem(R.id.mgmt_account_announce_pgp).setVisible(!Config.HIDE_PGP_IN_UI); } menu.setHeaderTitle(this.selectedAccount.getJid().toBareJid().toString()); } @Override void onBackendConnected() { - this.accountList.clear(); - this.accountList.addAll(xmppConnectionService.getAccounts()); - mAccountAdapter.notifyDataSetChanged(); + if (selectedAccountJid != null) { + this.selectedAccount = xmppConnectionService.findAccountByJid(selectedAccountJid); + } + refreshUiReal(); + if (this.mPostponedActivityResult != null) { + this.onActivityResult(mPostponedActivityResult.first, RESULT_OK, mPostponedActivityResult.second); + } + if (Config.X509_VERIFICATION && this.accountList.size() == 0) { + if (mInvokedAddAccount.compareAndSet(false, true)) { + addAccountFromKey(); + } + } } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.manageaccounts, menu); MenuItem enableAll = menu.findItem(R.id.action_enable_all); + MenuItem addAccount = menu.findItem(R.id.action_add_account); + MenuItem addAccountWithCertificate = menu.findItem(R.id.action_add_account_with_cert); + + if (Config.X509_VERIFICATION) { + addAccount.setVisible(false); + addAccountWithCertificate.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); + } + if (!accountsLeftToEnable()) { enableAll.setVisible(false); } @@ -108,23 +170,23 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda @Override public boolean onContextItemSelected(MenuItem item) { switch (item.getItemId()) { - case R.id.mgmt_account_publish_avatar: - publishAvatar(selectedAccount); - return true; - case R.id.mgmt_account_disable: - disableAccount(selectedAccount); - return true; - case R.id.mgmt_account_enable: - enableAccount(selectedAccount); - return true; - case R.id.mgmt_account_delete: - deleteAccount(selectedAccount); - return true; - case R.id.mgmt_account_announce_pgp: - publishOpenPGPPublicKey(selectedAccount); - return true; - default: - return super.onContextItemSelected(item); + case R.id.mgmt_account_publish_avatar: + publishAvatar(selectedAccount); + return true; + case R.id.mgmt_account_disable: + disableAccount(selectedAccount); + return true; + case R.id.mgmt_account_enable: + enableAccount(selectedAccount); + return true; + case R.id.mgmt_account_delete: + deleteAccount(selectedAccount); + return true; + case R.id.mgmt_account_announce_pgp: + publishOpenPGPPublicKey(selectedAccount); + return true; + default: + return super.onContextItemSelected(item); } } @@ -141,6 +203,9 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda case R.id.action_enable_all: enableAllAccounts(); break; + case R.id.action_add_account_with_cert: + addAccountFromKey(); + break; default: break; } @@ -153,9 +218,9 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda Intent contactsIntent = new Intent(this, StartConversationActivity.class); contactsIntent.setFlags( - // if activity exists in stack, pop the stack and go back to it + // if activity exists in stack, pop the stack and go back to it Intent.FLAG_ACTIVITY_CLEAR_TOP | - // otherwise, make a new task for it + // otherwise, make a new task for it Intent.FLAG_ACTIVITY_NEW_TASK | // don't use the new activity animation; finish // animation runs instead @@ -176,10 +241,18 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda } } + private void addAccountFromKey() { + try { + KeyChain.choosePrivateKeyAlias(this, this, null, null, null, -1, null); + } catch (ActivityNotFoundException e) { + Toast.makeText(this, R.string.device_does_not_support_certificates, Toast.LENGTH_LONG).show(); + } + } + private void publishAvatar(Account account) { Intent intent = new Intent(getApplicationContext(), PublishProfilePictureActivity.class); - intent.putExtra("account", account.getJid().toString()); + intent.putExtra(EXTRA_ACCOUNT, account.getJid().toString()); startActivity(intent); } @@ -192,7 +265,7 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda } } } - for(Account account : list) { + for (Account account : list) { disableAccount(account); } } @@ -228,7 +301,7 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda } } } - for(Account account : list) { + for (Account account : list) { enableAccount(account); } } @@ -245,7 +318,7 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda private void publishOpenPGPPublicKey(Account account) { if (ManageAccountActivity.this.hasPgp()) { - announcePgp(account, null); + choosePgpSignId(selectedAccount); } else { this.showInstallPgpDialog(); } @@ -273,9 +346,43 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode == RESULT_OK) { - if (requestCode == REQUEST_ANNOUNCE_PGP) { - announcePgp(selectedAccount, null); + if (xmppConnectionServiceBound) { + if (requestCode == REQUEST_CHOOSE_PGP_ID) { + if (data.getExtras().containsKey(OpenPgpApi.EXTRA_SIGN_KEY_ID)) { + selectedAccount.setPgpSignId(data.getExtras().getLong(OpenPgpApi.EXTRA_SIGN_KEY_ID)); + announcePgp(selectedAccount, null); + } else { + choosePgpSignId(selectedAccount); + } + } else if (requestCode == REQUEST_ANNOUNCE_PGP) { + announcePgp(selectedAccount, null); + } + this.mPostponedActivityResult = null; + } else { + this.mPostponedActivityResult = new Pair<>(requestCode, data); } } } + + @Override + public void alias(String alias) { + if (alias != null) { + xmppConnectionService.createAccountFromKey(alias, this); + } + } + + @Override + public void onAccountCreated(Account account) { + switchToAccount(account, true); + } + + @Override + public void informUser(final int r) { + runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(ManageAccountActivity.this, r, Toast.LENGTH_LONG).show(); + } + }); + } } diff --git a/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java b/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java index 1f7a4d6b..9f8ac45c 100644 --- a/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java @@ -2,7 +2,9 @@ package eu.siacs.conversations.ui; import android.app.PendingIntent; import android.content.Intent; +import android.content.pm.PackageManager; import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.net.Uri; import android.os.Bundle; import android.view.View; @@ -14,10 +16,18 @@ import android.widget.TextView; import android.widget.Toast; import de.thedevstack.conversationsplus.utils.ImageUtil; +import com.soundcloud.android.crop.Crop; +import java.io.File; +import java.io.FileNotFoundException; + +import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.services.AvatarService; +import eu.siacs.conversations.persistance.FileBackend; +import eu.siacs.conversations.utils.ExifHelper; +import eu.siacs.conversations.utils.FileUtils; import eu.siacs.conversations.utils.PhoneHelper; import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; @@ -26,16 +36,16 @@ import eu.siacs.conversations.xmpp.pep.Avatar; public class PublishProfilePictureActivity extends XmppActivity { private static final int REQUEST_CHOOSE_FILE = 0xac23; - private ImageView avatar; private TextView accountTextView; private TextView hintOrWarning; private TextView secondaryHint; private Button cancelButton; private Button publishButton; - private Uri avatarUri; private Uri defaultUri; + private Account account; + private boolean support = false; private OnLongClickListener backToDefaultListener = new OnLongClickListener() { @Override @@ -45,8 +55,6 @@ public class PublishProfilePictureActivity extends XmppActivity { return true; } }; - private Account account; - private boolean support = false; private boolean mInitialAccountSetup; private UiCallback<Avatar> avatarPublication = new UiCallback<Avatar>() { @@ -59,7 +67,7 @@ public class PublishProfilePictureActivity extends XmppActivity { if (mInitialAccountSetup) { Intent intent = new Intent(getApplicationContext(), StartConversationActivity.class); - intent.putExtra("init",true); + intent.putExtra("init", true); startActivity(intent); } Toast.makeText(PublishProfilePictureActivity.this, @@ -131,26 +139,60 @@ public class PublishProfilePictureActivity extends XmppActivity { @Override public void onClick(View v) { - Intent attachFileIntent = new Intent(); - attachFileIntent.setType("image/*"); - attachFileIntent.setAction(Intent.ACTION_GET_CONTENT); - Intent chooser = Intent.createChooser(attachFileIntent, - getString(R.string.attach_file)); - startActivityForResult(chooser, REQUEST_CHOOSE_FILE); + if (hasStoragePermission(REQUEST_CHOOSE_FILE)) { + chooseAvatar(); + } + } }); this.defaultUri = PhoneHelper.getSefliUri(getApplicationContext()); } + private void chooseAvatar() { + Intent attachFileIntent = new Intent(); + attachFileIntent.setType("image/*"); + attachFileIntent.setAction(Intent.ACTION_GET_CONTENT); + Intent chooser = Intent.createChooser(attachFileIntent, getString(R.string.attach_file)); + startActivityForResult(chooser, REQUEST_CHOOSE_FILE); + } + @Override - protected void onActivityResult(int requestCode, int resultCode, - final Intent data) { + public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { + if (grantResults.length > 0) + if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { + if (requestCode == REQUEST_CHOOSE_FILE) { + chooseAvatar(); + } + } else { + Toast.makeText(this, R.string.no_storage_permission, Toast.LENGTH_SHORT).show(); + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, final Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode == RESULT_OK) { - if (requestCode == REQUEST_CHOOSE_FILE) { - this.avatarUri = data.getData(); - if (xmppConnectionServiceBound) { + switch (requestCode) { + case REQUEST_CHOOSE_FILE: + Uri source = data.getData(); + String original = FileUtils.getPath(this, source); + if (original != null) { + source = Uri.parse("file://"+original); + } + Uri destination = Uri.fromFile(new File(getCacheDir(), "croppedAvatar")); + final int size = getPixel(192); + Crop.of(source, destination).asSquare().withMaxSize(size, size).start(this); + break; + case Crop.REQUEST_CROP: + this.avatarUri = Uri.fromFile(new File(getCacheDir(), "croppedAvatar")); loadImageIntoPreview(this.avatarUri); + break; + } + } else { + if (requestCode == Crop.REQUEST_CROP && data != null) { + Throwable throwable = Crop.getError(data); + if (throwable != null && throwable instanceof OutOfMemoryError) { + Toast.makeText(this,R.string.selection_too_large, Toast.LENGTH_SHORT).show(); } } } @@ -158,63 +200,75 @@ public class PublishProfilePictureActivity extends XmppActivity { @Override protected void onBackendConnected() { - if (getIntent() != null) { - Jid jid; - try { - jid = Jid.fromString(getIntent().getStringExtra("account")); - } catch (InvalidJidException e) { - jid = null; - } - if (jid != null) { - this.account = xmppConnectionService.findAccountByJid(jid); - if (this.account.getXmppConnection() != null) { - this.support = this.account.getXmppConnection().getFeatures().pep(); - } - if (this.avatarUri == null) { - if (this.account.getAvatar() != null - || this.defaultUri == null) { - this.avatar.setImageBitmap(AvatarService.getInstance().get(account, - getPixel(194))); - if (this.defaultUri != null) { - this.avatar - .setOnLongClickListener(this.backToDefaultListener); - } else { - this.secondaryHint.setVisibility(View.INVISIBLE); - } - if (!support) { - this.hintOrWarning - .setTextColor(getWarningTextColor()); - this.hintOrWarning - .setText(R.string.error_publish_avatar_no_server_support); - } + this.account = extractAccount(getIntent()); + if (this.account != null) { + if (this.account.getXmppConnection() != null) { + this.support = this.account.getXmppConnection().getFeatures().pep(); + } + if (this.avatarUri == null) { + if (this.account.getAvatar() != null + || this.defaultUri == null) { + this.avatar.setImageBitmap(avatarService().get(account, getPixel(192))); + if (this.defaultUri != null) { + this.avatar + .setOnLongClickListener(this.backToDefaultListener); } else { - this.avatarUri = this.defaultUri; - loadImageIntoPreview(this.defaultUri); this.secondaryHint.setVisibility(View.INVISIBLE); } + if (!support) { + this.hintOrWarning + .setTextColor(getWarningTextColor()); + this.hintOrWarning + .setText(R.string.error_publish_avatar_no_server_support); + } } else { - loadImageIntoPreview(avatarUri); + this.avatarUri = this.defaultUri; + loadImageIntoPreview(this.defaultUri); + this.secondaryHint.setVisibility(View.INVISIBLE); } - this.accountTextView.setText(this.account.getJid().toBareJid().toString()); + } else { + loadImageIntoPreview(avatarUri); } + String account; + if (Config.DOMAIN_LOCK != null) { + account = this.account.getJid().getLocalpart(); + } else { + account = this.account.getJid().toBareJid().toString(); + } + this.accountTextView.setText(account); } - } @Override protected void onStart() { super.onStart(); if (getIntent() != null) { - this.mInitialAccountSetup = getIntent().getBooleanExtra("setup", - false); + this.mInitialAccountSetup = getIntent().getBooleanExtra("setup", false); } if (this.mInitialAccountSetup) { this.cancelButton.setText(R.string.skip); } } + private Bitmap loadScaledBitmap(Uri uri, int reqSize) throws FileNotFoundException { + final BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeStream(getContentResolver().openInputStream(uri), null, options); + int rotation = ExifHelper.getOrientation(getContentResolver().openInputStream(uri)); + options.inSampleSize = FileBackend.calcSampleSize(options, reqSize); + options.inJustDecodeBounds = false; + Bitmap bm = BitmapFactory.decodeStream(getContentResolver().openInputStream(uri), null, options); + return FileBackend.rotate(bm,rotation); + } + protected void loadImageIntoPreview(Uri uri) { - Bitmap bm = ImageUtil.cropCenterSquare(uri, 384); + Bitmap bm = null; + try { + bm = loadScaledBitmap(uri, getPixel(192)); + } catch (Exception e) { + e.printStackTrace(); + } + if (bm == null) { disablePublishButton(); this.hintOrWarning.setTextColor(getWarningTextColor()); @@ -253,4 +307,7 @@ public class PublishProfilePictureActivity extends XmppActivity { this.publishButton.setTextColor(getSecondaryTextColor()); } + public void refreshUiReal() { + //nothing to do. This Activity doesn't implement any listeners + } } diff --git a/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java b/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java index ce003bfe..05c9eea7 100644 --- a/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java @@ -24,9 +24,24 @@ import android.os.Build; import android.os.Bundle; import android.preference.ListPreference; import android.preference.Preference; +import android.preference.PreferenceCategory; import android.preference.PreferenceManager; +import android.preference.PreferenceScreen; +import android.util.Log; import android.widget.Toast; +import java.security.KeyStoreException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Locale; + +import de.duenndns.ssl.MemorizingTrustManager; +import eu.siacs.conversations.Config; +import eu.siacs.conversations.R; +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.xmpp.XmppConnection; + public class SettingsActivity extends XmppActivity implements OnSharedPreferenceChangeListener { private SettingsFragment mSettingsFragment; @@ -61,6 +76,14 @@ public class SettingsActivity extends XmppActivity implements } } + if (Config.FORCE_ORBOT) { + PreferenceCategory connectionOptions = (PreferenceCategory) mSettingsFragment.findPreference("connection_options"); + PreferenceScreen expert = (PreferenceScreen) mSettingsFragment.findPreference("expert"); + if (connectionOptions != null) { + expert.removePreference(connectionOptions); + } + } + final Preference removeCertsPreference = mSettingsFragment.findPreference("remove_trusted_certificates"); removeCertsPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { @Override @@ -134,44 +157,40 @@ public class SettingsActivity extends XmppActivity implements @Override public void onSharedPreferenceChanged(SharedPreferences preferences, String name) { - // need to synchronize the settings class first - Settings.synchronizeSettingsClassWithPreferences(preferences, name); - switch (name) { - case "resource": - String resource = ConversationsPlusPreferences.resource().toLowerCase(Locale.US); - if (xmppConnectionServiceBound) { - for (Account account : xmppConnectionService.getAccounts()) { - account.setResource(resource); - if (!account.isOptionSet(Account.OPTION_DISABLED)) { + if (name.equals("resource")) { + String resource = preferences.getString("resource", "mobile") + .toLowerCase(Locale.US); + if (xmppConnectionServiceBound) { + for (Account account : xmppConnectionService.getAccounts()) { + if (account.setResource(resource)) { + if (!account.isOptionSet(Account.OPTION_DISABLED)) { XmppConnection connection = account.getXmppConnection(); if (connection != null) { connection.resetStreamId(); } - xmppConnectionService.reconnectAccountInBackground(account); - } - } - } - break; - case "keep_foreground_service": - xmppConnectionService.toggleForegroundService(); - break; - case "confirm_messages": - if (xmppConnectionServiceBound) { - for (Account account : xmppConnectionService.getAccounts()) { - if (!account.isOptionSet(Account.OPTION_DISABLED)) { - xmppConnectionService.sendPresence(account); + xmppConnectionService.reconnectAccountInBackground(account); } } } - break; - case "dont_trust_system_cas": - xmppConnectionService.updateMemorizingTrustmanager(); - reconnectAccounts(); - break; - case "parse_emoticons": - EmojiconHandler.setParseEmoticons(Settings.PARSE_EMOTICONS); - break; + } + } else if (name.equals("keep_foreground_service")) { + xmppConnectionService.toggleForegroundService(); + } else if (name.equals("confirm_messages") + || name.equals("xa_on_silent_mode") + || name.equals("away_when_screen_off")) { + if (xmppConnectionServiceBound) { + if (name.equals("away_when_screen_off")) { + xmppConnectionService.toggleScreenEventReceiver(); + } + xmppConnectionService.refreshAllPresences(); + } + } else if (name.equals("dont_trust_system_cas")) { + xmppConnectionService.updateMemorizingTrustmanager(); + reconnectAccounts(); + } else if (name.equals("use_tor")) { + reconnectAccounts(); } + } private void displayToast(final String msg) { @@ -191,4 +210,8 @@ public class SettingsActivity extends XmppActivity implements } } + public void refreshUiReal() { + //nothing to do. This Activity doesn't implement any listeners + } + } diff --git a/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java b/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java index cb08afe9..d0cfe558 100644 --- a/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java @@ -4,6 +4,7 @@ import android.app.PendingIntent; import android.content.Intent; import android.net.Uri; import android.os.Bundle; +import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; @@ -14,8 +15,10 @@ import android.widget.Toast; import java.net.URLConnection; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; +import eu.siacs.conversations.Config; import de.thedevstack.conversationsplus.ConversationsPlusPreferences; import de.thedevstack.conversationsplus.ui.dialogs.UserDecisionDialog; import de.thedevstack.conversationsplus.ui.listeners.ResizePictureUserDecisionListener; @@ -31,12 +34,13 @@ import eu.siacs.conversations.xmpp.jid.Jid; public class ShareWithActivity extends XmppActivity { - public class Share { + private class Share { public List<Uri> uris = new ArrayList<>(); public boolean image; public String account; public String contact; public String text; + public String uuid; } private Share share; @@ -44,6 +48,7 @@ public class ShareWithActivity extends XmppActivity { private static final int REQUEST_START_NEW_CONVERSATION = 0x0501; private ListView mListView; private List<Conversation> mConversations = new ArrayList<>(); + private Toast mToast; private UiCallback<Message> attachFileCallback = new UiCallback<Message>() { @@ -54,8 +59,22 @@ public class ShareWithActivity extends XmppActivity { } @Override - public void success(Message message) { + public void success(final Message message) { xmppConnectionService.sendMessage(message); + runOnUiThread(new Runnable() { + @Override + public void run() { + if (mToast != null) { + mToast.cancel(); + } + if (share.uuid != null) { + mToast = Toast.makeText(getApplicationContext(), + getString(share.image ? R.string.shared_image_with_x : R.string.shared_file_with_x,message.getConversation().getName()), + Toast.LENGTH_SHORT); + mToast.show(); + } + } + }); } @Override @@ -70,7 +89,7 @@ public class ShareWithActivity extends XmppActivity { if (requestCode == REQUEST_START_NEW_CONVERSATION && resultCode == RESULT_OK) { share.contact = data.getStringExtra("contact"); - share.account = data.getStringExtra("account"); + share.account = data.getStringExtra(EXTRA_ACCOUNT); } if (xmppConnectionServiceBound && share != null @@ -132,9 +151,12 @@ public class ShareWithActivity extends XmppActivity { return; } final String type = intent.getType(); + Log.d(Config.LOGTAG, "action: "+intent.getAction()+ ", type:"+type); + share.uuid = intent.getStringExtra("uuid"); if (Intent.ACTION_SEND.equals(intent.getAction())) { final Uri uri = getIntent().getParcelableExtra(Intent.EXTRA_STREAM); if (type != null && uri != null && !type.equalsIgnoreCase("text/plain")) { + this.share.uris.clear(); this.share.uris.add(uri); this.share.image = type.startsWith("image/") || isImage(uri); } else { @@ -149,7 +171,11 @@ public class ShareWithActivity extends XmppActivity { this.share.uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM); } if (xmppConnectionServiceBound) { - xmppConnectionService.populateWithOrderedConversations(mConversations, this.share.uris.size() == 0); + if (share.uuid != null) { + share(); + } else { + xmppConnectionService.populateWithOrderedConversations(mConversations, this.share.uris.size() == 0); + } } } @@ -166,7 +192,7 @@ public class ShareWithActivity extends XmppActivity { @Override void onBackendConnected() { if (xmppConnectionServiceBound && share != null - && share.contact != null && share.account != null) { + && ((share.contact != null && share.account != null) || share.uuid != null)) { share(); return; } @@ -175,26 +201,43 @@ public class ShareWithActivity extends XmppActivity { } private void share() { - Account account; - try { - account = xmppConnectionService.findAccountByJid(Jid.fromString(share.account)); - } catch (final InvalidJidException e) { - account = null; - } - if (account == null) { - return; - } final Conversation conversation; - try { - conversation = xmppConnectionService - .findOrCreateConversation(account, Jid.fromString(share.contact), false); - } catch (final InvalidJidException e) { - return; + if (share.uuid != null) { + conversation = xmppConnectionService.findConversationByUuid(share.uuid); + if (conversation == null) { + return; + } + }else{ + Account account; + try { + account = xmppConnectionService.findAccountByJid(Jid.fromString(share.account)); + } catch (final InvalidJidException e) { + account = null; + } + if (account == null) { + return; + } + + try { + conversation = xmppConnectionService + .findOrCreateConversation(account, Jid.fromString(share.contact), false); + } catch (final InvalidJidException e) { + return; + } } share(conversation); } private void share(final Conversation conversation) { + if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP && !hasPgp()) { + if (share.uuid == null) { + showInstallPgpDialog(); + } else { + Toast.makeText(this,R.string.openkeychain_not_installed,Toast.LENGTH_SHORT).show(); + finish(); + } + return; + } if (share.uris.size() != 0) { OnPresenceSelected callback; if (this.share.image) { @@ -210,22 +253,25 @@ public class ShareWithActivity extends XmppActivity { callback = new OnPresenceSelected() { @Override public void onPresenceSelected() { - Toast.makeText(getApplicationContext(), + mToast = Toast.makeText(getApplicationContext(), getText(R.string.preparing_file), - Toast.LENGTH_LONG).show(); + Toast.LENGTH_LONG); + mToast.show(); ShareWithActivity.this.xmppConnectionService .attachFileToConversation(conversation, share.uris.get(0), attachFileCallback); - switchToConversation(conversation, null, true); + if (share.uuid == null) { + switchToConversation(conversation, null, true); + } finish(); } }; } if (conversation.getAccount().httpUploadAvailable()) { - callback.onPresenceSelected(); + callback.onPresenceSelected(); } else { - selectPresence(conversation, callback); + selectPresence(conversation, callback); } } else { switchToConversation(conversation, this.share.text, true); @@ -234,4 +280,8 @@ public class ShareWithActivity extends XmppActivity { } + public void refreshUiReal() { + //nothing to do. This Activity doesn't implement any listeners + } + } diff --git a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java index c52d1d4d..c9638e18 100644 --- a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java @@ -1,5 +1,6 @@ package eu.siacs.conversations.ui; +import android.Manifest; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.app.ActionBar; @@ -13,6 +14,7 @@ import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.Intent; +import android.content.pm.PackageManager; import android.net.Uri; import android.nfc.NdefMessage; import android.nfc.NdefRecord; @@ -50,6 +52,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; import de.thedevstack.android.logcat.Logging; import de.thedevstack.conversationsplus.ConversationsPlusPreferences; @@ -91,6 +94,9 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU private Invite mPendingInvite = null; private Menu mOptionsMenu; private EditText mSearchEditText; + private AtomicBoolean mRequestedContactsPermission = new AtomicBoolean(false); + private final int REQUEST_SYNC_CONTACTS = 0x3b28cf; + private MenuItem.OnActionExpandListener mOnActionExpandListener = new MenuItem.OnActionExpandListener() { @Override @@ -247,6 +253,12 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU } + @Override + public void onStart() { + super.onStart(); + askForContactsPermissions(); + } + protected void openConversationForContact(int position) { Contact contact = (Contact) contacts.get(position); Conversation conversation = xmppConnectionService @@ -276,7 +288,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU if (!conversation.getMucOptions().online()) { xmppConnectionService.joinMuc(conversation); } - if (!bookmark.autojoin()) { + if (!bookmark.autojoin() && getPreferences().getBoolean("autojoin", true)) { bookmark.setAutojoin(true); xmppConnectionService.pushBookmarks(bookmark.getAccount()); } @@ -291,7 +303,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU protected void toggleContactBlock() { final int position = contact_context_id; - BlockContactDialog.show(this, xmppConnectionService, (Contact)contacts.get(position)); + BlockContactDialog.show(this, xmppConnectionService, (Contact) contacts.get(position)); } protected void deleteContact() { @@ -301,7 +313,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU builder.setNegativeButton(R.string.cancel, null); builder.setTitle(R.string.action_delete_contact); builder.setMessage(getString(R.string.remove_contact_text, - contact.getJid())); + contact.getJid())); builder.setPositiveButton(R.string.delete, new OnClickListener() { @Override @@ -321,7 +333,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU builder.setNegativeButton(R.string.cancel, null); builder.setTitle(R.string.delete_bookmark); builder.setMessage(getString(R.string.remove_bookmark_text, - bookmark.getJid())); + bookmark.getJid())); builder.setPositiveButton(R.string.delete, new OnClickListener() { @Override @@ -339,66 +351,37 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU @SuppressLint("InflateParams") protected void showCreateContactDialog(final String prefilledJid, final String fingerprint) { - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(R.string.create_contact); - View dialogView = getLayoutInflater().inflate(R.layout.create_contact_dialog, null); - final Spinner spinner = (Spinner) dialogView.findViewById(R.id.account); - final AutoCompleteTextView jid = (AutoCompleteTextView) dialogView.findViewById(R.id.jid); - jid.setAdapter(new KnownHostsAdapter(this,android.R.layout.simple_list_item_1, mKnownHosts)); - if (prefilledJid != null) { - jid.append(prefilledJid); - if (fingerprint!=null) { - jid.setFocusable(false); - jid.setFocusableInTouchMode(false); - jid.setClickable(false); - jid.setCursorVisible(false); - } - } - populateAccountSpinner(spinner); - builder.setView(dialogView); - builder.setNegativeButton(R.string.cancel, null); - builder.setPositiveButton(R.string.create, null); - final AlertDialog dialog = builder.create(); - dialog.show(); - dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener( - new View.OnClickListener() { + EnterJidDialog dialog = new EnterJidDialog( + this, mKnownHosts, mActivatedAccounts, + getString(R.string.create_contact), getString(R.string.create), + prefilledJid, null, fingerprint == null + ); - @Override - public void onClick(final View v) { - if (!xmppConnectionServiceBound) { - return; - } - final Jid accountJid; - try { - accountJid = Jid.fromString((String) spinner.getSelectedItem()); - } catch (final InvalidJidException e) { - return; - } - final Jid contactJid; - try { - contactJid = Jid.fromString(jid.getText().toString()); - } catch (final InvalidJidException e) { - jid.setError(getString(R.string.invalid_jid)); - return; - } - final Account account = xmppConnectionService - .findAccountByJid(accountJid); - if (account == null) { - dialog.dismiss(); - return; - } - final Contact contact = account.getRoster().getContact(contactJid); - if (contact.showInRoster()) { - jid.setError(getString(R.string.contact_already_exists)); - } else { - contact.addOtrFingerprint(fingerprint); - xmppConnectionService.createContact(contact); - dialog.dismiss(); - switchToConversation(contact); - } - } - }); + dialog.setOnEnterJidDialogPositiveListener(new EnterJidDialog.OnEnterJidDialogPositiveListener() { + @Override + public boolean onEnterJidDialogPositive(Jid accountJid, Jid contactJid) throws EnterJidDialog.JidError { + if (!xmppConnectionServiceBound) { + return false; + } + + final Account account = xmppConnectionService.findAccountByJid(accountJid); + if (account == null) { + return true; + } + final Contact contact = account.getRoster().getContact(contactJid); + if (contact.showInRoster()) { + throw new EnterJidDialog.JidError(getString(R.string.contact_already_exists)); + } else { + contact.addOtrFingerprint(fingerprint); + xmppConnectionService.createContact(contact); + switchToConversation(contact); + return true; + } + } + }); + + dialog.show(); } @SuppressLint("InflateParams") @@ -408,11 +391,11 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU final View dialogView = getLayoutInflater().inflate(R.layout.join_conference_dialog, null); final Spinner spinner = (Spinner) dialogView.findViewById(R.id.account); final AutoCompleteTextView jid = (AutoCompleteTextView) dialogView.findViewById(R.id.jid); - jid.setAdapter(new KnownHostsAdapter(this,android.R.layout.simple_list_item_1, mKnownConferenceHosts)); + jid.setAdapter(new KnownHostsAdapter(this, android.R.layout.simple_list_item_1, mKnownConferenceHosts)); if (prefilledJid != null) { jid.append(prefilledJid); } - populateAccountSpinner(spinner); + populateAccountSpinner(this, mActivatedAccounts, spinner); final Checkable bookmarkCheckBox = (CheckBox) dialogView .findViewById(R.id.bookmark); builder.setView(dialogView); @@ -428,10 +411,8 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU if (!xmppConnectionServiceBound) { return; } - final Jid accountJid; - try { - accountJid = Jid.fromString((String) spinner.getSelectedItem()); - } catch (final InvalidJidException e) { + final Account account = getSelectedAccount(spinner); + if (account == null) { return; } final Jid conferenceJid; @@ -441,36 +422,33 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU jid.setError(getString(R.string.invalid_jid)); return; } - final Account account = xmppConnectionService - .findAccountByJid(accountJid); - if (account == null) { - dialog.dismiss(); - return; - } + if (bookmarkCheckBox.isChecked()) { if (account.hasBookmarkFor(conferenceJid)) { jid.setError(getString(R.string.bookmark_already_exists)); } else { - final Bookmark bookmark = new Bookmark(account,conferenceJid.toBareJid()); - bookmark.setAutojoin(true); + final Bookmark bookmark = new Bookmark(account, conferenceJid.toBareJid()); + bookmark.setAutojoin(getPreferences().getBoolean("autojoin", true)); + String nick = conferenceJid.getResourcepart(); + if (nick != null && !nick.isEmpty()) { + bookmark.setNick(nick); + } account.getBookmarks().add(bookmark); - xmppConnectionService - .pushBookmarks(account); + xmppConnectionService.pushBookmarks(account); final Conversation conversation = xmppConnectionService - .findOrCreateConversation(account, - conferenceJid, true); + .findOrCreateConversation(account, + conferenceJid, true); conversation.setBookmark(bookmark); if (!conversation.getMucOptions().online()) { - xmppConnectionService - .joinMuc(conversation); + xmppConnectionService.joinMuc(conversation); } dialog.dismiss(); switchToConversation(conversation); } } else { final Conversation conversation = xmppConnectionService - .findOrCreateConversation(account, - conferenceJid, true); + .findOrCreateConversation(account, + conferenceJid, true); if (!conversation.getMucOptions().online()) { xmppConnectionService.joinMuc(conversation); } @@ -481,6 +459,23 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU }); } + private Account getSelectedAccount(Spinner spinner) { + if (!spinner.isEnabled()) { + return null; + } + Jid jid; + try { + if (Config.DOMAIN_LOCK != null) { + jid = Jid.fromParts((String) spinner.getSelectedItem(), Config.DOMAIN_LOCK, null); + } else { + jid = Jid.fromString((String) spinner.getSelectedItem()); + } + } catch (final InvalidJidException e) { + return null; + } + return xmppConnectionService.findAccountByJid(jid); + } + protected void switchToConversation(Contact contact) { Conversation conversation = xmppConnectionService .findOrCreateConversation(contact.getAccount(), @@ -488,11 +483,21 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU switchToConversation(conversation); } - private void populateAccountSpinner(Spinner spinner) { - ArrayAdapter<String> adapter = new ArrayAdapter<>(this, - android.R.layout.simple_spinner_item, mActivatedAccounts); - adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - spinner.setAdapter(adapter); + public static void populateAccountSpinner(Context context, List<String> accounts, Spinner spinner) { + if (accounts.size() > 0) { + ArrayAdapter<String> adapter = new ArrayAdapter<>(context, + android.R.layout.simple_spinner_item, accounts); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + spinner.setAdapter(adapter); + spinner.setEnabled(true); + } else { + ArrayAdapter<String> adapter = new ArrayAdapter<>(context, + android.R.layout.simple_spinner_item, + Arrays.asList(new String[]{context.getString(R.string.no_accounts)})); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + spinner.setAdapter(adapter); + spinner.setEnabled(false); + } } @Override @@ -574,12 +579,51 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU super.onActivityResult(requestCode, requestCode, intent); } + private void askForContactsPermissions() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) { + if (mRequestedContactsPermission.compareAndSet(false, true)) { + if (shouldShowRequestPermissionRationale(Manifest.permission.READ_CONTACTS)) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.sync_with_contacts); + builder.setMessage(R.string.sync_with_contacts_long); + builder.setPositiveButton(R.string.next, new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_SYNC_CONTACTS); + } + } + }); + builder.create().show(); + } else { + requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, 0); + } + } + } + } + } + + @Override + public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { + if (grantResults.length > 0) + if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { + if (requestCode == REQUEST_SYNC_CONTACTS && xmppConnectionServiceBound) { + xmppConnectionService.loadPhoneContacts(); + } + } + } + @Override protected void onBackendConnected() { this.mActivatedAccounts.clear(); for (Account account : xmppConnectionService.getAccounts()) { if (account.getStatus() != Account.State.DISABLED) { - this.mActivatedAccounts.add(account.getJid().toBareJid().toString()); + if (Config.DOMAIN_LOCK != null) { + this.mActivatedAccounts.add(account.getJid().getLocalpart()); + } else { + this.mActivatedAccounts.add(account.getJid().toBareJid().toString()); + } } } final Intent intent = getIntent(); @@ -762,7 +806,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU final AdapterView.AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo; if (mResContextMenu == R.menu.conference_context) { activity.conference_context_id = acmi.position; - } else { + } else if (mResContextMenu == R.menu.contact_context){ activity.contact_context_id = acmi.position; final Blockable contact = (Contact) activity.contacts.get(acmi.position); final MenuItem blockUnblockItem = menu.findItem(R.id.context_contact_block_unblock); diff --git a/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java new file mode 100644 index 00000000..eec30798 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java @@ -0,0 +1,301 @@ +package eu.siacs.conversations.ui; + +import android.content.Intent; +import android.os.Bundle; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Button; +import android.widget.CompoundButton; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.Toast; + +import org.whispersystems.libaxolotl.IdentityKey; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import eu.siacs.conversations.R; +import eu.siacs.conversations.crypto.axolotl.AxolotlService; +import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession; +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.entities.Contact; +import eu.siacs.conversations.xmpp.OnKeyStatusUpdated; +import eu.siacs.conversations.xmpp.jid.InvalidJidException; +import eu.siacs.conversations.xmpp.jid.Jid; + +public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdated { + private Jid accountJid; + private Jid contactJid; + + private Contact contact; + private Account mAccount; + private TextView keyErrorMessage; + private LinearLayout keyErrorMessageCard; + private TextView ownKeysTitle; + private LinearLayout ownKeys; + private LinearLayout ownKeysCard; + private TextView foreignKeysTitle; + private LinearLayout foreignKeys; + private LinearLayout foreignKeysCard; + private Button mSaveButton; + private Button mCancelButton; + + private AxolotlService.FetchStatus lastFetchReport = AxolotlService.FetchStatus.SUCCESS; + + private final Map<String, Boolean> ownKeysToTrust = new HashMap<>(); + private final Map<String, Boolean> foreignKeysToTrust = new HashMap<>(); + + private final OnClickListener mSaveButtonListener = new OnClickListener() { + @Override + public void onClick(View v) { + commitTrusts(); + finishOk(); + } + }; + + private final OnClickListener mCancelButtonListener = new OnClickListener() { + @Override + public void onClick(View v) { + setResult(RESULT_CANCELED); + finish(); + } + }; + + @Override + protected void refreshUiReal() { + invalidateOptionsMenu(); + populateView(); + } + + @Override + protected String getShareableUri() { + if (contact != null) { + return contact.getShareableUri(); + } else { + return ""; + } + } + + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_trust_keys); + try { + this.accountJid = Jid.fromString(getIntent().getExtras().getString(EXTRA_ACCOUNT)); + } catch (final InvalidJidException ignored) { + } + try { + this.contactJid = Jid.fromString(getIntent().getExtras().getString("contact")); + } catch (final InvalidJidException ignored) { + } + + keyErrorMessageCard = (LinearLayout) findViewById(R.id.key_error_message_card); + keyErrorMessage = (TextView) findViewById(R.id.key_error_message); + ownKeysTitle = (TextView) findViewById(R.id.own_keys_title); + ownKeys = (LinearLayout) findViewById(R.id.own_keys_details); + ownKeysCard = (LinearLayout) findViewById(R.id.own_keys_card); + foreignKeysTitle = (TextView) findViewById(R.id.foreign_keys_title); + foreignKeys = (LinearLayout) findViewById(R.id.foreign_keys_details); + foreignKeysCard = (LinearLayout) findViewById(R.id.foreign_keys_card); + mCancelButton = (Button) findViewById(R.id.cancel_button); + mCancelButton.setOnClickListener(mCancelButtonListener); + mSaveButton = (Button) findViewById(R.id.save_button); + mSaveButton.setOnClickListener(mSaveButtonListener); + + + if (getActionBar() != null) { + getActionBar().setHomeButtonEnabled(true); + getActionBar().setDisplayHomeAsUpEnabled(true); + } + } + + private void populateView() { + setTitle(getString(R.string.trust_omemo_fingerprints)); + ownKeys.removeAllViews(); + foreignKeys.removeAllViews(); + boolean hasOwnKeys = false; + boolean hasForeignKeys = false; + for(final String fingerprint : ownKeysToTrust.keySet()) { + hasOwnKeys = true; + addFingerprintRowWithListeners(ownKeys, contact.getAccount(), fingerprint, false, + XmppAxolotlSession.Trust.fromBoolean(ownKeysToTrust.get(fingerprint)), false, + new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + ownKeysToTrust.put(fingerprint, isChecked); + // own fingerprints have no impact on locked status. + } + }, + null, + null + ); + } + for(final String fingerprint : foreignKeysToTrust.keySet()) { + hasForeignKeys = true; + addFingerprintRowWithListeners(foreignKeys, contact.getAccount(), fingerprint, false, + XmppAxolotlSession.Trust.fromBoolean(foreignKeysToTrust.get(fingerprint)), false, + new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + foreignKeysToTrust.put(fingerprint, isChecked); + lockOrUnlockAsNeeded(); + } + }, + null, + null + ); + } + + if(hasOwnKeys) { + ownKeysTitle.setText(accountJid.toString()); + ownKeysCard.setVisibility(View.VISIBLE); + } + if(hasForeignKeys) { + foreignKeysTitle.setText(contactJid.toString()); + foreignKeysCard.setVisibility(View.VISIBLE); + } + if(hasPendingKeyFetches()) { + setFetching(); + lock(); + } else { + if (!hasForeignKeys && hasNoOtherTrustedKeys()) { + keyErrorMessageCard.setVisibility(View.VISIBLE); + if (lastFetchReport == AxolotlService.FetchStatus.ERROR + || contact.getAccount().getAxolotlService().fetchMapHasErrors(contact)) { + keyErrorMessage.setText(R.string.error_no_keys_to_trust_server_error); + } else { + keyErrorMessage.setText(R.string.error_no_keys_to_trust); + } + ownKeys.removeAllViews(); ownKeysCard.setVisibility(View.GONE); + foreignKeys.removeAllViews(); foreignKeysCard.setVisibility(View.GONE); + } + lockOrUnlockAsNeeded(); + setDone(); + } + } + + private boolean reloadFingerprints() { + ownKeysToTrust.clear(); + foreignKeysToTrust.clear(); + AxolotlService service = this.mAccount.getAxolotlService(); + Set<IdentityKey> ownKeysSet = service.getKeysWithTrust(XmppAxolotlSession.Trust.UNDECIDED); + Set<IdentityKey> foreignKeysSet = service.getKeysWithTrust(XmppAxolotlSession.Trust.UNDECIDED, contact); + if (hasNoOtherTrustedKeys() && ownKeysSet.size() == 0) { + foreignKeysSet.addAll(service.getKeysWithTrust(XmppAxolotlSession.Trust.UNTRUSTED, contact)); + } + for(final IdentityKey identityKey : ownKeysSet) { + if(!ownKeysToTrust.containsKey(identityKey)) { + ownKeysToTrust.put(identityKey.getFingerprint().replaceAll("\\s", ""), false); + } + } + for(final IdentityKey identityKey : foreignKeysSet) { + if(!foreignKeysToTrust.containsKey(identityKey)) { + foreignKeysToTrust.put(identityKey.getFingerprint().replaceAll("\\s", ""), false); + } + } + return ownKeysSet.size() + foreignKeysSet.size() > 0; + } + + @Override + public void onBackendConnected() { + if ((accountJid != null) && (contactJid != null)) { + this.mAccount = xmppConnectionService.findAccountByJid(accountJid); + if (this.mAccount == null) { + return; + } + this.contact = this.mAccount.getRoster().getContact(contactJid); + reloadFingerprints(); + populateView(); + } + } + + private boolean hasNoOtherTrustedKeys() { + return mAccount == null || mAccount.getAxolotlService().getNumTrustedKeys(contact) == 0; + } + + private boolean hasPendingKeyFetches() { + return mAccount != null && contact != null && mAccount.getAxolotlService().hasPendingKeyFetches(mAccount,contact); + } + + + @Override + public void onKeyStatusUpdated(final AxolotlService.FetchStatus report) { + if (report != null) { + lastFetchReport = report; + runOnUiThread(new Runnable() { + @Override + public void run() { + switch (report) { + case ERROR: + Toast.makeText(TrustKeysActivity.this,R.string.error_fetching_omemo_key,Toast.LENGTH_SHORT).show(); + break; + case SUCCESS_VERIFIED: + Toast.makeText(TrustKeysActivity.this,R.string.verified_omemo_key_with_certificate,Toast.LENGTH_LONG).show(); + break; + } + } + }); + + } + boolean keysToTrust = reloadFingerprints(); + if (keysToTrust || hasPendingKeyFetches() || hasNoOtherTrustedKeys()) { + refreshUi(); + } else { + runOnUiThread(new Runnable() { + @Override + public void run() { + finishOk(); + } + }); + + } + } + + private void finishOk() { + Intent data = new Intent(); + data.putExtra("choice", getIntent().getIntExtra("choice", ConversationActivity.ATTACHMENT_CHOICE_INVALID)); + setResult(RESULT_OK, data); + finish(); + } + + private void commitTrusts() { + for(final String fingerprint :ownKeysToTrust.keySet()) { + contact.getAccount().getAxolotlService().setFingerprintTrust( + fingerprint, + XmppAxolotlSession.Trust.fromBoolean(ownKeysToTrust.get(fingerprint))); + } + for(final String fingerprint:foreignKeysToTrust.keySet()) { + contact.getAccount().getAxolotlService().setFingerprintTrust( + fingerprint, + XmppAxolotlSession.Trust.fromBoolean(foreignKeysToTrust.get(fingerprint))); + } + } + + private void unlock() { + mSaveButton.setEnabled(true); + mSaveButton.setTextColor(getPrimaryTextColor()); + } + + private void lock() { + mSaveButton.setEnabled(false); + mSaveButton.setTextColor(getSecondaryTextColor()); + } + + private void lockOrUnlockAsNeeded() { + if (hasNoOtherTrustedKeys() && !foreignKeysToTrust.values().contains(true)){ + lock(); + } else { + unlock(); + } + } + + private void setDone() { + mSaveButton.setText(getString(R.string.done)); + } + + private void setFetching() { + mSaveButton.setText(getString(R.string.fetching_keys)); + } +} diff --git a/src/main/java/eu/siacs/conversations/ui/UiCallback.java b/src/main/java/eu/siacs/conversations/ui/UiCallback.java index c80199e1..d056d628 100644 --- a/src/main/java/eu/siacs/conversations/ui/UiCallback.java +++ b/src/main/java/eu/siacs/conversations/ui/UiCallback.java @@ -3,9 +3,9 @@ package eu.siacs.conversations.ui; import android.app.PendingIntent; public interface UiCallback<T> { - public void success(T object); + void success(T object); - public void error(int errorCode, T object); + void error(int errorCode, T object); - public void userInputRequried(PendingIntent pi, T object); + void userInputRequried(PendingIntent pi, T object); } diff --git a/src/main/java/eu/siacs/conversations/ui/VerifyOTRActivity.java b/src/main/java/eu/siacs/conversations/ui/VerifyOTRActivity.java index ec9d59e1..2e415d5b 100644 --- a/src/main/java/eu/siacs/conversations/ui/VerifyOTRActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/VerifyOTRActivity.java @@ -196,9 +196,8 @@ public class VerifyOTRActivity extends XmppActivity implements XmppConnectionSer protected boolean handleIntent(Intent intent) { if (intent != null && intent.getAction().equals(ACTION_VERIFY_CONTACT)) { - try { - this.mAccount = this.xmppConnectionService.findAccountByJid(Jid.fromString(intent.getExtras().getString("account"))); - } catch (final InvalidJidException ignored) { + this.mAccount = extractAccount(intent); + if (this.mAccount == null) { return false; } try { diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java index 900bb656..3d3207e4 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java @@ -1,5 +1,6 @@ package eu.siacs.conversations.ui; +import android.Manifest; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.app.ActionBar; @@ -16,6 +17,7 @@ import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.content.IntentSender.SendIntentException; import android.content.ServiceConnection; +import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.Resources; @@ -34,15 +36,20 @@ import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; +import android.os.PowerManager; import android.os.SystemClock; +import android.preference.PreferenceManager; import android.text.InputType; import android.util.DisplayMetrics; +import android.util.Log; import android.view.MenuItem; import android.view.View; import android.view.inputmethod.InputMethodManager; +import android.widget.CompoundButton; import android.widget.EditText; import android.widget.ImageView; import android.widget.LinearLayout; +import android.widget.TextView; import android.widget.Toast; import com.google.zxing.BarcodeFormat; @@ -67,15 +74,20 @@ import de.thedevstack.conversationsplus.utils.ImageUtil; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; +import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.MucOptions; import eu.siacs.conversations.entities.Presences; +import eu.siacs.conversations.services.AvatarService; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.services.XmppConnectionService.XmppConnectionBinder; +import eu.siacs.conversations.ui.widget.Switch; +import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.ExceptionHelper; +import eu.siacs.conversations.xmpp.OnKeyStatusUpdated; import eu.siacs.conversations.xmpp.OnUpdateBlocklist; import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; @@ -84,6 +96,10 @@ public abstract class XmppActivity extends Activity { protected static final int REQUEST_ANNOUNCE_PGP = 0x0101; protected static final int REQUEST_INVITE_TO_CONVERSATION = 0x0102; + protected static final int REQUEST_CHOOSE_PGP_ID = 0x0103; + protected static final int REQUEST_BATTERY_OP = 0x13849ff; + + public static final String EXTRA_ACCOUNT = "account"; public XmppConnectionService xmppConnectionService; public boolean xmppConnectionServiceBound = false; @@ -91,6 +107,7 @@ public abstract class XmppActivity extends Activity { protected int mPrimaryTextColor; protected int mSecondaryTextColor; + protected int mTertiaryTextColor; protected int mPrimaryBackgroundColor; protected int mSecondaryBackgroundColor; protected int mColorRed; @@ -98,6 +115,8 @@ public abstract class XmppActivity extends Activity { protected int mColorGreen; protected int mPrimaryColor; + protected boolean mUseSubject = true; + private DisplayMetrics metrics; protected int mTheme; protected boolean mUsingEnterKey = false; @@ -115,7 +134,7 @@ public abstract class XmppActivity extends Activity { protected ConferenceInvite mPendingConferenceInvite = null; - protected void refreshUi() { + protected final void refreshUi() { final long diff = SystemClock.elapsedRealtime() - mLastUiRefresh; if (diff > Config.REFRESH_UI_INTERVAL) { mRefreshUiHandler.removeCallbacks(mRefreshUiRunnable); @@ -127,9 +146,7 @@ public abstract class XmppActivity extends Activity { } } - protected void refreshUiReal() { - - }; + abstract protected void refreshUiReal(); protected interface OnValueEdited { public void onValueEdited(String value); @@ -274,6 +291,9 @@ public abstract class XmppActivity extends Activity { if (this instanceof XmppConnectionService.OnAccountUpdate) { this.xmppConnectionService.setOnAccountListChangedListener((XmppConnectionService.OnAccountUpdate) this); } + if (this instanceof XmppConnectionService.OnCaptchaRequested) { + this.xmppConnectionService.setOnCaptchaRequestedListener((XmppConnectionService.OnCaptchaRequested) this); + } if (this instanceof XmppConnectionService.OnRosterUpdate) { this.xmppConnectionService.setOnRosterUpdateListener((XmppConnectionService.OnRosterUpdate) this); } @@ -286,6 +306,9 @@ public abstract class XmppActivity extends Activity { if (this instanceof XmppConnectionService.OnShowErrorToast) { this.xmppConnectionService.setOnShowErrorToastListener((XmppConnectionService.OnShowErrorToast) this); } + if (this instanceof OnKeyStatusUpdated) { + this.xmppConnectionService.setOnKeyStatusUpdatedListener((OnKeyStatusUpdated) this); + } } protected void unregisterListeners() { @@ -295,6 +318,9 @@ public abstract class XmppActivity extends Activity { if (this instanceof XmppConnectionService.OnAccountUpdate) { this.xmppConnectionService.removeOnAccountListChangedListener(); } + if (this instanceof XmppConnectionService.OnCaptchaRequested) { + this.xmppConnectionService.removeOnCaptchaRequestedListener(); + } if (this instanceof XmppConnectionService.OnRosterUpdate) { this.xmppConnectionService.removeOnRosterUpdateListener(); } @@ -307,6 +333,9 @@ public abstract class XmppActivity extends Activity { if (this instanceof XmppConnectionService.OnShowErrorToast) { this.xmppConnectionService.removeOnShowErrorToastListener(); } + if (this instanceof OnKeyStatusUpdated) { + this.xmppConnectionService.removeOnNewKeysAvailableListener(); + } } @Override @@ -335,34 +364,63 @@ public abstract class XmppActivity extends Activity { ExceptionHelper.init(getApplicationContext()); mPrimaryTextColor = getResources().getColor(R.color.primaryText); mSecondaryTextColor = getResources().getColor(R.color.secondaryText); + mTertiaryTextColor = getResources().getColor(R.color.black12); mColorRed = getResources().getColor(R.color.warning); + mColorOrange = getResources().getColor(R.color.orange500); mColorGreen = getResources().getColor(R.color.online); + mPrimaryColor = getResources().getColor(R.color.primary); mPrimaryBackgroundColor = getResources().getColor(R.color.primaryBackground); mSecondaryBackgroundColor = getResources().getColor(R.color.secondaryBackground); this.mTheme = findTheme(); setTheme(this.mTheme); this.mUsingEnterKey = ConversationsPlusPreferences.displayEnterKey(); - + mUseSubject = getPreferences().getBoolean("use_subject", true); final ActionBar ab = getActionBar(); if (ab!=null) { ab.setDisplayHomeAsUpEnabled(true); } } + protected boolean showBatteryOptimizationWarning() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE); + return !pm.isIgnoringBatteryOptimizations(getPackageName()); + } else { + return false; + } + } + + protected boolean usingEnterKey() { + return getPreferences().getBoolean("display_enter_key", false); + } + + protected SharedPreferences getPreferences() { + return PreferenceManager + .getDefaultSharedPreferences(getApplicationContext()); + } + + public boolean useSubjectToIdentifyConference() { + return mUseSubject; + } + public void switchToConversation(Conversation conversation) { switchToConversation(conversation, null, false); } public void switchToConversation(Conversation conversation, String text, boolean newTask) { - switchToConversation(conversation,text,null,newTask); + switchToConversation(conversation,text,null,false,newTask); } public void highlightInMuc(Conversation conversation, String nick) { - switchToConversation(conversation, null, nick, false); + switchToConversation(conversation, null, nick, false, false); } - private void switchToConversation(Conversation conversation, String text, String nick, boolean newTask) { + public void privateMsgInMuc(Conversation conversation, String nick) { + switchToConversation(conversation, null, nick, true, false); + } + + private void switchToConversation(Conversation conversation, String text, String nick, boolean pm, boolean newTask) { Intent viewConversationIntent = new Intent(this, ConversationActivity.class); viewConversationIntent.setAction(Intent.ACTION_VIEW); @@ -373,6 +431,7 @@ public abstract class XmppActivity extends Activity { } if (nick != null) { viewConversationIntent.putExtra(ConversationActivity.NICK, nick); + viewConversationIntent.putExtra(ConversationActivity.PRIVATE_MESSAGE,pm); } viewConversationIntent.setType(ConversationActivity.VIEW_CONVERSATION); if (newTask) { @@ -388,16 +447,26 @@ public abstract class XmppActivity extends Activity { } public void switchToContactDetails(Contact contact) { + switchToContactDetails(contact, null); + } + + public void switchToContactDetails(Contact contact, String messageFingerprint) { Intent intent = new Intent(this, ContactDetailsActivity.class); intent.setAction(ContactDetailsActivity.ACTION_VIEW_CONTACT); - intent.putExtra("account", contact.getAccount().getJid().toBareJid().toString()); + intent.putExtra(EXTRA_ACCOUNT, contact.getAccount().getJid().toBareJid().toString()); intent.putExtra("contact", contact.getJid().toString()); + intent.putExtra("fingerprint", messageFingerprint); startActivity(intent); } public void switchToAccount(Account account) { + switchToAccount(account, false); + } + + public void switchToAccount(Account account, boolean init) { Intent intent = new Intent(this, EditAccountActivity.class); intent.putExtra("jid", account.getJid().toBareJid().toString()); + intent.putExtra("init", init); startActivity(intent); } @@ -418,54 +487,81 @@ public abstract class XmppActivity extends Activity { intent.putExtra("filter_contacts", contacts.toArray(new String[contacts.size()])); intent.putExtra("conversation", conversation.getUuid()); intent.putExtra("multiple", true); + intent.putExtra("show_enter_jid", true); + intent.putExtra(EXTRA_ACCOUNT, conversation.getAccount().getJid().toBareJid().toString()); startActivityForResult(intent, REQUEST_INVITE_TO_CONVERSATION); } protected void announcePgp(Account account, final Conversation conversation) { - xmppConnectionService.getPgpEngine().generateSignature(account, - "online", new UiCallback<Account>() { - - @Override - public void userInputRequried(PendingIntent pi, - Account account) { - try { - startIntentSenderForResult(pi.getIntentSender(), - REQUEST_ANNOUNCE_PGP, null, 0, 0, 0); - } catch (final SendIntentException ignored) { - } - } + if (account.getPgpId() == -1) { + choosePgpSignId(account); + } else { + xmppConnectionService.getPgpEngine().generateSignature(account, "", new UiCallback<Account>() { - @Override - public void success(Account account) { - xmppConnectionService.databaseBackend.updateAccount(account); - xmppConnectionService.sendPresence(account); - if (conversation != null) { - conversation.setNextEncryption(Message.ENCRYPTION_PGP); - xmppConnectionService.databaseBackend.updateConversation(conversation); - } - } + @Override + public void userInputRequried(PendingIntent pi, + Account account) { + try { + startIntentSenderForResult(pi.getIntentSender(), + REQUEST_ANNOUNCE_PGP, null, 0, 0, 0); + } catch (final SendIntentException ignored) { + } + } - @Override - public void error(int error, Account account) { - displayErrorDialog(error); - } - }); + @Override + public void success(Account account) { + xmppConnectionService.databaseBackend.updateAccount(account); + xmppConnectionService.sendPresence(account); + if (conversation != null) { + conversation.setNextEncryption(Message.ENCRYPTION_PGP); + xmppConnectionService.databaseBackend.updateConversation(conversation); + } + } + + @Override + public void error(int error, Account account) { + displayErrorDialog(error); + } + }); + } } - public void displayErrorDialog(final int errorCode) { + protected void choosePgpSignId(Account account) { + xmppConnectionService.getPgpEngine().chooseKey(account, new UiCallback<Account>() { + @Override + public void success(Account account1) { + } + + @Override + public void error(int errorCode, Account object) { + + } + + @Override + public void userInputRequried(PendingIntent pi, Account object) { + try { + startIntentSenderForResult(pi.getIntentSender(), + REQUEST_CHOOSE_PGP_ID, null, 0, 0, 0); + } catch (final SendIntentException ignored) { + } + } + }); + } + + protected void displayErrorDialog(final int errorCode) { runOnUiThread(new Runnable() { - @Override - public void run() { - AlertDialog.Builder builder = new AlertDialog.Builder( - XmppActivity.this); - builder.setIconAttribute(android.R.attr.alertDialogIcon); - builder.setTitle(getString(R.string.error)); - builder.setMessage(errorCode); - builder.setNeutralButton(R.string.accept, null); - builder.create().show(); - } - }); + @Override + public void run() { + AlertDialog.Builder builder = new AlertDialog.Builder( + XmppActivity.this); + builder.setIconAttribute(android.R.attr.alertDialogIcon); + builder.setTitle(getString(R.string.error)); + builder.setMessage(errorCode); + builder.setNeutralButton(R.string.accept, null); + builder.create().show(); + } + }); } @@ -504,9 +600,9 @@ public abstract class XmppActivity extends Activity { public void onClick(DialogInterface dialog, int which) { if (xmppConnectionServiceBound) { xmppConnectionService.sendPresencePacket(contact - .getAccount(), xmppConnectionService - .getPresenceGenerator() - .requestPresenceUpdatesFrom(contact)); + .getAccount(), xmppConnectionService + .getPresenceGenerator() + .requestPresenceUpdatesFrom(contact)); } } }); @@ -572,6 +668,151 @@ public abstract class XmppActivity extends Activity { builder.create().show(); } + protected boolean addFingerprintRow(LinearLayout keys, final Account account, final String fingerprint, boolean highlight, View.OnClickListener onKeyClickedListener) { + final XmppAxolotlSession.Trust trust = account.getAxolotlService() + .getFingerprintTrust(fingerprint); + if (trust == null) { + return false; + } + return addFingerprintRowWithListeners(keys, account, fingerprint, highlight, trust, true, + new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + account.getAxolotlService().setFingerprintTrust(fingerprint, + (isChecked) ? XmppAxolotlSession.Trust.TRUSTED : + XmppAxolotlSession.Trust.UNTRUSTED); + } + }, + new View.OnClickListener() { + @Override + public void onClick(View v) { + account.getAxolotlService().setFingerprintTrust(fingerprint, + XmppAxolotlSession.Trust.UNTRUSTED); + v.setEnabled(true); + } + }, + onKeyClickedListener + + ); + } + + protected boolean addFingerprintRowWithListeners(LinearLayout keys, final Account account, + final String fingerprint, + boolean highlight, + XmppAxolotlSession.Trust trust, + boolean showTag, + CompoundButton.OnCheckedChangeListener + onCheckedChangeListener, + View.OnClickListener onClickListener, + View.OnClickListener onKeyClickedListener) { + if (trust == XmppAxolotlSession.Trust.COMPROMISED) { + return false; + } + View view = getLayoutInflater().inflate(R.layout.contact_key, keys, false); + TextView key = (TextView) view.findViewById(R.id.key); + key.setOnClickListener(onKeyClickedListener); + TextView keyType = (TextView) view.findViewById(R.id.key_type); + keyType.setOnClickListener(onKeyClickedListener); + Switch trustToggle = (Switch) view.findViewById(R.id.tgl_trust); + trustToggle.setVisibility(View.VISIBLE); + trustToggle.setOnCheckedChangeListener(onCheckedChangeListener); + trustToggle.setOnClickListener(onClickListener); + final View.OnLongClickListener purge = new View.OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + showPurgeKeyDialog(account, fingerprint); + return true; + } + }; + view.setOnLongClickListener(purge); + key.setOnLongClickListener(purge); + keyType.setOnLongClickListener(purge); + boolean x509 = trust == XmppAxolotlSession.Trust.TRUSTED_X509 || trust == XmppAxolotlSession.Trust.INACTIVE_TRUSTED_X509; + switch (trust) { + case UNTRUSTED: + case TRUSTED: + case TRUSTED_X509: + trustToggle.setChecked(trust.trusted(), false); + trustToggle.setEnabled(trust != XmppAxolotlSession.Trust.TRUSTED_X509); + if (trust == XmppAxolotlSession.Trust.TRUSTED_X509) { + trustToggle.setOnClickListener(null); + } + key.setTextColor(getPrimaryTextColor()); + keyType.setTextColor(getSecondaryTextColor()); + break; + case UNDECIDED: + trustToggle.setChecked(false, false); + trustToggle.setEnabled(false); + key.setTextColor(getPrimaryTextColor()); + keyType.setTextColor(getSecondaryTextColor()); + break; + case INACTIVE_UNTRUSTED: + case INACTIVE_UNDECIDED: + trustToggle.setOnClickListener(null); + trustToggle.setChecked(false, false); + trustToggle.setEnabled(false); + key.setTextColor(getTertiaryTextColor()); + keyType.setTextColor(getTertiaryTextColor()); + break; + case INACTIVE_TRUSTED: + case INACTIVE_TRUSTED_X509: + trustToggle.setOnClickListener(null); + trustToggle.setChecked(true, false); + trustToggle.setEnabled(false); + key.setTextColor(getTertiaryTextColor()); + keyType.setTextColor(getTertiaryTextColor()); + break; + } + + if (showTag) { + keyType.setText(getString(x509 ? R.string.omemo_fingerprint_x509 : R.string.omemo_fingerprint)); + } else { + keyType.setVisibility(View.GONE); + } + if (highlight) { + keyType.setTextColor(getResources().getColor(R.color.accent)); + keyType.setText(getString(x509 ? R.string.omemo_fingerprint_x509_selected_message : R.string.omemo_fingerprint_selected_message)); + } else { + keyType.setText(getString(x509 ? R.string.omemo_fingerprint_x509 : R.string.omemo_fingerprint)); + } + + key.setText(CryptoHelper.prettifyFingerprint(fingerprint.substring(2))); + keys.addView(view); + return true; + } + + public void showPurgeKeyDialog(final Account account, final String fingerprint) { + Builder builder = new Builder(this); + builder.setTitle(getString(R.string.purge_key)); + builder.setIconAttribute(android.R.attr.alertDialogIcon); + builder.setMessage(getString(R.string.purge_key_desc_part1) + + "\n\n" + CryptoHelper.prettifyFingerprint(fingerprint.substring(2)) + + "\n\n" + getString(R.string.purge_key_desc_part2)); + builder.setNegativeButton(getString(R.string.cancel), null); + builder.setPositiveButton(getString(R.string.accept), + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + account.getAxolotlService().purgeKey(fingerprint); + refreshUi(); + } + }); + builder.create().show(); + } + + public boolean hasStoragePermission(int requestCode) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { + requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, requestCode); + return false; + } else { + return true; + } + } else { + return true; + } + } + public void selectPresence(final Conversation conversation, final OnPresenceSelected listener) { final Contact contact = conversation.getContact(); @@ -691,6 +932,10 @@ public abstract class XmppActivity extends Activity { } }; + public int getTertiaryTextColor() { + return this.mTertiaryTextColor; + } + public int getSecondaryTextColor() { return this.mSecondaryTextColor; } @@ -738,7 +983,7 @@ public abstract class XmppActivity extends Activity { @Override public NdefMessage createNdefMessage(NfcEvent nfcEvent) { return new NdefMessage(new NdefRecord[]{ - NdefRecord.createUri(getShareableUri()), + NdefRecord.createUri(getShareableUri()), NdefRecord.createApplicationRecord("eu.siacs.conversations") }); } @@ -819,6 +1064,15 @@ public abstract class XmppActivity extends Activity { } } + protected Account extractAccount(Intent intent) { + String jid = intent != null ? intent.getStringExtra(EXTRA_ACCOUNT) : null; + try { + return jid != null ? xmppConnectionService.findAccountByJid(Jid.fromString(jid)) : null; + } catch (InvalidJidException e) { + return null; + } + } + public static class ConferenceInvite { private String uuid; private List<Jid> jids = new ArrayList<>(); diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java index 1ca868b8..24b58af4 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java @@ -16,7 +16,15 @@ import android.widget.ArrayAdapter; import android.widget.CompoundButton; import android.widget.ImageView; import android.widget.TextView; -import android.widget.Switch; + +import java.util.List; + +import eu.siacs.conversations.Config; +import eu.siacs.conversations.R; +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.ui.ManageAccountActivity; +import eu.siacs.conversations.ui.XmppActivity; +import eu.siacs.conversations.ui.widget.Switch; public class AccountAdapter extends ArrayAdapter<Account> { @@ -36,7 +44,11 @@ public class AccountAdapter extends ArrayAdapter<Account> { view = inflater.inflate(R.layout.account_row, parent, false); } TextView jid = (TextView) view.findViewById(R.id.account_jid); - jid.setText(account.getJid().toBareJid().toString()); + if (Config.DOMAIN_LOCK != null) { + jid.setText(account.getJid().getLocalpart()); + } else { + jid.setText(account.getJid().toBareJid().toString()); + } TextView statusView = (TextView) view.findViewById(R.id.account_status); ImageView imageView = (ImageView) view.findViewById(R.id.account_image); imageView.setImageBitmap(AvatarService.getInstance().get(account, activity.getPixel(48))); @@ -55,8 +67,7 @@ public class AccountAdapter extends ArrayAdapter<Account> { } final Switch tglAccountState = (Switch) view.findViewById(R.id.tgl_account_status); final boolean isDisabled = (account.getStatus() == Account.State.DISABLED); - tglAccountState.setOnCheckedChangeListener(null); - tglAccountState.setChecked(!isDisabled); + tglAccountState.setChecked(!isDisabled,false); tglAccountState.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton compoundButton, boolean b) { diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java index c31b230e..b4069fd1 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java @@ -27,6 +27,7 @@ import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Conversation; +import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.Presences; @@ -73,14 +74,15 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> { view.findViewById(R.id.conversationListRowFrame).setBackgroundColor(c); } TextView convName = (TextView) view.findViewById(R.id.conversation_name); - if (conversation.getMode() == Conversation.MODE_SINGLE || ConversationsPlusPreferences.useSubject()) { + if (conversation.getMode() == Conversation.MODE_SINGLE || activity.useSubjectToIdentifyConference()) { convName.setText(conversation.getName()); } else { convName.setText(conversation.getJid().toBareJid().toString()); } - EmojiconTextView mLastMessage = (EmojiconTextView) view.findViewById(R.id.conversation_lastmsg); + TextView mLastMessage = (TextView) view.findViewById(R.id.conversation_lastmsg); TextView mTimestamp = (TextView) view.findViewById(R.id.conversation_lastupdate); ImageView imagePreview = (ImageView) view.findViewById(R.id.conversation_lastimage); + ImageView notificationStatus = (ImageView) view.findViewById(R.id.notification_status); if (Settings.SHOW_ONLINE_STATUS && conversation.getAccount().getStatus() == Account.State.ONLINE) { TextView status = (TextView) view.findViewById(R.id.status); @@ -124,18 +126,7 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> { Pair<String,Boolean> preview = UIHelper.getMessagePreview(activity,message); mLastMessage.setVisibility(View.VISIBLE); imagePreview.setVisibility(View.GONE); - CharSequence msgText = preview.first; - String msgPrefix = null; - if (message.getStatus() == Message.STATUS_SEND - || message.getStatus() == Message.STATUS_SEND_DISPLAYED - || message.getStatus() == Message.STATUS_SEND_FAILED - || message.getStatus() == Message.STATUS_SEND_RECEIVED) { - msgPrefix = activity.getString(R.string.cplus_me); - } else if (conversation.getMode() == Conversation.MODE_MULTI) { - msgPrefix = UIHelper.getMessageDisplayName(message); - } - String lastMessagePreview = ((null == msgPrefix || msgPrefix.isEmpty()) ? "" : (msgPrefix + ": ")) + msgText; - mLastMessage.setText(lastMessagePreview); + mLastMessage.setText(preview.first); if (preview.second) { if (conversation.isRead()) { mLastMessage.setTypeface(null, Typeface.ITALIC); @@ -151,7 +142,21 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> { } } - mTimestamp.setText(UIHelper.readableTimeDifference(activity, message.getTimeSent())); + long muted_till = conversation.getLongAttribute(Conversation.ATTRIBUTE_MUTED_TILL,0); + if (muted_till == Long.MAX_VALUE) { + notificationStatus.setVisibility(View.VISIBLE); + notificationStatus.setImageResource(R.drawable.ic_notifications_off_grey600_24dp); + } else if (muted_till >= System.currentTimeMillis()) { + notificationStatus.setVisibility(View.VISIBLE); + notificationStatus.setImageResource(R.drawable.ic_notifications_paused_grey600_24dp); + } else if (conversation.alwaysNotify()) { + notificationStatus.setVisibility(View.GONE); + } else { + notificationStatus.setVisibility(View.VISIBLE); + notificationStatus.setImageResource(R.drawable.ic_notifications_none_grey600_24dp); + } + + mTimestamp.setText(UIHelper.readableTimeDifference(activity,conversation.getLatestMessage().getTimeSent())); ImageView profilePicture = (ImageView) view.findViewById(R.id.conversation_image); profilePicture.setOnLongClickListener(new ShowResourcesListDialogListener(activity, conversation.getContact())); loadAvatar(conversation, profilePicture); @@ -241,4 +246,4 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> { return bitmapWorkerTaskReference.get(); } } -} +}
\ No newline at end of file diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/KnownHostsAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/KnownHostsAdapter.java index 0993735f..39bfc082 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/KnownHostsAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/KnownHostsAdapter.java @@ -1,13 +1,13 @@ package eu.siacs.conversations.ui.adapter; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; - import android.content.Context; import android.widget.ArrayAdapter; import android.widget.Filter; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + public class KnownHostsAdapter extends ArrayAdapter<String> { private ArrayList<String> domains; private Filter domainFilter = new Filter() { @@ -15,7 +15,7 @@ public class KnownHostsAdapter extends ArrayAdapter<String> { @Override protected FilterResults performFiltering(CharSequence constraint) { if (constraint != null) { - ArrayList<String> suggestions = new ArrayList<String>(); + ArrayList<String> suggestions = new ArrayList<>(); final String[] split = constraint.toString().split("@"); if (split.length == 1) { for (String domain : domains) { @@ -58,10 +58,9 @@ public class KnownHostsAdapter extends ArrayAdapter<String> { } }; - public KnownHostsAdapter(Context context, int viewResourceId, - List<String> mKnownHosts) { + public KnownHostsAdapter(Context context, int viewResourceId, List<String> mKnownHosts) { super(context, viewResourceId, new ArrayList<String>()); - domains = new ArrayList<String>(mKnownHosts); + domains = new ArrayList<>(mKnownHosts); } @Override diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java index 9a181a47..90d5a382 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java @@ -16,11 +16,13 @@ import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.xmpp.jid.Jid; import android.content.Context; +import android.content.SharedPreferences; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.AsyncTask; +import android.preference.PreferenceManager; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -86,9 +88,10 @@ public class ListItemAdapter extends ArrayAdapter<ListItem> { } final Jid jid = item.getJid(); if (jid != null) { + tvJid.setVisibility(View.VISIBLE); tvJid.setText(jid.toString()); } else { - tvJid.setText(""); + tvJid.setVisibility(View.GONE); } tvName.setText(item.getDisplayName()); loadAvatar(item,picture); @@ -100,7 +103,7 @@ public class ListItemAdapter extends ArrayAdapter<ListItem> { } public interface OnTagClickedListener { - public void onTagClicked(String tag); + void onTagClicked(String tag); } class BitmapWorkerTask extends AsyncTask<ListItem, Void, Bitmap> { diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java index 5dffc24d..ed2a23d2 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -1,10 +1,16 @@ package eu.siacs.conversations.ui.adapter; +import android.content.ActivityNotFoundException; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.content.res.Resources; +import android.graphics.Bitmap; import android.graphics.Typeface; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; import android.net.Uri; +import android.os.AsyncTask; import android.preference.PreferenceManager; import android.text.Spannable; import android.text.SpannableString; @@ -12,6 +18,8 @@ import android.text.Spanned; import android.text.style.ForegroundColorSpan; import android.text.style.RelativeSizeSpan; import android.text.style.StyleSpan; +import android.util.DisplayMetrics; +import android.util.Patterns; import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnLongClickListener; @@ -23,11 +31,15 @@ import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; +import java.lang.ref.WeakReference; import java.util.List; +import java.util.concurrent.RejectedExecutionException; +import java.util.regex.Matcher; import de.thedevstack.conversationsplus.ConversationsPlusPreferences; import eu.siacs.conversations.R; +import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; @@ -35,9 +47,11 @@ import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.entities.DownloadableFile; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.Message.FileParams; +import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.services.AvatarService; import eu.siacs.conversations.persistance.FileBackend; import eu.siacs.conversations.ui.ConversationActivity; +import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.GeoHelper; import eu.siacs.conversations.utils.UIHelper; @@ -48,9 +62,12 @@ public class MessageAdapter extends ArrayAdapter<Message> { private static final int SENT = 0; private static final int RECEIVED = 1; private static final int STATUS = 2; + private static final int NULL = 3; private ConversationActivity activity; + private DisplayMetrics metrics; + private OnContactPictureClicked mOnContactPictureClickedListener; private OnContactPictureLongClicked mOnContactPictureLongClickedListener; @@ -62,10 +79,14 @@ public class MessageAdapter extends ArrayAdapter<Message> { return true; } }; + private boolean mIndicateReceived = false; + private boolean mUseWhiteBackground = false; public MessageAdapter(ConversationActivity activity, List<Message> messages) { super(activity, 0, messages); this.activity = activity; + metrics = getContext().getResources().getDisplayMetrics(); + updatePreferences(); } public void setOnContactPictureClicked(OnContactPictureClicked listener) { @@ -82,18 +103,30 @@ public class MessageAdapter extends ArrayAdapter<Message> { return 3; } - @Override - public int getItemViewType(int position) { - if (getItem(position).getType() == Message.TYPE_STATUS) { + public int getItemViewType(Message message) { + if (message.getType() == Message.TYPE_STATUS) { return STATUS; - } else if (getItem(position).getStatus() <= Message.STATUS_RECEIVED) { + } else if (message.getStatus() <= Message.STATUS_RECEIVED) { return RECEIVED; + } + + return SENT; + } + + @Override + public int getItemViewType(int position) { + return this.getItemViewType(getItem(position)); + } + + private int getMessageTextColor(boolean onDark, boolean primary) { + if (onDark) { + return activity.getResources().getColor(primary ? R.color.white : R.color.white70); } else { - return SENT; + return activity.getResources().getColor(primary ? R.color.black87 : R.color.black54); } } - private void displayStatus(ViewHolder viewHolder, Message message) { + private void displayStatus(ViewHolder viewHolder, Message message, int type, boolean darkBackground) { String filesize = null; String info = null; boolean error = false; @@ -148,15 +181,40 @@ public class MessageAdapter extends ArrayAdapter<Message> { } break; } - if (error) { + if (error && type == SENT) { viewHolder.time.setTextColor(activity.getWarningTextColor()); } else { - viewHolder.time.setTextColor(activity.getSecondaryTextColor()); + viewHolder.time.setTextColor(this.getMessageTextColor(darkBackground,false)); } if (message.getEncryption() == Message.ENCRYPTION_NONE) { viewHolder.indicator.setVisibility(View.GONE); } else { + viewHolder.indicator.setImageResource(darkBackground ? R.drawable.ic_secure_indicator_white : R.drawable.ic_secure_indicator); viewHolder.indicator.setVisibility(View.VISIBLE); + if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) { + XmppAxolotlSession.Trust trust = message.getConversation() + .getAccount().getAxolotlService().getFingerprintTrust( + message.getAxolotlFingerprint()); + + if(trust == null || (!trust.trusted() && !trust.trustedInactive())) { + viewHolder.indicator.setColorFilter(activity.getWarningTextColor()); + viewHolder.indicator.setAlpha(1.0f); + } else { + viewHolder.indicator.clearColorFilter(); + if (darkBackground) { + viewHolder.indicator.setAlpha(0.7f); + } else { + viewHolder.indicator.setAlpha(0.57f); + } + } + } else { + viewHolder.indicator.clearColorFilter(); + if (darkBackground) { + viewHolder.indicator.setAlpha(0.7f); + } else { + viewHolder.indicator.setAlpha(0.57f); + } + } } String formatedTime = UIHelper.readableTimeDifferenceFull(getContext(), @@ -188,27 +246,27 @@ public class MessageAdapter extends ArrayAdapter<Message> { } } - private void displayInfoMessage(ViewHolder viewHolder, String text) { + private void displayInfoMessage(ViewHolder viewHolder, String text, boolean darkBackground) { if (viewHolder.download_button != null) { viewHolder.download_button.setVisibility(View.GONE); } viewHolder.image.setVisibility(View.GONE); viewHolder.messageBody.setVisibility(View.VISIBLE); viewHolder.messageBody.setText(text); - viewHolder.messageBody.setTextColor(activity.getSecondaryTextColor()); + viewHolder.messageBody.setTextColor(getMessageTextColor(darkBackground, false)); viewHolder.messageBody.setTypeface(null, Typeface.ITALIC); viewHolder.messageBody.setTextIsSelectable(false); } - private void displayDecryptionFailed(ViewHolder viewHolder) { + private void displayDecryptionFailed(ViewHolder viewHolder, boolean darkBackground) { if (viewHolder.download_button != null) { viewHolder.download_button.setVisibility(View.GONE); } viewHolder.image.setVisibility(View.GONE); viewHolder.messageBody.setVisibility(View.VISIBLE); viewHolder.messageBody.setText(getContext().getString( - R.string.decryption_failed)); - viewHolder.messageBody.setTextColor(activity.getWarningTextColor()); + R.string.decryption_failed)); + viewHolder.messageBody.setTextColor(getMessageTextColor(darkBackground, false)); viewHolder.messageBody.setTypeface(null, Typeface.NORMAL); viewHolder.messageBody.setTextIsSelectable(false); } @@ -226,7 +284,7 @@ public class MessageAdapter extends ArrayAdapter<Message> { viewHolder.messageBody.setText(span); } - private void displayTextMessage(final ViewHolder viewHolder, final Message message) { + private void displayTextMessage(final ViewHolder viewHolder, final Message message, boolean darkBackground) { if (viewHolder.download_button != null) { viewHolder.download_button.setVisibility(View.GONE); } @@ -235,7 +293,12 @@ public class MessageAdapter extends ArrayAdapter<Message> { viewHolder.messageBody.setIncludeFontPadding(true); if (message.getBody() != null) { final String nick = UIHelper.getMessageDisplayName(message); - final String body = message.getMergedBody().replaceAll("^" + Message.ME_COMMAND,nick + " "); + String body; + try { + body = message.getMergedBody().replaceAll("^" + Message.ME_COMMAND, nick + " "); + } catch (ArrayIndexOutOfBoundsException e) { + body = message.getMergedBody(); + } final SpannableString formattedBody = new SpannableString(body); int i = body.indexOf(Message.MERGE_SEPARATOR); while(i >= 0) { @@ -244,14 +307,13 @@ public class MessageAdapter extends ArrayAdapter<Message> { i = body.indexOf(Message.MERGE_SEPARATOR,end); } if (message.getType() != Message.TYPE_PRIVATE) { - if (message.hasMeCommand()) { final Spannable span = new SpannableString(formattedBody); span.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), 0, nick.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); viewHolder.messageBody.setText(span); } else { - viewHolder.messageBody.setText(formattedBody); + viewHolder.messageBody.setText(formattedBody); } } else { String privateMarker; @@ -269,8 +331,7 @@ public class MessageAdapter extends ArrayAdapter<Message> { } final Spannable span = new SpannableString(privateMarker + " " + formattedBody); - span.setSpan(new ForegroundColorSpan(activity - .getSecondaryTextColor()), 0, privateMarker + span.setSpan(new ForegroundColorSpan(getMessageTextColor(darkBackground,false)), 0, privateMarker .length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); span.setSpan(new StyleSpan(Typeface.BOLD), 0, privateMarker.length(), @@ -282,12 +343,21 @@ public class MessageAdapter extends ArrayAdapter<Message> { } viewHolder.messageBody.setText(span); } + int urlCount = 0; + Matcher matcher = Patterns.WEB_URL.matcher(body); + while (matcher.find()) { + urlCount++; + } + viewHolder.messageBody.setTextIsSelectable(urlCount <= 1); } else { viewHolder.messageBody.setText(""); + viewHolder.messageBody.setTextIsSelectable(false); } - viewHolder.messageBody.setTextColor(activity.getPrimaryTextColor()); + viewHolder.messageBody.setTextColor(this.getMessageTextColor(darkBackground, true)); + viewHolder.messageBody.setLinkTextColor(this.getMessageTextColor(darkBackground, true)); + viewHolder.messageBody.setHighlightColor(activity.getResources().getColor(darkBackground ? R.color.grey800 : R.color.grey500)); viewHolder.messageBody.setTypeface(null, Typeface.NORMAL); - viewHolder.messageBody.setTextIsSelectable(true); + viewHolder.messageBody.setOnLongClickListener(openContextMenu); } private void displayDownloadableMessage(ViewHolder viewHolder, @@ -298,11 +368,11 @@ public class MessageAdapter extends ArrayAdapter<Message> { viewHolder.download_button.setText(text); viewHolder.download_button.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - startDownloadable(message); - } - }); + @Override + public void onClick(View v) { + activity.startDownloadable(message); + } + }); viewHolder.download_button.setOnLongClickListener(openContextMenu); } @@ -355,25 +425,25 @@ public class MessageAdapter extends ArrayAdapter<Message> { scalledW = (int) target; scalledH = (int) (params.height / ((double) params.width / target)); } - viewHolder.image.setLayoutParams(new LinearLayout.LayoutParams( - scalledW, scalledH));*/ + LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(scalledW, scalledH); + layoutParams.setMargins(0, (int) (metrics.density * 4), 0, (int) (metrics.density * 4)); + viewHolder.image.setLayoutParams(layoutParams);*/ //TODO Why should this be calculated by hand??? activity.loadBitmap(message, viewHolder.image, true); - viewHolder.image.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setDataAndType(FileBackend.getJingleFileUri(message), "image/*"); - getContext().startActivity(intent); - } - }); + viewHolder.image.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + openDownloadable(message); + } + }); viewHolder.image.setOnLongClickListener(openContextMenu); } @Override public View getView(int position, View view, ViewGroup parent) { final Message message = getItem(position); + final boolean isInValidSession = message.isValidInSession(); final Conversation conversation = message.getConversation(); final Account account = conversation.getAccount(); final int type = getItemViewType(position); @@ -394,7 +464,7 @@ public class MessageAdapter extends ArrayAdapter<Message> { .findViewById(R.id.security_indicator); viewHolder.image = (ImageView) view .findViewById(R.id.message_image); - viewHolder.messageBody = (EmojiconTextView) view + viewHolder.messageBody = (TextView) view .findViewById(R.id.message_body); viewHolder.time = (TextView) view .findViewById(R.id.message_time); @@ -414,12 +484,13 @@ public class MessageAdapter extends ArrayAdapter<Message> { .findViewById(R.id.security_indicator); viewHolder.image = (ImageView) view .findViewById(R.id.message_image); - viewHolder.messageBody = (EmojiconTextView) view + viewHolder.messageBody = (TextView) view .findViewById(R.id.message_body); viewHolder.time = (TextView) view .findViewById(R.id.message_time); viewHolder.indicatorReceived = (ImageView) view .findViewById(R.id.indicator_received); + viewHolder.encryption = (TextView) view.findViewById(R.id.message_encryption); break; case STATUS: view = activity.getLayoutInflater().inflate(R.layout.message_status, parent, false); @@ -438,6 +509,8 @@ public class MessageAdapter extends ArrayAdapter<Message> { } } + boolean darkBackground = (type == RECEIVED && (!isInValidSession || !mUseWhiteBackground)); + if (type == STATUS) { if (conversation.getMode() == Conversation.MODE_SINGLE) { viewHolder.contact_picture.setImageBitmap(AvatarService.getInstance().get(conversation.getContact(), @@ -446,17 +519,8 @@ public class MessageAdapter extends ArrayAdapter<Message> { viewHolder.status_message.setText(message.getBody()); } return view; - } else if (type == RECEIVED) { - Contact contact = message.getContact(); - if (contact != null) { - viewHolder.contact_picture.setImageBitmap(AvatarService.getInstance().get(contact, activity.getPixel(48))); - } else if (conversation.getMode() == Conversation.MODE_MULTI) { - viewHolder.contact_picture.setImageBitmap(AvatarService.getInstance().get( - UIHelper.getMessageDisplayName(message), - activity.getPixel(48))); - } - } else if (type == SENT) { - viewHolder.contact_picture.setImageBitmap(AvatarService.getInstance().get(account, activity.getPixel(48))); + } else { + loadAvatar(message,viewHolder.contact_picture); } viewHolder.contact_picture @@ -466,7 +530,7 @@ public class MessageAdapter extends ArrayAdapter<Message> { public void onClick(View v) { if (MessageAdapter.this.mOnContactPictureClickedListener != null) { MessageAdapter.this.mOnContactPictureClickedListener - .onContactPictureClicked(message); + .onContactPictureClicked(message); } } @@ -478,7 +542,7 @@ public class MessageAdapter extends ArrayAdapter<Message> { public boolean onLongClick(View v) { if (MessageAdapter.this.mOnContactPictureLongClickedListener != null) { MessageAdapter.this.mOnContactPictureLongClickedListener - .onContactPictureLongClicked(message); + .onContactPictureLongClicked(message); return true; } else { return false; @@ -493,7 +557,7 @@ public class MessageAdapter extends ArrayAdapter<Message> { } else if (transferable.getStatus() == Transferable.STATUS_OFFER_CHECK_FILESIZE) { displayDownloadableMessage(viewHolder, message, activity.getString(R.string.check_x_filesize, UIHelper.getFileDescriptionString(activity, message))); } else { - displayInfoMessage(viewHolder, UIHelper.getMessagePreview(activity, message).first); + displayInfoMessage(viewHolder, UIHelper.getMessagePreview(activity, message).first,darkBackground); } } else if (message.getType() == Message.TYPE_IMAGE && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) { displayImageMessage(viewHolder, message); @@ -505,10 +569,13 @@ public class MessageAdapter extends ArrayAdapter<Message> { } } else if (message.getEncryption() == Message.ENCRYPTION_PGP) { if (activity.hasPgp()) { - displayInfoMessage(viewHolder,activity.getString(R.string.encrypted_message)); + if (account.getPgpDecryptionService().isRunning()) { + displayInfoMessage(viewHolder, activity.getString(R.string.message_decrypting), darkBackground); + } else { + displayInfoMessage(viewHolder, activity.getString(R.string.pgp_message), darkBackground); + } } else { - displayInfoMessage(viewHolder, - activity.getString(R.string.install_openkeychain)); + displayInfoMessage(viewHolder,activity.getString(R.string.install_openkeychain),darkBackground); if (viewHolder != null) { viewHolder.message_box .setOnClickListener(new OnClickListener() { @@ -521,49 +588,63 @@ public class MessageAdapter extends ArrayAdapter<Message> { } } } else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) { - displayDecryptionFailed(viewHolder); + displayDecryptionFailed(viewHolder,darkBackground); } else { if (GeoHelper.isGeoUri(message.getBody())) { displayLocationMessage(viewHolder,message); + } else if (message.bodyIsHeart()) { + displayHeartMessage(viewHolder, message.getBody().trim()); } else if (message.treatAsDownloadable() == Message.Decision.MUST) { displayDownloadableMessage(viewHolder, message, activity.getString(R.string.check_x_filesize, UIHelper.getFileDescriptionString(activity, message))); } else { - displayTextMessage(viewHolder, message); + displayTextMessage(viewHolder, message, darkBackground); } } - displayStatus(viewHolder, message); - - return view; - } - - public void startDownloadable(Message message) { - Transferable transferable = message.getTransferable(); - if (transferable != null) { - if (!transferable.start()) { - Toast.makeText(activity, R.string.not_connected_try_again, - Toast.LENGTH_SHORT).show(); + if (type == RECEIVED) { + if(isInValidSession) { + if (mUseWhiteBackground) { + viewHolder.message_box.setBackgroundResource(R.drawable.message_bubble_received_white); + } else { + viewHolder.message_box.setBackgroundResource(R.drawable.message_bubble_received); + } + viewHolder.encryption.setVisibility(View.GONE); + } else { + viewHolder.message_box.setBackgroundResource(R.drawable.message_bubble_received_warning); + viewHolder.encryption.setVisibility(View.VISIBLE); + viewHolder.encryption.setText(CryptoHelper.encryptionTypeToText(message.getEncryption())); } - } else if (message.treatAsDownloadable() != Message.Decision.NEVER) { - activity.xmppConnectionService.getHttpConnectionManager().createNewDownloadConnection(message); } + + displayStatus(viewHolder, message, type, darkBackground); + + return view; } public void openDownloadable(Message message) { - DownloadableFile file = FileBackend.getFile(message); + DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message); if (!file.exists()) { Toast.makeText(activity,R.string.file_deleted,Toast.LENGTH_SHORT).show(); return; } Intent openIntent = new Intent(Intent.ACTION_VIEW); - openIntent.setDataAndType(Uri.fromFile(file), file.getMimeType()); + String mime = file.getMimeType(); + if (mime == null) { + mime = "*/*"; + } + openIntent.setDataAndType(Uri.fromFile(file), mime); PackageManager manager = activity.getPackageManager(); List<ResolveInfo> infos = manager.queryIntentActivities(openIntent, 0); - if (infos.size() > 0) { + if (infos.size() == 0) { + openIntent.setDataAndType(Uri.fromFile(file),"*/*"); + } + try { getContext().startActivity(openIntent); - } else { - Toast.makeText(activity,R.string.no_application_found_to_open_file,Toast.LENGTH_SHORT).show(); + return; + } catch (ActivityNotFoundException e) { + //ignored } + Toast.makeText(activity,R.string.no_application_found_to_open_file,Toast.LENGTH_SHORT).show(); } public void showLocation(Message message) { @@ -576,12 +657,17 @@ public class MessageAdapter extends ArrayAdapter<Message> { Toast.makeText(activity,R.string.no_application_found_to_display_location,Toast.LENGTH_SHORT).show(); } + public void updatePreferences() { + this.mIndicateReceived = activity.indicateReceived(); + this.mUseWhiteBackground = activity.useWhiteBackground(); + } + public interface OnContactPictureClicked { - public void onContactPictureClicked(Message message); + void onContactPictureClicked(Message message); } public interface OnContactPictureLongClicked { - public void onContactPictureLongClicked(Message message); + void onContactPictureLongClicked(Message message); } private static class ViewHolder { @@ -592,8 +678,92 @@ public class MessageAdapter extends ArrayAdapter<Message> { protected ImageView indicator; protected ImageView indicatorReceived; protected TextView time; - protected EmojiconTextView messageBody; + protected TextView messageBody; protected ImageView contact_picture; protected TextView status_message; + protected TextView encryption; + } + + class BitmapWorkerTask extends AsyncTask<Message, Void, Bitmap> { + private final WeakReference<ImageView> imageViewReference; + private Message message = null; + + public BitmapWorkerTask(ImageView imageView) { + imageViewReference = new WeakReference<>(imageView); + } + + @Override + protected Bitmap doInBackground(Message... params) { + return activity.avatarService().get(params[0], activity.getPixel(48), isCancelled()); + } + + @Override + protected void onPostExecute(Bitmap bitmap) { + if (bitmap != null) { + final ImageView imageView = imageViewReference.get(); + if (imageView != null) { + imageView.setImageBitmap(bitmap); + imageView.setBackgroundColor(0x00000000); + } + } + } + } + + public void loadAvatar(Message message, ImageView imageView) { + if (cancelPotentialWork(message, imageView)) { + final Bitmap bm = activity.avatarService().get(message, activity.getPixel(48), true); + if (bm != null) { + imageView.setImageBitmap(bm); + imageView.setBackgroundColor(0x00000000); + } else { + imageView.setBackgroundColor(UIHelper.getColorForName(UIHelper.getMessageDisplayName(message))); + imageView.setImageDrawable(null); + final BitmapWorkerTask task = new BitmapWorkerTask(imageView); + final AsyncDrawable asyncDrawable = new AsyncDrawable(activity.getResources(), null, task); + imageView.setImageDrawable(asyncDrawable); + try { + task.execute(message); + } catch (final RejectedExecutionException ignored) { + } + } + } + } + + public static boolean cancelPotentialWork(Message message, ImageView imageView) { + final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView); + + if (bitmapWorkerTask != null) { + final Message oldMessage = bitmapWorkerTask.message; + if (oldMessage == null || message != oldMessage) { + bitmapWorkerTask.cancel(true); + } else { + return false; + } + } + return true; + } + + private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) { + if (imageView != null) { + final Drawable drawable = imageView.getDrawable(); + if (drawable instanceof AsyncDrawable) { + final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable; + return asyncDrawable.getBitmapWorkerTask(); + } + } + return null; + } + + static class AsyncDrawable extends BitmapDrawable { + private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference; + + public AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) { + super(res, bitmap); + bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask); + } + + public BitmapWorkerTask getBitmapWorkerTask() { + return bitmapWorkerTaskReference.get(); + } } } diff --git a/src/main/java/eu/siacs/conversations/ui/forms/FormBooleanFieldWrapper.java b/src/main/java/eu/siacs/conversations/ui/forms/FormBooleanFieldWrapper.java new file mode 100644 index 00000000..6cb357a9 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/ui/forms/FormBooleanFieldWrapper.java @@ -0,0 +1,80 @@ +package eu.siacs.conversations.ui.forms; + +import android.content.Context; +import android.widget.CheckBox; +import android.widget.CompoundButton; + +import java.util.ArrayList; +import java.util.List; + +import eu.siacs.conversations.R; +import eu.siacs.conversations.xmpp.forms.Field; + +public class FormBooleanFieldWrapper extends FormFieldWrapper { + + protected CheckBox checkBox; + + protected FormBooleanFieldWrapper(Context context, Field field) { + super(context, field); + checkBox = (CheckBox) view.findViewById(R.id.field); + checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + checkBox.setError(null); + invokeOnFormFieldValuesEdited(); + } + }); + } + + @Override + protected void setLabel(String label, boolean required) { + CheckBox checkBox = (CheckBox) view.findViewById(R.id.field); + checkBox.setText(createSpannableLabelString(label, required)); + } + + @Override + public List<String> getValues() { + List<String> values = new ArrayList<>(); + values.add(Boolean.toString(checkBox.isChecked())); + return values; + } + + @Override + protected void setValues(List<String> values) { + if (values.size() == 0) { + checkBox.setChecked(false); + } else { + checkBox.setChecked(Boolean.parseBoolean(values.get(0))); + } + } + + @Override + public boolean validates() { + if (checkBox.isChecked() || !field.isRequired()) { + return true; + } else { + checkBox.setError(context.getString(R.string.this_field_is_required)); + checkBox.requestFocus(); + return false; + } + } + + @Override + public boolean edited() { + if (field.getValues().size() == 0) { + return checkBox.isChecked(); + } else { + return super.edited(); + } + } + + @Override + protected int getLayoutResource() { + return R.layout.form_boolean; + } + + @Override + void setReadOnly(boolean readOnly) { + checkBox.setEnabled(!readOnly); + } +} diff --git a/src/main/java/eu/siacs/conversations/ui/forms/FormFieldFactory.java b/src/main/java/eu/siacs/conversations/ui/forms/FormFieldFactory.java new file mode 100644 index 00000000..ee306472 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/ui/forms/FormFieldFactory.java @@ -0,0 +1,30 @@ +package eu.siacs.conversations.ui.forms; + +import android.content.Context; + +import java.util.Hashtable; + +import eu.siacs.conversations.xmpp.forms.Field; + + + +public class FormFieldFactory { + + private static final Hashtable<String, Class> typeTable = new Hashtable<>(); + + static { + typeTable.put("text-single", FormTextFieldWrapper.class); + typeTable.put("text-multi", FormTextFieldWrapper.class); + typeTable.put("text-private", FormTextFieldWrapper.class); + typeTable.put("jid-single", FormJidSingleFieldWrapper.class); + typeTable.put("boolean", FormBooleanFieldWrapper.class); + } + + protected static FormFieldWrapper createFromField(Context context, Field field) { + Class clazz = typeTable.get(field.getType()); + if (clazz == null) { + clazz = FormTextFieldWrapper.class; + } + return FormFieldWrapper.createFromField(clazz, context, field); + } +} diff --git a/src/main/java/eu/siacs/conversations/ui/forms/FormFieldWrapper.java b/src/main/java/eu/siacs/conversations/ui/forms/FormFieldWrapper.java new file mode 100644 index 00000000..3a21ade3 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/ui/forms/FormFieldWrapper.java @@ -0,0 +1,95 @@ +package eu.siacs.conversations.ui.forms; + +import android.content.Context; +import android.text.SpannableString; +import android.text.style.ForegroundColorSpan; +import android.text.style.StyleSpan; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; + +import java.util.List; + +import eu.siacs.conversations.Config; +import eu.siacs.conversations.R; +import eu.siacs.conversations.xmpp.forms.Field; + +public abstract class FormFieldWrapper { + + protected final Context context; + protected final Field field; + protected final View view; + protected OnFormFieldValuesEdited onFormFieldValuesEditedListener; + + protected FormFieldWrapper(Context context, Field field) { + this.context = context; + this.field = field; + LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + this.view = inflater.inflate(getLayoutResource(), null); + String label = field.getLabel(); + if (label == null) { + label = field.getFieldName(); + } + setLabel(label, field.isRequired()); + } + + public final void submit() { + this.field.setValues(getValues()); + } + + public final View getView() { + return view; + } + + protected abstract void setLabel(String label, boolean required); + + abstract List<String> getValues(); + + protected abstract void setValues(List<String> values); + + abstract boolean validates(); + + abstract protected int getLayoutResource(); + + abstract void setReadOnly(boolean readOnly); + + protected SpannableString createSpannableLabelString(String label, boolean required) { + SpannableString spannableString = new SpannableString(label + (required ? " *" : "")); + if (required) { + int start = label.length(); + int end = label.length() + 2; + spannableString.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), start, end, 0); + spannableString.setSpan(new ForegroundColorSpan(context.getResources().getColor(R.color.accent)), start, end, 0); + } + return spannableString; + } + + protected void invokeOnFormFieldValuesEdited() { + if (this.onFormFieldValuesEditedListener != null) { + this.onFormFieldValuesEditedListener.onFormFieldValuesEdited(); + } + } + + public boolean edited() { + return !field.getValues().equals(getValues()); + } + + public void setOnFormFieldValuesEditedListener(OnFormFieldValuesEdited listener) { + this.onFormFieldValuesEditedListener = listener; + } + + protected static <F extends FormFieldWrapper> FormFieldWrapper createFromField(Class<F> c, Context context, Field field) { + try { + F fieldWrapper = c.getDeclaredConstructor(Context.class, Field.class).newInstance(context,field); + fieldWrapper.setValues(field.getValues()); + return fieldWrapper; + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + public interface OnFormFieldValuesEdited { + void onFormFieldValuesEdited(); + } +} diff --git a/src/main/java/eu/siacs/conversations/ui/forms/FormJidSingleFieldWrapper.java b/src/main/java/eu/siacs/conversations/ui/forms/FormJidSingleFieldWrapper.java new file mode 100644 index 00000000..553e8f21 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/ui/forms/FormJidSingleFieldWrapper.java @@ -0,0 +1,44 @@ +package eu.siacs.conversations.ui.forms; + +import android.content.Context; +import android.text.InputType; + +import java.util.List; + +import eu.siacs.conversations.R; +import eu.siacs.conversations.xmpp.forms.Field; +import eu.siacs.conversations.xmpp.jid.InvalidJidException; +import eu.siacs.conversations.xmpp.jid.Jid; + +public class FormJidSingleFieldWrapper extends FormTextFieldWrapper { + + protected FormJidSingleFieldWrapper(Context context, Field field) { + super(context, field); + editText.setInputType(InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS); + editText.setHint(R.string.account_settings_example_jabber_id); + } + + @Override + public boolean validates() { + String value = getValue(); + if (!value.isEmpty()) { + try { + Jid.fromString(value); + } catch (InvalidJidException e) { + editText.setError(context.getString(R.string.invalid_jid)); + editText.requestFocus(); + return false; + } + } + return super.validates(); + } + + @Override + protected void setValues(List<String> values) { + StringBuilder builder = new StringBuilder(""); + for(String value : values) { + builder.append(value); + } + editText.setText(builder.toString()); + } +} diff --git a/src/main/java/eu/siacs/conversations/ui/forms/FormTextFieldWrapper.java b/src/main/java/eu/siacs/conversations/ui/forms/FormTextFieldWrapper.java new file mode 100644 index 00000000..b7dac951 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/ui/forms/FormTextFieldWrapper.java @@ -0,0 +1,97 @@ +package eu.siacs.conversations.ui.forms; + +import android.content.Context; +import android.text.Editable; +import android.text.InputType; +import android.text.TextWatcher; +import android.widget.EditText; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.List; + +import eu.siacs.conversations.R; +import eu.siacs.conversations.xmpp.forms.Field; + +public class FormTextFieldWrapper extends FormFieldWrapper { + + protected EditText editText; + + protected FormTextFieldWrapper(Context context, Field field) { + super(context, field); + editText = (EditText) view.findViewById(R.id.field); + editText.setSingleLine(!"text-multi".equals(field.getType())); + if ("text-private".equals(field.getType())) { + editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); + } + editText.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + editText.setError(null); + invokeOnFormFieldValuesEdited(); + } + + @Override + public void afterTextChanged(Editable s) { + } + }); + } + + @Override + protected void setLabel(String label, boolean required) { + TextView textView = (TextView) view.findViewById(R.id.label); + textView.setText(createSpannableLabelString(label, required)); + } + + protected String getValue() { + return editText.getText().toString(); + } + + @Override + public List<String> getValues() { + List<String> values = new ArrayList<>(); + for (String line : getValue().split("\\n")) { + if (line.length() > 0) { + values.add(line); + } + } + return values; + } + + @Override + protected void setValues(List<String> values) { + StringBuilder builder = new StringBuilder(""); + for(int i = 0; i < values.size(); ++i) { + builder.append(values.get(i)); + if (i < values.size() - 1 && "text-multi".equals(field.getType())) { + builder.append("\n"); + } + } + editText.setText(builder.toString()); + } + + @Override + public boolean validates() { + if (getValue().trim().length() > 0 || !field.isRequired()) { + return true; + } else { + editText.setError(context.getString(R.string.this_field_is_required)); + editText.requestFocus(); + return false; + } + } + + @Override + protected int getLayoutResource() { + return R.layout.form_text; + } + + @Override + void setReadOnly(boolean readOnly) { + editText.setEnabled(!readOnly); + } +} diff --git a/src/main/java/eu/siacs/conversations/ui/forms/FormWrapper.java b/src/main/java/eu/siacs/conversations/ui/forms/FormWrapper.java new file mode 100644 index 00000000..eafe95cc --- /dev/null +++ b/src/main/java/eu/siacs/conversations/ui/forms/FormWrapper.java @@ -0,0 +1,72 @@ +package eu.siacs.conversations.ui.forms; + +import android.content.Context; +import android.widget.LinearLayout; + +import java.util.ArrayList; +import java.util.List; + +import eu.siacs.conversations.xmpp.forms.Data; +import eu.siacs.conversations.xmpp.forms.Field; + +public class FormWrapper { + + private final LinearLayout layout; + + private final Data form; + + private final List<FormFieldWrapper> fieldWrappers = new ArrayList<>(); + + private FormWrapper(Context context, LinearLayout linearLayout, Data form) { + this.form = form; + this.layout = linearLayout; + this.layout.removeAllViews(); + for(Field field : form.getFields()) { + FormFieldWrapper fieldWrapper = FormFieldFactory.createFromField(context,field); + if (fieldWrapper != null) { + layout.addView(fieldWrapper.getView()); + fieldWrappers.add(fieldWrapper); + } + } + } + + public Data submit() { + for(FormFieldWrapper fieldWrapper : fieldWrappers) { + fieldWrapper.submit(); + } + this.form.submit(); + return this.form; + } + + public boolean validates() { + boolean validates = true; + for(FormFieldWrapper fieldWrapper : fieldWrappers) { + validates &= fieldWrapper.validates(); + } + return validates; + } + + public void setOnFormFieldValuesEditedListener(FormFieldWrapper.OnFormFieldValuesEdited listener) { + for(FormFieldWrapper fieldWrapper : fieldWrappers) { + fieldWrapper.setOnFormFieldValuesEditedListener(listener); + } + } + + public void setReadOnly(boolean b) { + for(FormFieldWrapper fieldWrapper : fieldWrappers) { + fieldWrapper.setReadOnly(b); + } + } + + public boolean edited() { + boolean edited = false; + for(FormFieldWrapper fieldWrapper : fieldWrappers) { + edited |= fieldWrapper.edited(); + } + return edited; + } + + public static FormWrapper createInLayout(Context context, LinearLayout layout, Data form) { + return new FormWrapper(context, layout, form); + } +} diff --git a/src/main/java/eu/siacs/conversations/ui/widget/Switch.java b/src/main/java/eu/siacs/conversations/ui/widget/Switch.java new file mode 100644 index 00000000..fd3b5553 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/ui/widget/Switch.java @@ -0,0 +1,68 @@ +package eu.siacs.conversations.ui.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.ViewConfiguration; + +import com.kyleduo.switchbutton.SwitchButton; + +public class Switch extends SwitchButton { + + private int mTouchSlop; + private int mClickTimeout; + private float mStartX; + private float mStartY; + private OnClickListener mOnClickListener; + + public Switch(Context context) { + super(context); + mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); + mClickTimeout = ViewConfiguration.getPressedStateDuration() + ViewConfiguration.getTapTimeout(); + } + + public Switch(Context context, AttributeSet attrs) { + super(context, attrs); + mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); + mClickTimeout = ViewConfiguration.getPressedStateDuration() + ViewConfiguration.getTapTimeout(); + } + + public Switch(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); + mClickTimeout = ViewConfiguration.getPressedStateDuration() + ViewConfiguration.getTapTimeout(); + } + + @Override + public void setOnClickListener(OnClickListener onClickListener) { + this.mOnClickListener = onClickListener; + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (!isEnabled()) { + float deltaX = event.getX() - mStartX; + float deltaY = event.getY() - mStartY; + int action = event.getAction(); + switch (action) { + case MotionEvent.ACTION_DOWN: + mStartX = event.getX(); + mStartY = event.getY(); + break; + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + float time = event.getEventTime() - event.getDownTime(); + if (deltaX < mTouchSlop && deltaY < mTouchSlop && time < mClickTimeout) { + if (mOnClickListener != null) { + this.mOnClickListener.onClick(this); + } + } + break; + default: + break; + } + return true; + } + return super.onTouchEvent(event); + } +} diff --git a/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java b/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java index 2dec203d..4850f19d 100644 --- a/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java @@ -1,20 +1,36 @@ package eu.siacs.conversations.utils; +import android.os.Bundle; +import android.util.Pair; + +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x500.style.BCStyle; +import org.bouncycastle.asn1.x500.style.IETFUtils; +import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder; + +import java.security.MessageDigest; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; import java.security.SecureRandom; import java.text.Normalizer; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; +import java.util.Locale; import eu.siacs.conversations.Config; +import eu.siacs.conversations.R; +import eu.siacs.conversations.entities.Message; +import eu.siacs.conversations.xmpp.jid.InvalidJidException; +import eu.siacs.conversations.xmpp.jid.Jid; public final class CryptoHelper { public static final String FILETRANSFER = "?FILETRANSFERv1:"; private final static char[] hexArray = "0123456789abcdef".toCharArray(); - private final static char[] vowels = "aeiou".toCharArray(); - private final static char[] consonants = "bcdfghjklmnpqrstvwxyz".toCharArray(); final public static byte[] ONE = new byte[] { 0, 0, 0, 1 }; public static String bytesToHex(byte[] bytes) { @@ -48,22 +64,6 @@ public final class CryptoHelper { return result; } - public static String randomMucName(SecureRandom random) { - return randomWord(3, random) + "." + randomWord(7, random); - } - - private static String randomWord(int lenght, SecureRandom random) { - StringBuilder builder = new StringBuilder(lenght); - for (int i = 0; i < lenght; ++i) { - if (i % 2 == 0) { - builder.append(consonants[random.nextInt(consonants.length)]); - } else { - builder.append(vowels[random.nextInt(vowels.length)]); - } - } - return builder.toString(); - } - /** * Escapes usernames or passwords for SASL. */ @@ -96,11 +96,18 @@ public final class CryptoHelper { } else if (fingerprint.length() < 40) { return fingerprint; } + StringBuilder builder = new StringBuilder(fingerprint.toLowerCase(Locale.US).replaceAll("\\s", "")); + for(int i=8;i<builder.length();i+=9) { + builder.insert(i, ' '); + } + return builder.toString(); + } + + public static String prettifyFingerprintCert(String fingerprint) { StringBuilder builder = new StringBuilder(fingerprint); - builder.insert(8, " "); - builder.insert(17, " "); - builder.insert(26, " "); - builder.insert(35, " "); + for(int i=2;i < builder.length(); i+=3) { + builder.insert(i,':'); + } return builder.toString(); } @@ -126,4 +133,80 @@ public final class CryptoHelper { } } } + + public static Pair<Jid,String> extractJidAndName(X509Certificate certificate) throws CertificateEncodingException, InvalidJidException, CertificateParsingException { + Collection<List<?>> alternativeNames = certificate.getSubjectAlternativeNames(); + List<String> emails = new ArrayList<>(); + if (alternativeNames != null) { + for(List<?> san : alternativeNames) { + Integer type = (Integer) san.get(0); + if (type == 1) { + emails.add((String) san.get(1)); + } + } + } + X500Name x500name = new JcaX509CertificateHolder(certificate).getSubject(); + if (emails.size() == 0) { + emails.add(IETFUtils.valueToString(x500name.getRDNs(BCStyle.EmailAddress)[0].getFirst().getValue())); + } + String name = IETFUtils.valueToString(x500name.getRDNs(BCStyle.CN)[0].getFirst().getValue()); + if (emails.size() >= 1) { + return new Pair<>(Jid.fromString(emails.get(0)), name); + } else { + return null; + } + } + + public static Bundle extractCertificateInformation(X509Certificate certificate) { + Bundle information = new Bundle(); + try { + JcaX509CertificateHolder holder = new JcaX509CertificateHolder(certificate); + X500Name subject = holder.getSubject(); + try { + information.putString("subject_cn", subject.getRDNs(BCStyle.CN)[0].getFirst().getValue().toString()); + } catch (Exception e) { + //ignored + } + try { + information.putString("subject_o",subject.getRDNs(BCStyle.O)[0].getFirst().getValue().toString()); + } catch (Exception e) { + //ignored + } + + X500Name issuer = holder.getIssuer(); + try { + information.putString("issuer_cn", issuer.getRDNs(BCStyle.CN)[0].getFirst().getValue().toString()); + } catch (Exception e) { + //ignored + } + try { + information.putString("issuer_o", issuer.getRDNs(BCStyle.O)[0].getFirst().getValue().toString()); + } catch (Exception e) { + //ignored + } + try { + MessageDigest md = MessageDigest.getInstance("SHA-1"); + byte[] fingerprint = md.digest(certificate.getEncoded()); + information.putString("sha1", prettifyFingerprintCert(bytesToHex(fingerprint))); + } catch (Exception e) { + + } + return information; + } catch (CertificateEncodingException e) { + return information; + } + } + + public static int encryptionTypeToText(int encryption) { + switch (encryption) { + case Message.ENCRYPTION_OTR: + return R.string.encryption_choice_otr; + case Message.ENCRYPTION_AXOLOTL: + return R.string.encryption_choice_omemo; + case Message.ENCRYPTION_NONE: + return R.string.encryption_choice_unencrypted; + default: + return R.string.encryption_choice_pgp; + } + } } diff --git a/src/main/java/eu/siacs/conversations/utils/DNSHelper.java b/src/main/java/eu/siacs/conversations/utils/DNSHelper.java index 79a8c854..58d53216 100644 --- a/src/main/java/eu/siacs/conversations/utils/DNSHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/DNSHelper.java @@ -1,12 +1,39 @@ package eu.siacs.conversations.utils; +import android.annotation.TargetApi; +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.LinkProperties; +import android.net.Network; +import android.net.RouteInfo; +import android.os.Build; +import android.os.Bundle; +import android.os.Parcelable; +import android.util.Log; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.SocketTimeoutException; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Random; +import java.util.TreeMap; +import java.util.Map; +import java.util.regex.Pattern; + import de.measite.minidns.Client; import de.measite.minidns.DNSMessage; import de.measite.minidns.Record; +import de.measite.minidns.Record.CLASS; import de.measite.minidns.Record.TYPE; import de.measite.minidns.Record.CLASS; import de.measite.minidns.record.SRV; +import de.measite.minidns.record.A; +import de.measite.minidns.record.AAAA; import de.measite.minidns.record.Data; +import de.measite.minidns.record.SRV; import de.measite.minidns.util.NameUtil; import java.io.IOException; @@ -21,83 +48,237 @@ import eu.siacs.conversations.Config; import eu.siacs.conversations.xmpp.jid.Jid; public class DNSHelper { - private static final String CLIENT_SRV_PREFIX = "_xmpp-client._tcp."; - private static final Pattern PATTERN_IPV4 = Pattern.compile("\\A(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z"); - private static final Pattern PATTERN_IPV6_HEX4DECCOMPRESSED = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::((?:[0-9A-Fa-f]{1,4}:)*)(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z"); - private static final Pattern PATTERN_IPV6_6HEX4DEC = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}:){6,6})(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z"); - private static final Pattern PATTERN_IPV6_HEXCOMPRESSED = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)\\z"); - private static final Pattern PATTERN_IPV6 = Pattern.compile("\\A(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\\z"); + + public static final Pattern PATTERN_IPV4 = Pattern.compile("\\A(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z"); + public static final Pattern PATTERN_IPV6_HEX4DECCOMPRESSED = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::((?:[0-9A-Fa-f]{1,4}:)*)(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z"); + public static final Pattern PATTERN_IPV6_6HEX4DEC = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}:){6,6})(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z"); + public static final Pattern PATTERN_IPV6_HEXCOMPRESSED = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)\\z"); + public static final Pattern PATTERN_IPV6 = Pattern.compile("\\A(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\\z"); protected static Client client = new Client(); - /** - * Queries the SRV record for the server JID. - * This method uses all available Domain Name Servers. - * @param jid the server JID - * @return TreeSet with SrvRecords. If no SRV record is found for JID an empty TreeSet is returned. - */ - public static final TreeSet<SrvRecord> querySrvRecord(Jid jid) { - String host = jid.getDomainpart(); - String dns[] = client.findDNS(); - TreeSet<SrvRecord> result = new TreeSet<>(); - - if (dns != null) { - for (String dnsserver : dns) { - result = querySrvRecord(host, dnsserver); - if (!result.isEmpty()) { - break; - } - } - } - - return result; - } - - /** - * Queries the SRV record for an host from the given Domain Name Server. - * @param host the host to query for - * @param dnsserver the DNS to query on - * @return TreeSet with SrvRecords. - */ - private static final TreeSet<SrvRecord> querySrvRecord(String host, String dnsserver) { - TreeSet<SrvRecord> result = new TreeSet<>(); - try { - InetAddress dnsServerAddress = InetAddress.getByName(dnsserver); - String qname = CLIENT_SRV_PREFIX + host; - DNSMessage message = client.query(qname, TYPE.SRV, CLASS.IN, dnsServerAddress.getHostAddress()); - Record[] rrset = message.getAnswers(); - for (Record rr : rrset) { - Data d = rr.getPayload(); - if (d instanceof SRV && NameUtil.idnEquals(qname, rr.getName())) { - SRV srv = (SRV) d; - SrvRecord srvRecord = new SrvRecord(srv.getPriority(), srv.getName(), srv.getPort()); - result.add(srvRecord); - } - } - } catch (IOException e) { - Logging.d("dns", "Error while retrieving SRV record for '" + host + "' from DNS '" + dnsserver + "': " + e.getMessage()); - } - return result; - } - - /** - * Checks whether the given server is an IP address or not. - * The following patterns are treated as valid IP addresses: - * <ul> - * <li>{@link #PATTERN_IPV4}</li> - * <li>{@link #PATTERN_IPV6}</li> - * <li>{@link #PATTERN_IPV6_6HEX4DEC}</li> - * <li>{@link #PATTERN_IPV6_HEX4DECCOMPRESSED}</li> - * <li>{@link #PATTERN_IPV6_HEXCOMPRESSED}</li> - * </ul> - * @param server the string to check - * @return <code>true</code> if one of the patterns is matched <code>false</code> otherwise - */ + public static Bundle getSRVRecord(final Jid jid, Context context) throws IOException { + final String host = jid.getDomainpart(); + final List<InetAddress> servers = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? getDnsServers(context) : getDnsServersPreLollipop(); + Bundle b = new Bundle(); + for(InetAddress server : servers) { + b = queryDNS(host, server); + if (b.containsKey("values")) { + return b; + } + } + if (!b.containsKey("values")) { + Log.d(Config.LOGTAG,"all dns queries failed. provide fallback A record"); + ArrayList<Parcelable> values = new ArrayList<>(); + values.add(createNamePortBundle(host, 5222, false)); + b.putParcelableArrayList("values",values); + } + return b; + } + + @TargetApi(21) + private static List<InetAddress> getDnsServers(Context context) { + List<InetAddress> servers = new ArrayList<>(); + ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + Network[] networks = connectivityManager == null ? null : connectivityManager.getAllNetworks(); + if (networks == null) { + return getDnsServersPreLollipop(); + } + for(int i = 0; i < networks.length; ++i) { + LinkProperties linkProperties = connectivityManager.getLinkProperties(networks[i]); + if (linkProperties != null) { + if (hasDefaultRoute(linkProperties)) { + servers.addAll(0, linkProperties.getDnsServers()); + } else { + servers.addAll(linkProperties.getDnsServers()); + } + } + } + if (servers.size() > 0) { + Log.d(Config.LOGTAG, "used lollipop variant to discover dns servers in " + networks.length + " networks"); + } + return servers.size() > 0 ? servers : getDnsServersPreLollipop(); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + private static boolean hasDefaultRoute(LinkProperties linkProperties) { + for(RouteInfo route: linkProperties.getRoutes()) { + if (route.isDefaultRoute()) { + return true; + } + } + return false; + } + + private static List<InetAddress> getDnsServersPreLollipop() { + List<InetAddress> servers = new ArrayList<>(); + String[] dns = client.findDNS(); + for(int i = 0; i < dns.length; ++i) { + try { + servers.add(InetAddress.getByName(dns[i])); + } catch (UnknownHostException e) { + //ignore + } + } + return servers; + } + + private static class TlsSrv { + private final SRV srv; + private final boolean tls; + + public TlsSrv(SRV srv, boolean tls) { + this.srv = srv; + this.tls = tls; + } + } + + private static void fillSrvMaps(final String qname, final InetAddress dnsServer, final Map<Integer, List<TlsSrv>> priorities, final Map<String, List<String>> ips4, final Map<String, List<String>> ips6, final boolean tls) throws IOException { + final DNSMessage message = client.query(qname, TYPE.SRV, CLASS.IN, dnsServer.getHostAddress()); + for (Record[] rrset : new Record[][] { message.getAnswers(), message.getAdditionalResourceRecords() }) { + for (Record rr : rrset) { + Data d = rr.getPayload(); + if (d instanceof SRV && NameUtil.idnEquals(qname, rr.getName())) { + SRV srv = (SRV) d; + if (!priorities.containsKey(srv.getPriority())) { + priorities.put(srv.getPriority(),new ArrayList<TlsSrv>()); + } + priorities.get(srv.getPriority()).add(new TlsSrv(srv, tls)); + } + if (d instanceof A) { + A a = (A) d; + if (!ips4.containsKey(rr.getName())) { + ips4.put(rr.getName(), new ArrayList<String>()); + } + ips4.get(rr.getName()).add(a.toString()); + } + if (d instanceof AAAA) { + AAAA aaaa = (AAAA) d; + if (!ips6.containsKey(rr.getName())) { + ips6.put(rr.getName(), new ArrayList<String>()); + } + ips6.get(rr.getName()).add("[" + aaaa.toString() + "]"); + } + } + } + } + + public static Bundle queryDNS(String host, InetAddress dnsServer) { + Bundle bundle = new Bundle(); + try { + client.setTimeout(Config.PING_TIMEOUT * 1000); + final String qname = "_xmpp-client._tcp." + host; + final String tlsQname = "_xmpps-client._tcp." + host; + Log.d(Config.LOGTAG, "using dns server: " + dnsServer.getHostAddress() + " to look up " + host); + + final Map<Integer, List<TlsSrv>> priorities = new TreeMap<>(); + final Map<String, List<String>> ips4 = new TreeMap<>(); + final Map<String, List<String>> ips6 = new TreeMap<>(); + + fillSrvMaps(qname, dnsServer, priorities, ips4, ips6, false); + fillSrvMaps(tlsQname, dnsServer, priorities, ips4, ips6, true); + + final List<TlsSrv> result = new ArrayList<>(); + for (final List<TlsSrv> s : priorities.values()) { + result.addAll(s); + } + + final ArrayList<Bundle> values = new ArrayList<>(); + if (result.size() == 0) { + DNSMessage response; + try { + response = client.query(host, TYPE.A, CLASS.IN, dnsServer.getHostAddress()); + for (int i = 0; i < response.getAnswers().length; ++i) { + values.add(createNamePortBundle(host, 5222, response.getAnswers()[i].getPayload(), false)); + } + } catch (SocketTimeoutException e) { + Log.d(Config.LOGTAG,"ignoring timeout exception when querying A record on "+dnsServer.getHostAddress()); + } + try { + response = client.query(host, TYPE.AAAA, CLASS.IN, dnsServer.getHostAddress()); + for (int i = 0; i < response.getAnswers().length; ++i) { + values.add(createNamePortBundle(host, 5222, response.getAnswers()[i].getPayload(), false)); + } + } catch (SocketTimeoutException e) { + Log.d(Config.LOGTAG,"ignoring timeout exception when querying AAAA record on "+dnsServer.getHostAddress()); + } + values.add(createNamePortBundle(host, 5222, false)); + bundle.putParcelableArrayList("values", values); + return bundle; + } + for (final TlsSrv tlsSrv : result) { + final SRV srv = tlsSrv.srv; + if (ips6.containsKey(srv.getName())) { + values.add(createNamePortBundle(srv.getName(),srv.getPort(),ips6, tlsSrv.tls)); + } else { + try { + DNSMessage response = client.query(srv.getName(), TYPE.AAAA, CLASS.IN, dnsServer.getHostAddress()); + for (int i = 0; i < response.getAnswers().length; ++i) { + values.add(createNamePortBundle(srv.getName(), srv.getPort(), response.getAnswers()[i].getPayload(), tlsSrv.tls)); + } + } catch (SocketTimeoutException e) { + Log.d(Config.LOGTAG,"ignoring timeout exception when querying AAAA record on "+dnsServer.getHostAddress()); + } + } + if (ips4.containsKey(srv.getName())) { + values.add(createNamePortBundle(srv.getName(),srv.getPort(),ips4, tlsSrv.tls)); + } else { + DNSMessage response = client.query(srv.getName(), TYPE.A, CLASS.IN, dnsServer.getHostAddress()); + for(int i = 0; i < response.getAnswers().length; ++i) { + values.add(createNamePortBundle(srv.getName(),srv.getPort(),response.getAnswers()[i].getPayload(), tlsSrv.tls)); + } + } + values.add(createNamePortBundle(srv.getName(), srv.getPort(), tlsSrv.tls)); + } + bundle.putParcelableArrayList("values", values); + } catch (SocketTimeoutException e) { + bundle.putString("error", "timeout"); + } catch (Exception e) { + bundle.putString("error", "unhandled"); + } + return bundle; + } + + private static Bundle createNamePortBundle(String name, int port, final boolean tls) { + Bundle namePort = new Bundle(); + namePort.putString("name", name); + namePort.putBoolean("tls", tls); + namePort.putInt("port", port); + return namePort; + } + + private static Bundle createNamePortBundle(String name, int port, Map<String, List<String>> ips, final boolean tls) { + Bundle namePort = new Bundle(); + namePort.putString("name", name); + namePort.putBoolean("tls", tls); + namePort.putInt("port", port); + if (ips!=null) { + List<String> ip = ips.get(name); + Collections.shuffle(ip, new Random()); + namePort.putString("ip", ip.get(0)); + } + return namePort; + } + + private static Bundle createNamePortBundle(String name, int port, Data data, final boolean tls) { + Bundle namePort = new Bundle(); + namePort.putString("name", name); + namePort.putBoolean("tls", tls); + namePort.putInt("port", port); + if (data instanceof A) { + namePort.putString("ip", data.toString()); + } else if (data instanceof AAAA) { + namePort.putString("ip","["+data.toString()+"]"); + } + return namePort; + } + public static boolean isIp(final String server) { - return PATTERN_IPV4.matcher(server).matches() + return server != null && ( + PATTERN_IPV4.matcher(server).matches() || PATTERN_IPV6.matcher(server).matches() || PATTERN_IPV6_6HEX4DEC.matcher(server).matches() || PATTERN_IPV6_HEX4DECCOMPRESSED.matcher(server).matches() - || PATTERN_IPV6_HEXCOMPRESSED.matcher(server).matches(); + || PATTERN_IPV6_HEXCOMPRESSED.matcher(server).matches()); } } diff --git a/src/main/java/eu/siacs/conversations/utils/ExceptionHandler.java b/src/main/java/eu/siacs/conversations/utils/ExceptionHandler.java index 0ad57fe2..4e3ec236 100644 --- a/src/main/java/eu/siacs/conversations/utils/ExceptionHandler.java +++ b/src/main/java/eu/siacs/conversations/utils/ExceptionHandler.java @@ -1,5 +1,7 @@ package eu.siacs.conversations.utils; +import android.content.Context; + import java.io.FileNotFoundException; import java.io.IOException; import java.io.OutputStream; @@ -8,8 +10,6 @@ import java.io.StringWriter; import java.io.Writer; import java.lang.Thread.UncaughtExceptionHandler; -import android.content.Context; - public class ExceptionHandler implements UncaughtExceptionHandler { private UncaughtExceptionHandler defaultHandler; diff --git a/src/main/java/eu/siacs/conversations/utils/ExceptionHelper.java b/src/main/java/eu/siacs/conversations/utils/ExceptionHelper.java index b0a0455d..892d5a00 100644 --- a/src/main/java/eu/siacs/conversations/utils/ExceptionHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/ExceptionHelper.java @@ -1,5 +1,17 @@ package eu.siacs.conversations.utils; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.content.SharedPreferences; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.preference.PreferenceManager; +import android.text.format.DateUtils; +import android.util.Log; + import java.io.BufferedReader; import java.io.FileInputStream; import java.io.IOException; @@ -15,6 +27,7 @@ import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.services.XmppConnectionService; +import eu.siacs.conversations.ui.ConversationActivity; import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; @@ -37,12 +50,11 @@ public class ExceptionHelper { } } - public static void checkForCrash(final Context context, - final XmppConnectionService service) { + public static boolean checkForCrash(ConversationActivity activity, final XmppConnectionService service) { try { boolean neverSend = ConversationsPlusPreferences.neverSend(); if (neverSend) { - return; + return false; } List<Account> accounts = service.getAccounts(); Account account = null; @@ -53,24 +65,25 @@ public class ExceptionHelper { } } if (account == null) { - return; + return false; } final Account finalAccount = account; - FileInputStream file = context.openFileInput("stacktrace.txt"); + FileInputStream file = activity.openFileInput("stacktrace.txt"); InputStreamReader inputStreamReader = new InputStreamReader(file); BufferedReader stacktrace = new BufferedReader(inputStreamReader); final StringBuilder report = new StringBuilder(); - PackageManager pm = context.getPackageManager(); + PackageManager pm = activity.getPackageManager(); PackageInfo packageInfo = null; try { - packageInfo = pm.getPackageInfo(context.getPackageName(), 0); + packageInfo = pm.getPackageInfo(activity.getPackageName(), 0); report.append("Version: " + packageInfo.versionName + '\n'); report.append("Last Update: " - + DateUtils.formatDateTime(context, - packageInfo.lastUpdateTime, - DateUtils.FORMAT_SHOW_TIME - | DateUtils.FORMAT_SHOW_DATE) + '\n'); + + DateUtils.formatDateTime(activity, + packageInfo.lastUpdateTime, + DateUtils.FORMAT_SHOW_TIME + | DateUtils.FORMAT_SHOW_DATE) + '\n'); } catch (NameNotFoundException e) { + return false; } String line; while ((line = stacktrace.readLine()) != null) { @@ -78,11 +91,11 @@ public class ExceptionHelper { report.append('\n'); } file.close(); - context.deleteFile("stacktrace.txt"); - AlertDialog.Builder builder = new AlertDialog.Builder(context); - builder.setTitle(context.getString(R.string.crash_report_title)); - builder.setMessage(context.getText(R.string.crash_report_message)); - builder.setPositiveButton(context.getText(R.string.send_now), + activity.deleteFile("stacktrace.txt"); + AlertDialog.Builder builder = new AlertDialog.Builder(activity); + builder.setTitle(activity.getString(R.string.crash_report_title)); + builder.setMessage(activity.getText(R.string.crash_report_message)); + builder.setPositiveButton(activity.getText(R.string.send_now), new OnClickListener() { @Override @@ -91,18 +104,18 @@ public class ExceptionHelper { Logging.d(Config.LOGTAG, "using account=" + finalAccount.getJid().toBareJid() + " to send in stack trace"); - Conversation conversation = null; - try { - conversation = service.findOrCreateConversation(finalAccount, - Jid.fromString(context.getString(R.string.cplus_bugreport_jabberid)), false); - } catch (final InvalidJidException ignored) { - } - Message message = new Message(conversation, report + Conversation conversation = null; + try { + conversation = service.findOrCreateConversation(finalAccount, + Jid.fromString("bugs@siacs.eu"), false); + } catch (final InvalidJidException ignored) { + } + Message message = new Message(conversation, report .toString(), Message.ENCRYPTION_NONE); service.sendMessage(message); } }); - builder.setNegativeButton(context.getText(R.string.send_never), + builder.setNegativeButton(activity.getText(R.string.send_never), new OnClickListener() { @Override @@ -111,8 +124,9 @@ public class ExceptionHelper { } }); builder.create().show(); + return true; } catch (final IOException ignored) { - } - + return false; + } } } diff --git a/src/main/java/eu/siacs/conversations/utils/FileUtils.java b/src/main/java/eu/siacs/conversations/utils/FileUtils.java new file mode 100644 index 00000000..ad8b8640 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/utils/FileUtils.java @@ -0,0 +1,149 @@ +package eu.siacs.conversations.utils; + +import android.annotation.SuppressLint; +import android.content.ContentUris; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.os.Build; +import android.os.Environment; +import android.provider.DocumentsContract; +import android.provider.MediaStore; + +public class FileUtils { + + /** + * Get a file path from a Uri. This will get the the path for Storage Access + * Framework Documents, as well as the _data field for the MediaStore and + * other file-based ContentProviders. + * + * @param context The context. + * @param uri The Uri to query. + * @author paulburke + */ + @SuppressLint("NewApi") + public static String getPath(final Context context, final Uri uri) { + if (uri == null) { + return null; + } + + final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; + + // DocumentProvider + if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) { + // ExternalStorageProvider + if (isExternalStorageDocument(uri)) { + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + + if ("primary".equalsIgnoreCase(type)) { + return Environment.getExternalStorageDirectory() + "/" + split[1]; + } + + // TODO handle non-primary volumes + } + // DownloadsProvider + else if (isDownloadsDocument(uri)) { + + final String id = DocumentsContract.getDocumentId(uri); + final Uri contentUri = ContentUris.withAppendedId( + Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); + + return getDataColumn(context, contentUri, null, null); + } + // MediaProvider + else if (isMediaDocument(uri)) { + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + + Uri contentUri = null; + if ("image".equals(type)) { + contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; + } else if ("video".equals(type)) { + contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; + } else if ("audio".equals(type)) { + contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; + } + + final String selection = "_id=?"; + final String[] selectionArgs = new String[]{ + split[1] + }; + + return getDataColumn(context, contentUri, selection, selectionArgs); + } + } + // MediaStore (and general) + else if ("content".equalsIgnoreCase(uri.getScheme())) { + return getDataColumn(context, uri, null, null); + } + // File + else if ("file".equalsIgnoreCase(uri.getScheme())) { + return uri.getPath(); + } + + return null; + } + + /** + * Get the value of the data column for this Uri. This is useful for + * MediaStore Uris, and other file-based ContentProviders. + * + * @param context The context. + * @param uri The Uri to query. + * @param selection (Optional) Filter used in the query. + * @param selectionArgs (Optional) Selection arguments used in the query. + * @return The value of the _data column, which is typically a file path. + */ + public static String getDataColumn(Context context, Uri uri, String selection, + String[] selectionArgs) { + + Cursor cursor = null; + final String column = "_data"; + final String[] projection = { + column + }; + + try { + cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,null); + if (cursor != null && cursor.moveToFirst()) { + final int column_index = cursor.getColumnIndexOrThrow(column); + return cursor.getString(column_index); + } + } catch(Exception e) { + return null; + } finally { + if (cursor != null) { + cursor.close(); + } + } + return null; + } + + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is ExternalStorageProvider. + */ + public static boolean isExternalStorageDocument(Uri uri) { + return "com.android.externalstorage.documents".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is DownloadsProvider. + */ + public static boolean isDownloadsDocument(Uri uri) { + return "com.android.providers.downloads.documents".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is MediaProvider. + */ + public static boolean isMediaDocument(Uri uri) { + return "com.android.providers.media.documents".equals(uri.getAuthority()); + } +} diff --git a/src/main/java/eu/siacs/conversations/utils/MimeUtils.java b/src/main/java/eu/siacs/conversations/utils/MimeUtils.java index a9e89d1b..d4544424 100644 --- a/src/main/java/eu/siacs/conversations/utils/MimeUtils.java +++ b/src/main/java/eu/siacs/conversations/utils/MimeUtils.java @@ -458,7 +458,7 @@ public final class MimeUtils { if (extension == null || extension.isEmpty()) { return null; } - return extensionToMimeTypeMap.get(extension); + return extensionToMimeTypeMap.get(extension.toLowerCase()); } /** * Returns true if the given extension has a registered MIME type. diff --git a/src/main/java/eu/siacs/conversations/utils/OnPhoneContactsLoadedListener.java b/src/main/java/eu/siacs/conversations/utils/OnPhoneContactsLoadedListener.java index 9a689768..f18a4ed8 100644 --- a/src/main/java/eu/siacs/conversations/utils/OnPhoneContactsLoadedListener.java +++ b/src/main/java/eu/siacs/conversations/utils/OnPhoneContactsLoadedListener.java @@ -1,9 +1,9 @@ package eu.siacs.conversations.utils; -import java.util.List; - import android.os.Bundle; +import java.util.List; + public interface OnPhoneContactsLoadedListener { public void onPhoneContactsLoaded(List<Bundle> phoneContacts); } diff --git a/src/main/java/eu/siacs/conversations/utils/PhoneHelper.java b/src/main/java/eu/siacs/conversations/utils/PhoneHelper.java index e0556af3..774532d1 100644 --- a/src/main/java/eu/siacs/conversations/utils/PhoneHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/PhoneHelper.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.RejectedExecutionException; +import android.Manifest; import android.content.Context; import android.content.CursorLoader; import android.content.Loader; @@ -19,12 +20,17 @@ import de.thedevstack.conversationsplus.ConversationsPlusApplication; public class PhoneHelper { - public static void loadPhoneContacts(Context context,final List<Bundle> phoneContacts, final OnPhoneContactsLoadedListener listener) { - final String[] PROJECTION = new String[] { ContactsContract.Data._ID, + public static void loadPhoneContacts(Context context, final List<Bundle> phoneContacts, final OnPhoneContactsLoadedListener listener) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M + && context.checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) { + listener.onPhoneContactsLoaded(phoneContacts); + return; + } + final String[] PROJECTION = new String[]{ContactsContract.Data._ID, ContactsContract.Data.DISPLAY_NAME, ContactsContract.Data.PHOTO_URI, ContactsContract.Data.LOOKUP_KEY, - ContactsContract.CommonDataKinds.Im.DATA }; + ContactsContract.CommonDataKinds.Im.DATA}; final String SELECTION = "(" + ContactsContract.Data.MIMETYPE + "=\"" + ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE @@ -32,39 +38,39 @@ public class PhoneHelper { + "=\"" + ContactsContract.CommonDataKinds.Im.PROTOCOL_JABBER + "\")"; - CursorLoader mCursorLoader = new CursorLoader(context, + CursorLoader mCursorLoader = new NotThrowCursorLoader(context, ContactsContract.Data.CONTENT_URI, PROJECTION, SELECTION, null, null); mCursorLoader.registerListener(0, new OnLoadCompleteListener<Cursor>() { @Override public void onLoadComplete(Loader<Cursor> arg0, Cursor cursor) { - if (cursor == null) { - return; - } - while (cursor.moveToNext()) { - Bundle contact = new Bundle(); - contact.putInt("phoneid", cursor.getInt(cursor - .getColumnIndex(ContactsContract.Data._ID))); - contact.putString( - "displayname", - cursor.getString(cursor - .getColumnIndex(ContactsContract.Data.DISPLAY_NAME))); - contact.putString("photouri", cursor.getString(cursor - .getColumnIndex(ContactsContract.Data.PHOTO_URI))); - contact.putString("lookup", cursor.getString(cursor - .getColumnIndex(ContactsContract.Data.LOOKUP_KEY))); - - contact.putString( - "jid", - cursor.getString(cursor - .getColumnIndex(ContactsContract.CommonDataKinds.Im.DATA))); - phoneContacts.add(contact); + if (cursor != null) { + while (cursor.moveToNext()) { + Bundle contact = new Bundle(); + contact.putInt("phoneid", cursor.getInt(cursor + .getColumnIndex(ContactsContract.Data._ID))); + contact.putString( + "displayname", + cursor.getString(cursor + .getColumnIndex(ContactsContract.Data.DISPLAY_NAME))); + contact.putString("photouri", cursor.getString(cursor + .getColumnIndex(ContactsContract.Data.PHOTO_URI))); + contact.putString("lookup", cursor.getString(cursor + .getColumnIndex(ContactsContract.Data.LOOKUP_KEY))); + + contact.putString( + "jid", + cursor.getString(cursor + .getColumnIndex(ContactsContract.CommonDataKinds.Im.DATA))); + phoneContacts.add(contact); + } + cursor.close(); } + if (listener != null) { listener.onPhoneContactsLoaded(phoneContacts); } - cursor.close(); } }); try { @@ -76,8 +82,30 @@ public class PhoneHelper { } } + private static class NotThrowCursorLoader extends CursorLoader { + + public NotThrowCursorLoader(Context c, Uri u, String[] p, String s, String[] sa, String so) { + super(c, u, p, s, sa, so); + } + + @Override + public Cursor loadInBackground() { + + try { + return (super.loadInBackground()); + } catch (SecurityException e) { + return(null); + } + } + + } + public static Uri getSefliUri(Context context) { - String[] mProjection = new String[] { Profile._ID, Profile.PHOTO_URI }; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M + && context.checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) { + return null; + } + String[] mProjection = new String[]{Profile._ID, Profile.PHOTO_URI}; Cursor mProfileCursor = context.getContentResolver().query( Profile.CONTENT_URI, mProjection, null, null, null); @@ -94,4 +122,17 @@ public class PhoneHelper { } } } + + public static String getVersionName(Context context) { + final String packageName = context == null ? null : context.getPackageName(); + if (packageName != null) { + try { + return context.getPackageManager().getPackageInfo(packageName, 0).versionName; + } catch (final PackageManager.NameNotFoundException | RuntimeException e) { + return "unknown"; + } + } else { + return "unknown"; + } + } } diff --git a/src/main/java/eu/siacs/conversations/utils/SSLSocketHelper.java b/src/main/java/eu/siacs/conversations/utils/SSLSocketHelper.java new file mode 100644 index 00000000..49e9a81a --- /dev/null +++ b/src/main/java/eu/siacs/conversations/utils/SSLSocketHelper.java @@ -0,0 +1,62 @@ +package eu.siacs.conversations.utils; + +import java.lang.reflect.Method; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedList; + +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; + +public class SSLSocketHelper { + + public static void setSecurity(final SSLSocket sslSocket) throws NoSuchAlgorithmException { + final String[] supportProtocols; + final Collection<String> supportedProtocols = new LinkedList<>( + Arrays.asList(sslSocket.getSupportedProtocols())); + supportedProtocols.remove("SSLv3"); + supportProtocols = supportedProtocols.toArray(new String[supportedProtocols.size()]); + + sslSocket.setEnabledProtocols(supportProtocols); + + final String[] cipherSuites = CryptoHelper.getOrderedCipherSuites( + sslSocket.getSupportedCipherSuites()); + if (cipherSuites.length > 0) { + sslSocket.setEnabledCipherSuites(cipherSuites); + } + } + + public static void setSNIHost(final SSLSocketFactory factory, final SSLSocket socket, final String hostname) { + if (factory instanceof android.net.SSLCertificateSocketFactory && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) { + ((android.net.SSLCertificateSocketFactory) factory).setHostname(socket, hostname); + } else { + try { + socket.getClass().getMethod("setHostname", String.class).invoke(socket, hostname); + } catch (Throwable e) { + // ignore any error, we just can't set the hostname... + } + } + } + + public static void setAlpnProtocol(final SSLSocketFactory factory, final SSLSocket socket, final String protocol) { + try { + if (factory instanceof android.net.SSLCertificateSocketFactory && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { + // can't call directly because of @hide? + //((android.net.SSLCertificateSocketFactory)factory).setAlpnProtocols(new byte[][]{protocol.getBytes("UTF-8")}); + android.net.SSLCertificateSocketFactory.class.getMethod("setAlpnProtocols", byte[][].class).invoke(socket, new Object[]{new byte[][]{protocol.getBytes("UTF-8")}}); + } else { + final Method method = socket.getClass().getMethod("setAlpnProtocols", byte[].class); + // the concatenation of 8-bit, length prefixed protocol names, just one in our case... + // http://tools.ietf.org/html/draft-agl-tls-nextprotoneg-04#page-4 + final byte[] protocolUTF8Bytes = protocol.getBytes("UTF-8"); + final byte[] lengthPrefixedProtocols = new byte[protocolUTF8Bytes.length + 1]; + lengthPrefixedProtocols[0] = (byte) protocol.length(); // cannot be over 255 anyhow + System.arraycopy(protocolUTF8Bytes, 0, lengthPrefixedProtocols, 1, protocolUTF8Bytes.length); + method.invoke(socket, new Object[]{lengthPrefixedProtocols}); + } + } catch (Throwable e) { + // ignore any error, we just can't set the alpn protocol... + } + } +} diff --git a/src/main/java/eu/siacs/conversations/utils/SocksSocketFactory.java b/src/main/java/eu/siacs/conversations/utils/SocksSocketFactory.java new file mode 100644 index 00000000..768e9f17 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/utils/SocksSocketFactory.java @@ -0,0 +1,57 @@ +package eu.siacs.conversations.utils; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.nio.ByteBuffer; + +import eu.siacs.conversations.Config; + +public class SocksSocketFactory { + + public static void createSocksConnection(Socket socket, String destination, int port) throws IOException { + InputStream proxyIs = socket.getInputStream(); + OutputStream proxyOs = socket.getOutputStream(); + proxyOs.write(new byte[]{0x05, 0x01, 0x00}); + byte[] response = new byte[2]; + proxyIs.read(response); + byte[] dest = destination.getBytes(); + ByteBuffer request = ByteBuffer.allocate(7 + dest.length); + request.put(new byte[]{0x05, 0x01, 0x00, 0x03}); + request.put((byte) dest.length); + request.put(dest); + request.putShort((short) port); + proxyOs.write(request.array()); + response = new byte[7 + dest.length]; + proxyIs.read(response); + if (response[1] != 0x00) { + throw new SocksConnectionException(); + } + } + + public static Socket createSocket(InetSocketAddress address, String destination, int port) throws IOException { + Socket socket = new Socket(); + try { + socket.connect(address, Config.CONNECT_TIMEOUT * 1000); + } catch (IOException e) { + throw new SocksProxyNotFoundException(); + } + createSocksConnection(socket, destination, port); + return socket; + } + + public static Socket createSocketOverTor(String destination, int port) throws IOException { + return createSocket(new InetSocketAddress(InetAddress.getLocalHost(), 9050), destination, port); + } + + static class SocksConnectionException extends IOException { + + } + + public static class SocksProxyNotFoundException extends IOException { + + } +} diff --git a/src/main/java/eu/siacs/conversations/utils/UIHelper.java b/src/main/java/eu/siacs/conversations/utils/UIHelper.java index 7562c446..e1b1a2a9 100644 --- a/src/main/java/eu/siacs/conversations/utils/UIHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/UIHelper.java @@ -1,5 +1,10 @@ package eu.siacs.conversations.utils; +import android.content.Context; +import android.text.format.DateFormat; +import android.text.format.DateUtils; +import android.util.Pair; + import java.math.BigDecimal; import java.text.NumberFormat; import java.util.ArrayList; @@ -22,6 +27,7 @@ import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Presences; import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.entities.Message; +import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.xmpp.jid.Jid; import android.content.Context; @@ -145,7 +151,7 @@ public class UIHelper { } public static int getColorForName(String name) { - if (name.isEmpty()) { + if (name == null || name.isEmpty()) { return 0xFF202020; } int colors[] = {0xFFe91e63, 0xFF9c27b0, 0xFF673ab7, 0xFF3f51b5, diff --git a/src/main/java/eu/siacs/conversations/utils/Xmlns.java b/src/main/java/eu/siacs/conversations/utils/Xmlns.java index de0a29ce..a19ec791 100644 --- a/src/main/java/eu/siacs/conversations/utils/Xmlns.java +++ b/src/main/java/eu/siacs/conversations/utils/Xmlns.java @@ -1,9 +1,11 @@ package eu.siacs.conversations.utils; +import eu.siacs.conversations.Config; + public final class Xmlns { public static final String BLOCKING = "urn:xmpp:blocking"; public static final String ROSTER = "jabber:iq:roster"; public static final String REGISTER = "jabber:iq:register"; public static final String BYTE_STREAMS = "http://jabber.org/protocol/bytestreams"; - public static final String HTTP_UPLOAD = "urn:xmpp:http:upload"; + public static final String HTTP_UPLOAD = Config.LEGACY_NAMESPACE_HTTP_UPLOAD ? "eu:siacs:conversations:http:upload" : "urn:xmpp:http:upload"; } diff --git a/src/main/java/eu/siacs/conversations/xml/Element.java b/src/main/java/eu/siacs/conversations/xml/Element.java index 1c60d321..90eb112d 100644 --- a/src/main/java/eu/siacs/conversations/xml/Element.java +++ b/src/main/java/eu/siacs/conversations/xml/Element.java @@ -12,15 +12,20 @@ import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; public class Element { - protected String name; - protected Hashtable<String, String> attributes = new Hashtable<>(); - protected String content; + private final String name; + private Hashtable<String, String> attributes = new Hashtable<>(); + private String content; protected List<Element> children = new ArrayList<>(); public Element(String name) { this.name = name; } + public Element(String name, String xmlns) { + this.name = name; + this.setAttribute("xmlns", xmlns); + } + public Element addChild(Element child) { this.content = null; children.add(child); @@ -64,10 +69,9 @@ public class Element { public Element findChild(String name, String xmlns) { for (Element child : this.children) { - if (child.getName().equals(name) - && (child.getAttribute("xmlns").equals(xmlns))) { + if (name.equals(child.getName()) && xmlns.equals(child.getAttribute("xmlns"))) { return child; - } + } } return null; } @@ -94,7 +98,7 @@ public class Element { return this; } - public String getContent() { + public final String getContent() { return content; } @@ -158,7 +162,7 @@ public class Element { return elementOutput.toString(); } - public String getName() { + public final String getName() { return name; } @@ -178,4 +182,8 @@ public class Element { String attr = getAttribute(name); return (attr != null && (attr.equalsIgnoreCase("true") || attr.equalsIgnoreCase("1"))); } + + public String getNamespace() { + return getAttribute("xmlns"); + } } diff --git a/src/main/java/eu/siacs/conversations/xml/XmlReader.java b/src/main/java/eu/siacs/conversations/xml/XmlReader.java index 5375d60d..a58dc43f 100644 --- a/src/main/java/eu/siacs/conversations/xml/XmlReader.java +++ b/src/main/java/eu/siacs/conversations/xml/XmlReader.java @@ -2,8 +2,12 @@ package eu.siacs.conversations.xml; import android.os.PowerManager; import android.os.PowerManager.WakeLock; +import android.util.Log; import android.util.Xml; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; diff --git a/src/main/java/eu/siacs/conversations/xmpp/OnKeyStatusUpdated.java b/src/main/java/eu/siacs/conversations/xmpp/OnKeyStatusUpdated.java new file mode 100644 index 00000000..e7fc582e --- /dev/null +++ b/src/main/java/eu/siacs/conversations/xmpp/OnKeyStatusUpdated.java @@ -0,0 +1,7 @@ +package eu.siacs.conversations.xmpp; + +import eu.siacs.conversations.crypto.axolotl.AxolotlService; + +public interface OnKeyStatusUpdated { + public void onKeyStatusUpdated(AxolotlService.FetchStatus report); +} diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index 284787a0..ab647a15 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -1,9 +1,16 @@ package eu.siacs.conversations.xmpp; import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.os.Bundle; +import android.os.Parcelable; import android.os.PowerManager; import android.os.PowerManager.WakeLock; import android.os.SystemClock; +import android.security.KeyChain; +import android.util.Base64; +import android.util.Log; import android.util.Pair; import android.util.SparseArray; @@ -12,22 +19,29 @@ import org.json.JSONException; import org.json.JSONObject; import org.xmlpull.v1.XmlPullParserException; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.math.BigInteger; import java.net.ConnectException; +import java.net.IDN; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; import java.net.UnknownHostException; +import java.net.URL; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; +import java.security.Principal; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Hashtable; +import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -35,33 +49,44 @@ import java.util.Map.Entry; import java.util.TreeSet; import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.KeyManager; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.X509KeyManager; import javax.net.ssl.X509TrustManager; import de.thedevstack.android.logcat.Logging; import de.thedevstack.conversationsplus.dto.SrvRecord; +import de.duenndns.ssl.MemorizingTrustManager; import eu.siacs.conversations.Config; +import eu.siacs.conversations.crypto.XmppDomainVerifier; import eu.siacs.conversations.crypto.sasl.DigestMd5; +import eu.siacs.conversations.crypto.sasl.External; import eu.siacs.conversations.crypto.sasl.Plain; import eu.siacs.conversations.crypto.sasl.SaslMechanism; import eu.siacs.conversations.crypto.sasl.ScramSha1; import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.generator.IqGenerator; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.DNSHelper; +import eu.siacs.conversations.utils.SSLSocketHelper; +import eu.siacs.conversations.utils.SocksSocketFactory; import eu.siacs.conversations.utils.Xmlns; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Tag; import eu.siacs.conversations.xml.TagWriter; import eu.siacs.conversations.xml.XmlReader; +import eu.siacs.conversations.xmpp.forms.Data; +import eu.siacs.conversations.xmpp.forms.Field; import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; import eu.siacs.conversations.xmpp.jingle.OnJinglePacketReceived; import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket; +import eu.siacs.conversations.xmpp.stanzas.AbstractAcknowledgeableStanza; import eu.siacs.conversations.xmpp.stanzas.AbstractStanza; import eu.siacs.conversations.xmpp.stanzas.IqPacket; import eu.siacs.conversations.xmpp.stanzas.MessagePacket; @@ -84,14 +109,14 @@ public class XmppConnection implements Runnable { private XmlReader tagReader; private TagWriter tagWriter; private final Features features = new Features(this); - private boolean shouldBind = true; + private boolean needsBinding = true; private boolean shouldAuthenticate = true; private Element streamFeatures; private final HashMap<Jid, Info> disco = new HashMap<>(); private String streamId = null; private int smVersion = 3; - private final SparseArray<String> messageReceipts = new SparseArray<>(); + private final SparseArray<AbstractAcknowledgeableStanza> mStanzaQueue = new SparseArray<>(); private int stanzasReceived = 0; private int stanzasSent = 0; @@ -99,8 +124,12 @@ public class XmppConnection implements Runnable { private long lastPingSent = 0; private long lastConnect = 0; private long lastSessionStarted = 0; + private long lastDiscoStarted = 0; + private int mPendingServiceDiscoveries = 0; + private final ArrayList<String> mPendingServiceDiscoveriesIds = new ArrayList<>(); + private boolean mInteractive = false; private int attempt = 0; - private final Map<String, Pair<IqPacket, OnIqPacketReceived>> packetCallbacks = new Hashtable<>(); + private final Hashtable<String, Pair<IqPacket, OnIqPacketReceived>> packetCallbacks = new Hashtable<>(); private OnPresencePacketReceived presenceListener = null; private OnJinglePacketReceived jingleListener = null; private OnIqPacketReceived unregisteredIqListener = null; @@ -113,6 +142,68 @@ public class XmppConnection implements Runnable { private SaslMechanism saslMechanism; + private X509KeyManager mKeyManager = new X509KeyManager() { + @Override + public String chooseClientAlias(String[] strings, Principal[] principals, Socket socket) { + return account.getPrivateKeyAlias(); + } + + @Override + public String chooseServerAlias(String s, Principal[] principals, Socket socket) { + return null; + } + + @Override + public X509Certificate[] getCertificateChain(String alias) { + try { + return KeyChain.getCertificateChain(mXmppConnectionService, alias); + } catch (Exception e) { + return new X509Certificate[0]; + } + } + + @Override + public String[] getClientAliases(String s, Principal[] principals) { + return new String[0]; + } + + @Override + public String[] getServerAliases(String s, Principal[] principals) { + return new String[0]; + } + + @Override + public PrivateKey getPrivateKey(String alias) { + try { + return KeyChain.getPrivateKey(mXmppConnectionService, alias); + } catch (Exception e) { + return null; + } + } + }; + private Identity mServerIdentity = Identity.UNKNOWN; + + private OnIqPacketReceived createPacketReceiveHandler() { + return new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + if (packet.getType() == IqPacket.TYPE.RESULT) { + account.setOption(Account.OPTION_REGISTER, + false); + changeStatus(Account.State.REGISTRATION_SUCCESSFUL); + } else if (packet.hasChild("error") + && (packet.findChild("error") + .hasChild("conflict"))) { + changeStatus(Account.State.REGISTRATION_CONFLICT); + } else { + changeStatus(Account.State.REGISTRATION_FAILED); + Log.d(Config.LOGTAG, packet.toString()); + } + disconnect(true); + } + }; + } + public XmppConnection(final Account account, final XmppConnectionService service) { this.account = account; this.wakeLock = service.getPowerManager().newWakeLock( @@ -144,59 +235,118 @@ public class XmppConnection implements Runnable { features.encryptionEnabled = false; lastConnect = SystemClock.elapsedRealtime(); lastPingSent = SystemClock.elapsedRealtime(); + lastDiscoStarted = Long.MAX_VALUE; this.attempt++; + switch (account.getJid().getDomainpart()) { + case "chat.facebook.com": + mServerIdentity = Identity.FACEBOOK; + break; + case "nimbuzz.com": + mServerIdentity = Identity.NIMBUZZ; + break; + default: + mServerIdentity = Identity.UNKNOWN; + break; + } try { - shouldAuthenticate = shouldBind = !account.isOptionSet(Account.OPTION_REGISTER); + shouldAuthenticate = needsBinding = !account.isOptionSet(Account.OPTION_REGISTER); tagReader = new XmlReader(wakeLock); tagWriter = new TagWriter(); - packetCallbacks.clear(); this.changeStatus(Account.State.CONNECTING); - if (DNSHelper.isIp(account.getServer().toString())) { + final boolean useTor = mXmppConnectionService.useTorToConnect() || account.isOnion(); + final boolean extended = mXmppConnectionService.showExtendedConnectionOptions(); + if (useTor) { + String destination; + if (account.getHostname() == null || account.getHostname().isEmpty()) { + destination = account.getServer().toString(); + } else { + destination = account.getHostname(); + } + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": connect to " + destination + " via TOR"); + socket = SocksSocketFactory.createSocketOverTor(destination, account.getPort()); + startXmpp(); + } else if (extended && account.getHostname() != null && !account.getHostname().isEmpty()) { + socket = new Socket(); + try { + socket.connect(new InetSocketAddress(account.getHostname(), account.getPort()), Config.SOCKET_TIMEOUT * 1000); + } catch (IOException e) { + throw new UnknownHostException(); + } + startXmpp(); + } else if (DNSHelper.isIp(account.getServer().toString())) { socket = new Socket(); try { socket.connect(new InetSocketAddress(account.getServer().toString(), DEFAULT_PORT), Config.SOCKET_TIMEOUT * 1000); } catch (IOException e) { throw new UnknownHostException(); } + startXmpp(); } else { - socket = new Socket(); - TreeSet<SrvRecord> result = DNSHelper.querySrvRecord(account.getServer()); - if (!result.isEmpty()) { - for (SrvRecord record : result) { - try { - socket.connect(new InetSocketAddress(record.getName(), record.getPort()), Config.SOCKET_TIMEOUT * 1000); - break; - } catch (IOException e) { - Logging.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": " + e.getMessage()); - } - } - } - if (result.isEmpty() || !socket.isConnected()){ - try { - socket.connect(new InetSocketAddress(account.getServer().getDomainpart(), DEFAULT_PORT), Config.SOCKET_TIMEOUT * 1000); - } catch (IOException e) { - throw new UnknownHostException(); - } - } - } - final OutputStream out = socket.getOutputStream(); - tagWriter.setOutputStream(out); - final InputStream in = socket.getInputStream(); - tagReader.setInputStream(in); - tagWriter.beginDocument(); - sendStartStream(); - Tag nextTag; - while ((nextTag = tagReader.readTag()) != null) { - if (nextTag.isStart("stream")) { - processStream(nextTag); - break; - } else { - throw new IOException("unknown tag on connect"); + final Bundle result = DNSHelper.getSRVRecord(account.getServer(), mXmppConnectionService); + final ArrayList<Parcelable>values = result.getParcelableArrayList("values"); + for(Iterator<Parcelable> iterator = values.iterator(); iterator.hasNext();) { + final Bundle namePort = (Bundle) iterator.next(); + try { + String srvRecordServer; + try { + srvRecordServer = IDN.toASCII(namePort.getString("name")); + } catch (final IllegalArgumentException e) { + // TODO: Handle me?` + srvRecordServer = ""; + } + final int srvRecordPort = namePort.getInt("port"); + final String srvIpServer = namePort.getString("ip"); + // if tls is true, encryption is implied and must not be started + features.encryptionEnabled = namePort.getBoolean("tls"); + final InetSocketAddress addr; + if (srvIpServer != null) { + addr = new InetSocketAddress(srvIpServer, srvRecordPort); + Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + + ": using values from dns " + srvRecordServer + + "[" + srvIpServer + "]:" + srvRecordPort + " tls: " + features.encryptionEnabled); + } else { + addr = new InetSocketAddress(srvRecordServer, srvRecordPort); + Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + + ": using values from dns " + + srvRecordServer + ":" + srvRecordPort + " tls: " + features.encryptionEnabled); + } + + if (!features.encryptionEnabled) { + socket = new Socket(); + socket.connect(addr, Config.SOCKET_TIMEOUT * 1000); + } else { + final TlsFactoryVerifier tlsFactoryVerifier = getTlsFactoryVerifier(); + socket = tlsFactoryVerifier.factory.createSocket(); + + if (socket == null) { + throw new IOException("could not initialize ssl socket"); + } + + SSLSocketHelper.setSecurity((SSLSocket) socket); + SSLSocketHelper.setSNIHost(tlsFactoryVerifier.factory, (SSLSocket) socket, account.getServer().getDomainpart()); + SSLSocketHelper.setAlpnProtocol(tlsFactoryVerifier.factory, (SSLSocket) socket, "xmpp-client"); + + socket.connect(addr, Config.SOCKET_TIMEOUT * 1000); + + if (!tlsFactoryVerifier.verifier.verify(account.getServer().getDomainpart(), ((SSLSocket) socket).getSession())) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": TLS certificate verification failed"); + throw new SecurityException(); + } + } + + if (startXmpp()) + break; // successfully connected to server that speaks xmpp + } catch(final SecurityException e) { + throw e; + } catch (final Throwable e) { + Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": " + e.getMessage() +"("+e.getClass().getName()+")"); + if (!iterator.hasNext()) { + throw new UnknownHostException(); + } + } } } - if (socket.isConnected()) { - socket.close(); - } + processStream(); } catch (final IncompatibleServerException e) { this.changeStatus(Account.State.INCOMPATIBLE_SERVER); } catch (final SecurityException e) { @@ -205,6 +355,8 @@ public class XmppConnection implements Runnable { this.changeStatus(Account.State.UNAUTHORIZED); } catch (final UnknownHostException | ConnectException e) { this.changeStatus(Account.State.SERVER_NOT_FOUND); + } catch (final SocksSocketFactory.SocksProxyNotFoundException e) { + this.changeStatus(Account.State.TOR_NOT_AVAILABLE); } catch (final IOException | XmlPullParserException | NoSuchAlgorithmException e) { Logging.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": " + e.getMessage()); this.changeStatus(Account.State.OFFLINE); @@ -226,6 +378,66 @@ public class XmppConnection implements Runnable { } } + /** + * Starts xmpp protocol, call after connecting to socket + * @return true if server returns with valid xmpp, false otherwise + * @throws IOException Unknown tag on connect + * @throws XmlPullParserException Bad Xml + * @throws NoSuchAlgorithmException Other error + */ + private boolean startXmpp() throws IOException, XmlPullParserException, NoSuchAlgorithmException { + tagWriter.setOutputStream(socket.getOutputStream()); + tagReader.setInputStream(socket.getInputStream()); + tagWriter.beginDocument(); + sendStartStream(); + Tag nextTag; + while ((nextTag = tagReader.readTag()) != null) { + if (nextTag.isStart("stream")) { + return true; + } else { + throw new IOException("unknown tag on connect"); + } + } + if (socket.isConnected()) { + socket.close(); + } + return false; + } + + private static class TlsFactoryVerifier { + private final SSLSocketFactory factory; + private final HostnameVerifier verifier; + + public TlsFactoryVerifier(final SSLSocketFactory factory, final HostnameVerifier verifier) throws IOException { + this.factory = factory; + this.verifier = verifier; + if (factory == null || verifier == null) { + throw new IOException("could not setup ssl"); + } + } + } + + private TlsFactoryVerifier getTlsFactoryVerifier() throws NoSuchAlgorithmException, KeyManagementException, IOException { + final SSLContext sc = SSLContext.getInstance("TLS"); + MemorizingTrustManager trustManager = this.mXmppConnectionService.getMemorizingTrustManager(); + KeyManager[] keyManager; + if (account.getPrivateKeyAlias() != null && account.getPassword().isEmpty()) { + keyManager = new KeyManager[]{mKeyManager}; + } else { + keyManager = null; + } + sc.init(keyManager, new X509TrustManager[]{mInteractive ? trustManager : trustManager.getNonInteractive()}, mXmppConnectionService.getRNG()); + final SSLSocketFactory factory = sc.getSocketFactory(); + final HostnameVerifier verifier; + if (mInteractive) { + verifier = trustManager.wrapHostnameVerifier(new XmppDomainVerifier()); + } else { + verifier = trustManager.wrapHostnameVerifierNonInteractive(new XmppDomainVerifier()); + } + + return new TlsFactoryVerifier(factory, verifier); + } + @Override public void run() { try { @@ -238,153 +450,147 @@ public class XmppConnection implements Runnable { connect(); } - private void processStream(final Tag currentTag) throws XmlPullParserException, - IOException, NoSuchAlgorithmException { - Tag nextTag = tagReader.readTag(); - - while ((nextTag != null) && (!nextTag.isEnd("stream"))) { - if (nextTag.isStart("error")) { - processStreamError(nextTag); - } else if (nextTag.isStart("features")) { - processStreamFeatures(nextTag); - } else if (nextTag.isStart("proceed")) { - switchOverToTls(nextTag); - } else if (nextTag.isStart("success")) { - final String challenge = tagReader.readElement(nextTag).getContent(); - try { - saslMechanism.getResponse(challenge); - } catch (final SaslMechanism.AuthenticationException e) { - disconnect(true); - Logging.e(Config.LOGTAG, String.valueOf(e)); - } - Logging.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": logged in"); - account.setKey(Account.PINNED_MECHANISM_KEY, - String.valueOf(saslMechanism.getPriority())); - tagReader.reset(); - sendStartStream(); - processStream(tagReader.readTag()); - break; - } else if (nextTag.isStart("failure")) { - throw new UnauthorizedException(); - } else if (nextTag.isStart("challenge")) { - final String challenge = tagReader.readElement(nextTag).getContent(); - final Element response = new Element("response"); - response.setAttribute("xmlns", - "urn:ietf:params:xml:ns:xmpp-sasl"); - try { - response.setContent(saslMechanism.getResponse(challenge)); - } catch (final SaslMechanism.AuthenticationException e) { - // TODO: Send auth abort tag. - Logging.e(Config.LOGTAG, e.toString()); - } - tagWriter.writeElement(response); - } else if (nextTag.isStart("enabled")) { - final Element enabled = tagReader.readElement(nextTag); - if ("true".equals(enabled.getAttribute("resume"))) { - this.streamId = enabled.getAttribute("id"); - Logging.d(Config.LOGTAG, account.getJid().toBareJid().toString() - + ": stream managment(" + smVersion - + ") enabled (resumable)"); - } else { - Logging.d(Config.LOGTAG, account.getJid().toBareJid().toString() - + ": stream managment(" + smVersion + ") enabled"); - } - this.lastSessionStarted = SystemClock.elapsedRealtime(); - this.stanzasReceived = 0; - final RequestPacket r = new RequestPacket(smVersion); - tagWriter.writeStanzaAsync(r); - } else if (nextTag.isStart("resumed")) { - lastPacketReceived = SystemClock.elapsedRealtime(); - final Element resumed = tagReader.readElement(nextTag); - final String h = resumed.getAttribute("h"); - try { - final int serverCount = Integer.parseInt(h); - if (serverCount != stanzasSent) { - Logging.d(Config.LOGTAG, account.getJid().toBareJid().toString() - + ": session resumed with lost packages"); - stanzasSent = serverCount; - } else { - Logging.d(Config.LOGTAG, account.getJid().toBareJid().toString() - + ": session resumed"); - } - if (acknowledgedListener != null) { - for (int i = 0; i < messageReceipts.size(); ++i) { - if (serverCount >= messageReceipts.keyAt(i)) { - acknowledgedListener.onMessageAcknowledged( - account, messageReceipts.valueAt(i)); - } - } - } - messageReceipts.clear(); - } catch (final NumberFormatException ignored) { - } - sendServiceDiscoveryInfo(account.getServer()); - sendServiceDiscoveryInfo(account.getJid().toBareJid()); - sendServiceDiscoveryItems(account.getServer()); - sendInitialPing(); - } else if (nextTag.isStart("r")) { - tagReader.readElement(nextTag); - if (Config.EXTENDED_SM_LOGGING) { - Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": acknowledging stanza #" + this.stanzasReceived); - } - final AckPacket ack = new AckPacket(this.stanzasReceived, smVersion); - tagWriter.writeStanzaAsync(ack); - } else if (nextTag.isStart("a")) { - final Element ack = tagReader.readElement(nextTag); - lastPacketReceived = SystemClock.elapsedRealtime(); - try { - final int serverSequence = Integer.parseInt(ack.getAttribute("h")); - if (Config.EXTENDED_SM_LOGGING) { - Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": server acknowledged stanza #" + serverSequence); - } - final String msgId = this.messageReceipts.get(serverSequence); - if (msgId != null) { - if (this.acknowledgedListener != null) { - this.acknowledgedListener.onMessageAcknowledged( - account, msgId); - } - this.messageReceipts.remove(serverSequence); - } - } catch (NumberFormatException e) { - Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": server send ack without sequence number"); - } - } else if (nextTag.isStart("failed")) { - tagReader.readElement(nextTag); - Logging.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": resumption failed"); - streamId = null; - if (account.getStatus() != Account.State.ONLINE) { - sendBindRequest(); - } - } else if (nextTag.isStart("iq")) { - processIq(nextTag); - } else if (nextTag.isStart("message")) { - processMessage(nextTag); - } else if (nextTag.isStart("presence")) { - processPresence(nextTag); - } - nextTag = tagReader.readTag(); - } - if (account.getStatus() == Account.State.ONLINE) { - account. setStatus(Account.State.OFFLINE); - if (statusListener != null) { - statusListener.onStatusChanged(account); - } + private void processStream() throws XmlPullParserException, IOException, NoSuchAlgorithmException { + Tag nextTag = tagReader.readTag(); + while (nextTag != null && !nextTag.isEnd("stream")) { + if (nextTag.isStart("error")) { + processStreamError(nextTag); + } else if (nextTag.isStart("features")) { + processStreamFeatures(nextTag); + } else if (nextTag.isStart("proceed")) { + switchOverToTls(nextTag); + } else if (nextTag.isStart("success")) { + final String challenge = tagReader.readElement(nextTag).getContent(); + try { + saslMechanism.getResponse(challenge); + } catch (final SaslMechanism.AuthenticationException e) { + disconnect(true); + Log.e(Config.LOGTAG, String.valueOf(e)); + } + Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": logged in"); + account.setKey(Account.PINNED_MECHANISM_KEY, + String.valueOf(saslMechanism.getPriority())); + tagReader.reset(); + sendStartStream(); + final Tag tag = tagReader.readTag(); + if (tag != null && tag.isStart("stream")) { + processStream(); + } else { + throw new IOException("server didn't restart stream after successful auth"); + } + break; + } else if (nextTag.isStart("failure")) { + throw new UnauthorizedException(); + } else if (nextTag.isStart("challenge")) { + final String challenge = tagReader.readElement(nextTag).getContent(); + final Element response = new Element("response"); + response.setAttribute("xmlns", + "urn:ietf:params:xml:ns:xmpp-sasl"); + try { + response.setContent(saslMechanism.getResponse(challenge)); + } catch (final SaslMechanism.AuthenticationException e) { + // TODO: Send auth abort tag. + Log.e(Config.LOGTAG, e.toString()); + } + tagWriter.writeElement(response); + } else if (nextTag.isStart("enabled")) { + final Element enabled = tagReader.readElement(nextTag); + if ("true".equals(enabled.getAttribute("resume"))) { + this.streamId = enabled.getAttribute("id"); + Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + + ": stream managment(" + smVersion + + ") enabled (resumable)"); + } else { + Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + + ": stream management(" + smVersion + ") enabled"); + } + this.stanzasReceived = 0; + final RequestPacket r = new RequestPacket(smVersion); + tagWriter.writeStanzaAsync(r); + } else if (nextTag.isStart("resumed")) { + lastPacketReceived = SystemClock.elapsedRealtime(); + final Element resumed = tagReader.readElement(nextTag); + final String h = resumed.getAttribute("h"); + try { + final int serverCount = Integer.parseInt(h); + if (serverCount != stanzasSent) { + Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + + ": session resumed with lost packages"); + stanzasSent = serverCount; + } else { + Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": session resumed"); + } + acknowledgeStanzaUpTo(serverCount); + ArrayList<AbstractAcknowledgeableStanza> failedStanzas = new ArrayList<>(); + for(int i = 0; i < this.mStanzaQueue.size(); ++i) { + failedStanzas.add(mStanzaQueue.valueAt(i)); + } + mStanzaQueue.clear(); + Log.d(Config.LOGTAG,"resending "+failedStanzas.size()+" stanzas"); + for(AbstractAcknowledgeableStanza packet : failedStanzas) { + if (packet instanceof MessagePacket) { + MessagePacket message = (MessagePacket) packet; + mXmppConnectionService.markMessage(account, + message.getTo().toBareJid(), + message.getId(), + Message.STATUS_UNSEND); } + sendPacket(packet); + } + } catch (final NumberFormatException ignored) { + } + Log.d(Config.LOGTAG, account.getJid().toBareJid()+ ": online with resource " + account.getResource()); + changeStatus(Account.State.ONLINE); + } else if (nextTag.isStart("r")) { + tagReader.readElement(nextTag); + if (Config.EXTENDED_SM_LOGGING) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": acknowledging stanza #" + this.stanzasReceived); + } + final AckPacket ack = new AckPacket(this.stanzasReceived, smVersion); + tagWriter.writeStanzaAsync(ack); + } else if (nextTag.isStart("a")) { + final Element ack = tagReader.readElement(nextTag); + lastPacketReceived = SystemClock.elapsedRealtime(); + try { + final int serverSequence = Integer.parseInt(ack.getAttribute("h")); + acknowledgeStanzaUpTo(serverSequence); + } catch (NumberFormatException e) { + Log.d(Config.LOGTAG,account.getJid().toBareJid()+": server send ack without sequence number"); + } + } else if (nextTag.isStart("failed")) { + tagReader.readElement(nextTag); + Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": resumption failed"); + resetStreamId(); + if (account.getStatus() != Account.State.ONLINE) { + sendBindRequest(); + } + } else if (nextTag.isStart("iq")) { + processIq(nextTag); + } else if (nextTag.isStart("message")) { + processMessage(nextTag); + } else if (nextTag.isStart("presence")) { + processPresence(nextTag); + } + nextTag = tagReader.readTag(); + } + throw new IOException("reached end of stream. last tag was "+nextTag); } - private void sendInitialPing() { - Logging.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": sending intial ping"); - final IqPacket iq = new IqPacket(IqPacket.TYPE.GET); - iq.setFrom(account.getJid()); - iq.addChild("ping", "urn:xmpp:ping"); - this.sendIqPacket(iq, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(final Account account, final IqPacket packet) { - Logging.d(Config.LOGTAG, account.getJid().toBareJid().toString() - + ": online with resource " + account.getResource()); - changeStatus(Account.State.ONLINE); + private void acknowledgeStanzaUpTo(int serverCount) { + for (int i = 0; i < mStanzaQueue.size(); ++i) { + if (serverCount >= mStanzaQueue.keyAt(i)) { + if (Config.EXTENDED_SM_LOGGING) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": server acknowledged stanza #" + mStanzaQueue.keyAt(i)); + } + AbstractAcknowledgeableStanza stanza = mStanzaQueue.valueAt(i); + if (stanza instanceof MessagePacket && acknowledgedListener != null) { + MessagePacket packet = (MessagePacket) stanza; + acknowledgedListener.onMessageAcknowledged(account, packet.getId()); + } + mStanzaQueue.removeAt(i); + i--; } - }); + } } private Element processPacket(final Tag currentTag, final int packetType) @@ -447,26 +653,32 @@ public class XmppConnection implements Runnable { this.jingleListener.onJinglePacketReceived(account,(JinglePacket) packet); } } else { - if (packetCallbacks.containsKey(packet.getId())) { - final Pair<IqPacket, OnIqPacketReceived> packetCallbackDuple = packetCallbacks.get(packet.getId()); - // Packets to the server should have responses from the server - if (packetCallbackDuple.first.toServer(account)) { - if (packet.fromServer(account)) { - packetCallbackDuple.second.onIqPacketReceived(account, packet); - packetCallbacks.remove(packet.getId()); - } else { - Logging.e(Config.LOGTAG,account.getJid().toBareJid().toString()+": ignoring spoofed iq packet"); - } - } else { - if (packet.getFrom().equals(packetCallbackDuple.first.getTo())) { - packetCallbackDuple.second.onIqPacketReceived(account, packet); - packetCallbacks.remove(packet.getId()); + OnIqPacketReceived callback = null; + synchronized (this.packetCallbacks) { + if (packetCallbacks.containsKey(packet.getId())) { + final Pair<IqPacket, OnIqPacketReceived> packetCallbackDuple = packetCallbacks.get(packet.getId()); + // Packets to the server should have responses from the server + if (packetCallbackDuple.first.toServer(account)) { + if (packet.fromServer(account) || mServerIdentity == Identity.FACEBOOK) { + callback = packetCallbackDuple.second; + packetCallbacks.remove(packet.getId()); + } else { + Log.e(Config.LOGTAG, account.getJid().toBareJid().toString() + ": ignoring spoofed iq packet"); + } } else { - Logging.e(Config.LOGTAG,account.getJid().toBareJid().toString()+": ignoring spoofed iq packet"); + if (packet.getFrom().equals(packetCallbackDuple.first.getTo())) { + callback = packetCallbackDuple.second; + packetCallbacks.remove(packet.getId()); + } else { + Logging.e(Config.LOGTAG, account.getJid().toBareJid().toString() + ": ignoring spoofed iq packet"); + } } + } else if (packet.getType() == IqPacket.TYPE.GET || packet.getType() == IqPacket.TYPE.SET) { + callback = this.unregisteredIqListener; } - } else if (packet.getType() == IqPacket.TYPE.GET|| packet.getType() == IqPacket.TYPE.SET) { - this.unregisteredIqListener.onIqPacketReceived(account, packet); + } + if (callback != null) { + callback.onIqPacketReceived(account,packet); } } } @@ -487,53 +699,44 @@ public class XmppConnection implements Runnable { tagWriter.writeTag(startTLS); } + + private void switchOverToTls(final Tag currentTag) throws XmlPullParserException, IOException { tagReader.readTag(); try { - final SSLContext sc = SSLContext.getInstance("TLS"); - sc.init(null,new X509TrustManager[]{this.mXmppConnectionService.getMemorizingTrustManager()},mXmppConnectionService.getRNG()); - final SSLSocketFactory factory = sc.getSocketFactory(); - final HostnameVerifier verifier = this.mXmppConnectionService.getMemorizingTrustManager().wrapHostnameVerifier(new StrictHostnameVerifier()); + final TlsFactoryVerifier tlsFactoryVerifier = getTlsFactoryVerifier(); final InetAddress address = socket == null ? null : socket.getInetAddress(); - if (factory == null || address == null || verifier == null) { + if (address == null) { throw new IOException("could not setup ssl"); } - final SSLSocket sslSocket = (SSLSocket) factory.createSocket(socket,address.getHostAddress(), socket.getPort(),true); + final SSLSocket sslSocket = (SSLSocket) tlsFactoryVerifier.factory.createSocket(socket, address.getHostAddress(), socket.getPort(), true); if (sslSocket == null) { throw new IOException("could not initialize ssl socket"); } - final String[] supportProtocols; - final Collection<String> supportedProtocols = new LinkedList<>( - Arrays.asList(sslSocket.getSupportedProtocols())); - supportedProtocols.remove("SSLv3"); - supportProtocols = supportedProtocols.toArray(new String[supportedProtocols.size()]); - - sslSocket.setEnabledProtocols(supportProtocols); + SSLSocketHelper.setSecurity(sslSocket); - final String[] cipherSuites = CryptoHelper.getOrderedCipherSuites( - sslSocket.getSupportedCipherSuites()); - //Logging.d(Config.LOGTAG, "Using ciphers: " + Arrays.toString(cipherSuites)); - if (cipherSuites.length > 0) { - sslSocket.setEnabledCipherSuites(cipherSuites); - } - - if (!verifier.verify(account.getServer().getDomainpart(),sslSocket.getSession())) { - Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": TLS certificate verification failed"); + if (!tlsFactoryVerifier.verifier.verify(account.getServer().getDomainpart(), sslSocket.getSession())) { + Log.d(Config.LOGTAG,account.getJid().toBareJid()+": TLS certificate verification failed"); throw new SecurityException(); } tagReader.setInputStream(sslSocket.getInputStream()); tagWriter.setOutputStream(sslSocket.getOutputStream()); sendStartStream(); - Logging.d(Config.LOGTAG, account.getJid().toBareJid()+ ": TLS connection established"); + Log.d(Config.LOGTAG, account.getJid().toBareJid()+ ": TLS connection established"); features.encryptionEnabled = true; - processStream(tagReader.readTag()); + final Tag tag = tagReader.readTag(); + if (tag != null && tag.isStart("stream")) { + processStream(); + } else { + throw new IOException("server didn't restart stream after STARTTLS"); + } sslSocket.close(); } catch (final NoSuchAlgorithmException | KeyManagementException e1) { - Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": TLS certificate verification failed"); + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": TLS certificate verification failed"); throw new SecurityException(); } } @@ -543,21 +746,26 @@ public class XmppConnection implements Runnable { this.streamFeatures = tagReader.readElement(currentTag); if (this.streamFeatures.hasChild("starttls") && !features.encryptionEnabled) { sendStartTLS(); - } else if (this.streamFeatures.hasChild("register") - && account.isOptionSet(Account.OPTION_REGISTER) - && features.encryptionEnabled) { - sendRegistryRequest(); + } else if (this.streamFeatures.hasChild("register") && account.isOptionSet(Account.OPTION_REGISTER)) { + if (features.encryptionEnabled || Config.ALLOW_NON_TLS_CONNECTIONS) { + sendRegistryRequest(); + } else { + throw new IncompatibleServerException(); + } } else if (!this.streamFeatures.hasChild("register") && account.isOptionSet(Account.OPTION_REGISTER)) { changeStatus(Account.State.REGISTRATION_NOT_SUPPORTED); disconnect(true); } else if (this.streamFeatures.hasChild("mechanisms") - && shouldAuthenticate && features.encryptionEnabled) { + && shouldAuthenticate + && (features.encryptionEnabled || Config.ALLOW_NON_TLS_CONNECTIONS)) { final List<String> mechanisms = extractMechanisms(streamFeatures .findChild("mechanisms")); final Element auth = new Element("auth"); auth.setAttribute("xmlns", "urn:ietf:params:xml:ns:xmpp-sasl"); - if (mechanisms.contains("SCRAM-SHA-1")) { + if (mechanisms.contains("EXTERNAL") && account.getPrivateKeyAlias() != null) { + saslMechanism = new External(tagWriter, account, mXmppConnectionService.getRNG()); + } else if (mechanisms.contains("SCRAM-SHA-1")) { saslMechanism = new ScramSha1(tagWriter, account, mXmppConnectionService.getRNG()); } else if (mechanisms.contains("PLAIN")) { saslMechanism = new Plain(tagWriter, account); @@ -587,19 +795,18 @@ public class XmppConnection implements Runnable { } else { throw new IncompatibleServerException(); } - } else if (this.streamFeatures.hasChild("sm", "urn:xmpp:sm:" - + smVersion) - && streamId != null) { + } else if (this.streamFeatures.hasChild("sm", "urn:xmpp:sm:" + smVersion) && streamId != null) { if (Config.EXTENDED_SM_LOGGING) { Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": resuming after stanza #"+stanzasReceived); } final ResumePacket resume = new ResumePacket(this.streamId, stanzasReceived, smVersion); this.tagWriter.writeStanzaAsync(resume); - } else if (this.streamFeatures.hasChild("bind") && shouldBind) { - sendBindRequest(); - } else { - disconnect(true); - changeStatus(Account.State.INCOMPATIBLE_SERVER); + } else if (needsBinding) { + if (this.streamFeatures.hasChild("bind")) { + sendBindRequest(); + } else { + throw new IncompatibleServerException(); + } } } @@ -612,6 +819,15 @@ public class XmppConnection implements Runnable { return mechanisms; } + public void sendCaptchaRegistryRequest(String id, Data data) { + if (data == null) { + setAccountCreationFailed(""); + } else { + IqPacket request = getIqGenerator().generateCreateAccountWithCaptcha(account, id, data); + sendIqPacket(request, createPacketReceiveHandler()); + } + } + private void sendRegistryRequest() { final IqPacket register = new IqPacket(IqPacket.TYPE.GET); register.query("jabber:iq:register"); @@ -620,59 +836,95 @@ public class XmppConnection implements Runnable { @Override public void onIqPacketReceived(final Account account, final IqPacket packet) { - final Element instructions = packet.query().findChild("instructions"); - if (packet.query().hasChild("username") + boolean failed = false; + if (packet.getType() == IqPacket.TYPE.RESULT + && packet.query().hasChild("username") && (packet.query().hasChild("password"))) { final IqPacket register = new IqPacket(IqPacket.TYPE.SET); final Element username = new Element("username").setContent(account.getUsername()); final Element password = new Element("password").setContent(account.getPassword()); register.query("jabber:iq:register").addChild(username); register.query().addChild(password); - sendIqPacket(register, new OnIqPacketReceived() { - - @Override - public void onIqPacketReceived(final Account account, final IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.RESULT) { - account.setOption(Account.OPTION_REGISTER, - false); - changeStatus(Account.State.REGISTRATION_SUCCESSFUL); - } else if (packet.hasChild("error") - && (packet.findChild("error") - .hasChild("conflict"))) { - changeStatus(Account.State.REGISTRATION_CONFLICT); - } else { - changeStatus(Account.State.REGISTRATION_FAILED); - Logging.d(Config.LOGTAG, packet.toString()); - } - disconnect(true); + sendIqPacket(register, createPacketReceiveHandler()); + } else if (packet.getType() == IqPacket.TYPE.RESULT + && (packet.query().hasChild("x", "jabber:x:data"))) { + final Data data = Data.parse(packet.query().findChild("x", "jabber:x:data")); + final Element blob = packet.query().findChild("data", "urn:xmpp:bob"); + final String id = packet.getId(); + + Bitmap captcha = null; + if (blob != null) { + try { + final String base64Blob = blob.getContent(); + final byte[] strBlob = Base64.decode(base64Blob, Base64.DEFAULT); + InputStream stream = new ByteArrayInputStream(strBlob); + captcha = BitmapFactory.decodeStream(stream); + } catch (Exception e) { + //ignored + } + } else { + try { + Field url = data.getFieldByName("url"); + String urlString = url.findChildContent("value"); + URL uri = new URL(urlString); + captcha = BitmapFactory.decodeStream(uri.openConnection().getInputStream()); + } catch (IOException e) { + Logging.e(Config.LOGTAG, e.toString()); } - }); + } + + if (captcha != null) { + failed = !mXmppConnectionService.displayCaptchaRequest(account, id, data, captcha); + } } else { - changeStatus(Account.State.REGISTRATION_FAILED); - disconnect(true); - Logging.d(Config.LOGTAG, account.getJid().toBareJid() - + ": could not register. instructions are" - + (instructions != null ? instructions.getContent() : "")); + failed = true; + } + + if (failed) { + final Element instructions = packet.query().findChild("instructions"); + setAccountCreationFailed((instructions != null) ? instructions.getContent() : ""); } } }); } + private void setAccountCreationFailed(String instructions) { + changeStatus(Account.State.REGISTRATION_FAILED); + disconnect(true); + Log.d(Config.LOGTAG, account.getJid().toBareJid() + + ": could not register. instructions are" + + instructions); + } + + public void resetEverything() { + resetStreamId(); + clearIqCallbacks(); + mStanzaQueue.clear(); + synchronized (this.disco) { + disco.clear(); + } + } + private void sendBindRequest() { - while(!mXmppConnectionService.areMessagesInitialized()) { + while(!mXmppConnectionService.areMessagesInitialized() && socket != null && !socket.isClosed()) { try { Thread.sleep(500); } catch (final InterruptedException ignored) { } } + needsBinding = false; + clearIqCallbacks(); final IqPacket iq = new IqPacket(IqPacket.TYPE.SET); iq.addChild("bind", "urn:ietf:params:xml:ns:xmpp-bind") .addChild("resource").setContent(account.getResource()); this.sendUnmodifiedIqPacket(iq, new OnIqPacketReceived() { @Override public void onIqPacketReceived(final Account account, final IqPacket packet) { + if (packet.getType() == IqPacket.TYPE.TIMEOUT) { + return; + } final Element bind = packet.findChild("bind"); - if (bind != null) { + if (bind != null && packet.getType() == IqPacket.TYPE.RESULT) { final Element jid = bind.findChild("jid"); if (jid != null && jid.getContent() != null) { try { @@ -686,24 +938,71 @@ public class XmppConnection implements Runnable { sendPostBindInitialization(); } } else { + Logging.d(Config.LOGTAG, account.getJid() + ": disconnecting because of bind failure"); disconnect(true); } } else { + Logging.d(Config.LOGTAG, account.getJid() + ": disconnecting because of bind failure"); disconnect(true); } } }); } + private void clearIqCallbacks() { + final IqPacket failurePacket = new IqPacket(IqPacket.TYPE.TIMEOUT); + final ArrayList<OnIqPacketReceived> callbacks = new ArrayList<>(); + synchronized (this.packetCallbacks) { + if (this.packetCallbacks.size() == 0) { + return; + } + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": clearing "+this.packetCallbacks.size()+" iq callbacks"); + final Iterator<Pair<IqPacket, OnIqPacketReceived>> iterator = this.packetCallbacks.values().iterator(); + while (iterator.hasNext()) { + Pair<IqPacket, OnIqPacketReceived> entry = iterator.next(); + callbacks.add(entry.second); + iterator.remove(); + } + } + for(OnIqPacketReceived callback : callbacks) { + callback.onIqPacketReceived(account,failurePacket); + } + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": done clearing iq callbacks. " + this.packetCallbacks.size() + " left"); + } + + public void sendDiscoTimeout() { + final IqPacket failurePacket = new IqPacket(IqPacket.TYPE.ERROR); //don't use timeout + final ArrayList<OnIqPacketReceived> callbacks = new ArrayList<>(); + synchronized (this.mPendingServiceDiscoveriesIds) { + for(String id : mPendingServiceDiscoveriesIds) { + synchronized (this.packetCallbacks) { + Pair<IqPacket, OnIqPacketReceived> pair = this.packetCallbacks.remove(id); + if (pair != null) { + callbacks.add(pair.second); + } + } + } + this.mPendingServiceDiscoveriesIds.clear(); + } + if (callbacks.size() > 0) { + Log.d(Config.LOGTAG,account.getJid().toBareJid()+": sending disco timeout"); + resetStreamId(); //we don't want to live with this for ever + } + for(OnIqPacketReceived callback : callbacks) { + callback.onIqPacketReceived(account,failurePacket); + } + } + private void sendStartSession() { final IqPacket startSession = new IqPacket(IqPacket.TYPE.SET); - startSession.addChild("session","urn:ietf:params:xml:ns:xmpp-session"); + startSession.addChild("session", "urn:ietf:params:xml:ns:xmpp-session"); this.sendUnmodifiedIqPacket(startSession, new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket packet) { if (packet.getType() == IqPacket.TYPE.RESULT) { sendPostBindInitialization(); - } else { + } else if (packet.getType() != IqPacket.TYPE.TIMEOUT) { + Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not init sessions"); disconnect(true); } } @@ -721,56 +1020,94 @@ public class XmppConnection implements Runnable { final EnablePacket enable = new EnablePacket(smVersion); tagWriter.writeStanzaAsync(enable); stanzasSent = 0; - messageReceipts.clear(); + mStanzaQueue.clear(); } features.carbonsEnabled = false; features.blockListRequested = false; - disco.clear(); + synchronized (this.disco) { + this.disco.clear(); + } + mPendingServiceDiscoveries = mServerIdentity == Identity.NIMBUZZ ? 1 : 0; + lastDiscoStarted = SystemClock.elapsedRealtime(); + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": starting service discovery"); + mXmppConnectionService.scheduleWakeUpCall(Config.CONNECT_DISCO_TIMEOUT, account.getUuid().hashCode()); + sendServiceDiscoveryItems(account.getServer()); sendServiceDiscoveryInfo(account.getServer()); sendServiceDiscoveryInfo(account.getJid().toBareJid()); - sendServiceDiscoveryItems(account.getServer()); - if (bindListener != null) { - bindListener.onBind(account); - } - sendInitialPing(); + this.lastSessionStarted = SystemClock.elapsedRealtime(); } private void sendServiceDiscoveryInfo(final Jid jid) { - if (disco.containsKey(jid)) { - if (account.getServer().equals(jid)) { - enableAdvancedStreamFeatures(); - } - } else { - final IqPacket iq = new IqPacket(IqPacket.TYPE.GET); - iq.setTo(jid); - iq.query("http://jabber.org/protocol/disco#info"); - this.sendIqPacket(iq, new OnIqPacketReceived() { + if (mServerIdentity != Identity.NIMBUZZ) { + mPendingServiceDiscoveries++; + } + final IqPacket iq = new IqPacket(IqPacket.TYPE.GET); + iq.setTo(jid); + iq.query("http://jabber.org/protocol/disco#info"); + String id = this.sendIqPacket(iq, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(final Account account, final IqPacket packet) { - final List<Element> elements = packet.query().getChildren(); - final Info info = new Info(); - for (final Element element : elements) { - if (element.getName().equals("identity")) { - String type = element.getAttribute("type"); - String category = element.getAttribute("category"); - if (type != null && category != null) { - info.identities.add(new Pair<>(category,type)); + @Override + public void onIqPacketReceived(final Account account, final IqPacket packet) { + if (packet.getType() == IqPacket.TYPE.RESULT) { + boolean advancedStreamFeaturesLoaded; + synchronized (XmppConnection.this.disco) { + final List<Element> elements = packet.query().getChildren(); + final Info info = new Info(); + for (final Element element : elements) { + if (element.getName().equals("identity")) { + String type = element.getAttribute("type"); + String category = element.getAttribute("category"); + String name = element.getAttribute("name"); + if (type != null && category != null) { + info.identities.add(new Pair<>(category, type)); + if (mServerIdentity == Identity.UNKNOWN + && type.equals("im") + && category.equals("server")) { + if (name != null && jid.equals(account.getServer())) { + switch (name) { + case "Prosody": + mServerIdentity = Identity.PROSODY; + break; + case "ejabberd": + mServerIdentity = Identity.EJABBERD; + break; + case "Slack-XMPP": + mServerIdentity = Identity.SLACK; + break; + } + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": server name: " + name); + } + } + } + } else if (element.getName().equals("feature")) { + info.features.add(element.getAttribute("var")); } - } else if (element.getName().equals("feature")) { - info.features.add(element.getAttribute("var")); } + disco.put(jid, info); + advancedStreamFeaturesLoaded = disco.containsKey(account.getServer()) + && disco.containsKey(account.getJid().toBareJid()); } - disco.put(jid, info); - - if (account.getServer().equals(jid)) { + if (advancedStreamFeaturesLoaded && (jid.equals(account.getServer()) || jid.equals(account.getJid().toBareJid()))) { enableAdvancedStreamFeatures(); - for (final OnAdvancedStreamFeaturesLoaded listener : advancedStreamFeaturesLoadedListeners) { - listener.onAdvancedStreamFeaturesAvailable(account); + } + } else { + Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not query disco info for " + jid.toString()); + } + if (packet.getType() != IqPacket.TYPE.TIMEOUT) { + mPendingServiceDiscoveries--; + if (mPendingServiceDiscoveries == 0) { + Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": done with service discovery"); + Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": online with resource " + account.getResource()); + changeStatus(Account.State.ONLINE); + if (bindListener != null) { + bindListener.onBind(account); } } } - }); + } + }); + synchronized (this.mPendingServiceDiscoveriesIds) { + this.mPendingServiceDiscoveriesIds.add(id); } } @@ -779,9 +1116,12 @@ public class XmppConnection implements Runnable { sendEnableCarbons(); } if (getFeatures().blocking() && !features.blockListRequested) { - Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": Requesting block list"); + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": Requesting block list"); this.sendIqPacket(getIqGenerator().generateGetBlockList(), mXmppConnectionService.getIqParser()); } + for (final OnAdvancedStreamFeaturesLoaded listener : advancedStreamFeaturesLoadedListeners) { + listener.onAdvancedStreamFeaturesAvailable(account); + } } private void sendServiceDiscoveryItems(final Jid server) { @@ -792,14 +1132,18 @@ public class XmppConnection implements Runnable { @Override public void onIqPacketReceived(final Account account, final IqPacket packet) { - final List<Element> elements = packet.query().getChildren(); - for (final Element element : elements) { - if (element.getName().equals("item")) { - final Jid jid = element.getAttributeAsJid("jid"); - if (jid != null && !jid.equals(account.getServer())) { - sendServiceDiscoveryInfo(jid); + if (packet.getType() == IqPacket.TYPE.RESULT) { + final List<Element> elements = packet.query().getChildren(); + for (final Element element : elements) { + if (element.getName().equals("item")) { + final Jid jid = element.getAttributeAsJid("jid"); + if (jid != null && !jid.equals(account.getServer())) { + sendServiceDiscoveryInfo(jid); + } } } + } else { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not query disco items of " + server); } } }); @@ -833,12 +1177,13 @@ public class XmppConnection implements Runnable { Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": switching resource due to conflict (" + account.getResource() + ")"); + } else if (streamError != null) { + Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": stream error "+streamError.toString()); } } private void sendStartStream() throws IOException { final Tag stream = Tag.start("stream:stream"); - stream.setAttribute("from", account.getJid().toBareJid().toString()); stream.setAttribute("to", account.getServer().toString()); stream.setAttribute("version", "1.0"); stream.setAttribute("xml:lang", "en"); @@ -851,24 +1196,23 @@ public class XmppConnection implements Runnable { return new BigInteger(50, mXmppConnectionService.getRNG()).toString(32); } - public void sendIqPacket(final IqPacket packet, final OnIqPacketReceived callback) { + public String sendIqPacket(final IqPacket packet, final OnIqPacketReceived callback) { packet.setFrom(account.getJid()); - this.sendUnmodifiedIqPacket(packet,callback); - + return this.sendUnmodifiedIqPacket(packet, callback); } - private synchronized void sendUnmodifiedIqPacket(final IqPacket packet, final OnIqPacketReceived callback) { + private synchronized String sendUnmodifiedIqPacket(final IqPacket packet, final OnIqPacketReceived callback) { if (packet.getId() == null) { final String id = nextRandomId(); packet.setAttribute("id", id); } if (callback != null) { - if (packet.getId() == null) { - packet.setId(nextRandomId()); + synchronized (this.packetCallbacks) { + packetCallbacks.put(packet.getId(), new Pair<>(packet, callback)); } - packetCallbacks.put(packet.getId(), new Pair<>(packet, callback)); } this.sendPacket(packet); + return packet.getId(); } public void sendMessagePacket(final MessagePacket packet) { @@ -885,24 +1229,22 @@ public class XmppConnection implements Runnable { disconnect(true); return; } - final String name = packet.getName(); - if (name.equals("iq") || name.equals("message") || name.equals("presence")) { - ++stanzasSent; - } tagWriter.writeStanzaAsync(packet); - if (packet instanceof MessagePacket && packet.getId() != null && getFeatures().sm()) { - if (Config.EXTENDED_SM_LOGGING) { - Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": requesting ack for message stanza #" + stanzasSent); + if (packet instanceof AbstractAcknowledgeableStanza) { + AbstractAcknowledgeableStanza stanza = (AbstractAcknowledgeableStanza) packet; + ++stanzasSent; + this.mStanzaQueue.put(stanzasSent, stanza); + if (stanza instanceof MessagePacket && stanza.getId() != null && getFeatures().sm()) { + if (Config.EXTENDED_SM_LOGGING) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": requesting ack for message stanza #" + stanzasSent); + } + tagWriter.writeStanzaAsync(new RequestPacket(this.smVersion)); } - this.messageReceipts.put(stanzasSent, packet.getId()); - tagWriter.writeStanzaAsync(new RequestPacket(this.smVersion)); } } public void sendPing() { - if (streamFeatures.hasChild("sm")) { - tagWriter.writeStanzaAsync(new RequestPacket(smVersion)); - } else { + if (!r()) { final IqPacket iq = new IqPacket(IqPacket.TYPE.GET); iq.setFrom(account.getJid()); iq.addChild("ping", "urn:xmpp:ping"); @@ -950,36 +1292,39 @@ public class XmppConnection implements Runnable { } public void disconnect(final boolean force) { - Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": disconnecting"); - try { - if (force) { + Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": disconnecting force="+Boolean.valueOf(force)); + if (force) { + try { socket.close(); - return; + } catch(Exception e) { + Logging.d(Config.LOGTAG,account.getJid().toBareJid().toString()+": exception during force close ("+e.getMessage()+")"); } - new Thread(new Runnable() { - - @Override - public void run() { - if (tagWriter.isActive()) { - tagWriter.finish(); - try { - while (!tagWriter.finished()) { - Logging.d(Config.LOGTAG, "not yet finished"); - Thread.sleep(100); - } - tagWriter.writeTag(Tag.end("stream:stream")); - socket.close(); - } catch (final IOException e) { - Logging.d(Config.LOGTAG, - "io exception during disconnect"); - } catch (final InterruptedException e) { - Logging.d(Config.LOGTAG, "interrupted"); + return; + } else { + if (tagWriter.isActive()) { + tagWriter.finish(); + try { + int i = 0; + boolean warned = false; + while (!tagWriter.finished() && socket.isConnected() && i <= 10) { + if (!warned) { + Log.d(Config.LOGTAG, account.getJid().toBareJid()+": waiting for tag writer to finish"); + warned = true; } + Thread.sleep(200); + i++; + } + if (warned) { + Log.d(Config.LOGTAG,account.getJid().toBareJid()+": tag writer has finished"); } + Log.d(Config.LOGTAG,account.getJid().toBareJid()+": closing stream"); + tagWriter.writeTag(Tag.end("stream:stream")); + } catch (final IOException e) { + Log.d(Config.LOGTAG,account.getJid().toBareJid()+": io exception during disconnect ("+e.getMessage()+")"); + } catch (final InterruptedException e) { + Log.d(Config.LOGTAG, "interrupted"); } - }).start(); - } catch (final IOException e) { - Logging.d(Config.LOGTAG, "io exception during disconnect"); + } } } @@ -988,13 +1333,15 @@ public class XmppConnection implements Runnable { } public List<Jid> findDiscoItemsByFeature(final String feature) { - final List<Jid> items = new ArrayList<>(); - for (final Entry<Jid, Info> cursor : disco.entrySet()) { - if (cursor.getValue().features.contains(feature)) { - items.add(cursor.getKey()); + synchronized (this.disco) { + final List<Jid> items = new ArrayList<>(); + for (final Entry<Jid, Info> cursor : this.disco.entrySet()) { + if (cursor.getValue().features.contains(feature)) { + items.add(cursor.getKey()); + } } + return items; } - return items; } public Jid findDiscoItemByFeature(final String feature) { @@ -1005,17 +1352,24 @@ public class XmppConnection implements Runnable { return null; } - public void r() { - this.tagWriter.writeStanzaAsync(new RequestPacket(smVersion)); + public boolean r() { + if (getFeatures().sm()) { + this.tagWriter.writeStanzaAsync(new RequestPacket(smVersion)); + return true; + } else { + return false; + } } public String getMucServer() { - for (final Entry<Jid, Info> cursor : disco.entrySet()) { - final Info value = cursor.getValue(); - if (value.features.contains("http://jabber.org/protocol/muc") - && !value.features.contains("jabber:iq:gateway") - && !value.identities.contains(new Pair<>("conference","irc"))) { - return cursor.getKey().toString(); + synchronized (this.disco) { + for (final Entry<Jid, Info> cursor : disco.entrySet()) { + final Info value = cursor.getValue(); + if (value.features.contains("http://jabber.org/protocol/muc") + && !value.features.contains("jabber:iq:gateway") + && !value.identities.contains(new Pair<>("conference", "irc"))) { + return cursor.getKey().toString(); + } } } return null; @@ -1053,6 +1407,9 @@ public class XmppConnection implements Runnable { return this.lastPingSent; } + public long getLastDiscoStarted() { + return this.lastDiscoStarted; + } public long getLastPacketReceived() { return this.lastPacketReceived; } @@ -1070,6 +1427,14 @@ public class XmppConnection implements Runnable { this.lastConnect = 0; } + public void setInteractive(boolean interactive) { + this.mInteractive = interactive; + } + + public Identity getServerIdentity() { + return mServerIdentity; + } + private class Info { public final ArrayList<String> features = new ArrayList<>(); public final ArrayList<Pair<String,String>> identities = new ArrayList<>(); @@ -1087,6 +1452,15 @@ public class XmppConnection implements Runnable { } + public enum Identity { + FACEBOOK, + SLACK, + EJABBERD, + PROSODY, + NIMBUZZ, + UNKNOWN + } + public class Features { XmppConnection connection; private boolean carbonsEnabled = false; @@ -1098,8 +1472,10 @@ public class XmppConnection implements Runnable { } private boolean hasDiscoFeature(final Jid server, final String feature) { - return connection.disco.containsKey(server) && - connection.disco.get(server).features.contains(feature); + synchronized (XmppConnection.this.disco) { + return connection.disco.containsKey(server) && + connection.disco.get(server).features.contains(feature); + } } public boolean carbons() { @@ -1124,13 +1500,15 @@ public class XmppConnection implements Runnable { } public boolean pep() { - final Pair<String,String> needle = new Pair<>("pubsub","pep"); - Info info = disco.get(account.getServer()); - if (info != null && info.identities.contains(needle)) { - return true; - } else { - info = disco.get(account.getJid().toBareJid()); - return info != null && info.identities.contains(needle); + synchronized (XmppConnection.this.disco) { + final Pair<String, String> needle = new Pair<>("pubsub", "pep"); + Info info = disco.get(account.getServer()); + if (info != null && info.identities.contains(needle)) { + return true; + } else { + info = disco.get(account.getJid().toBareJid()); + return info != null && info.identities.contains(needle); + } } } @@ -1143,7 +1521,9 @@ public class XmppConnection implements Runnable { } public boolean advancedStreamFeaturesLoaded() { - return disco.containsKey(account.getServer()); + synchronized (XmppConnection.this.disco) { + return disco.containsKey(account.getServer()); + } } public boolean rosterVersioning() { @@ -1155,7 +1535,7 @@ public class XmppConnection implements Runnable { } public boolean httpUpload() { - return findDiscoItemsByFeature(Xmlns.HTTP_UPLOAD).size() > 0; + return !Config.DISABLE_HTTP_UPLOAD && findDiscoItemsByFeature(Xmlns.HTTP_UPLOAD).size() > 0; } } diff --git a/src/main/java/eu/siacs/conversations/xmpp/chatstate/ChatState.java b/src/main/java/eu/siacs/conversations/xmpp/chatstate/ChatState.java index f85efbdb..3e371562 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/chatstate/ChatState.java +++ b/src/main/java/eu/siacs/conversations/xmpp/chatstate/ChatState.java @@ -4,7 +4,7 @@ import eu.siacs.conversations.xml.Element; public enum ChatState { - ACTIVE, INACTIVE, GONE, COMPOSING, PAUSED, mIncomingChatState; + ACTIVE, INACTIVE, GONE, COMPOSING, PAUSED; public static ChatState parse(Element element) { final String NAMESPACE = "http://jabber.org/protocol/chatstates"; diff --git a/src/main/java/eu/siacs/conversations/xmpp/forms/Data.java b/src/main/java/eu/siacs/conversations/xmpp/forms/Data.java index 44794c80..0053a399 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/forms/Data.java +++ b/src/main/java/eu/siacs/conversations/xmpp/forms/Data.java @@ -53,16 +53,16 @@ public class Data extends Element { public void submit() { this.setAttribute("type","submit"); - removeNonFieldChildren(); + removeUnnecessaryChildren(); for(Field field : getFields()) { field.removeNonValueChildren(); } } - private void removeNonFieldChildren() { + private void removeUnnecessaryChildren() { for(Iterator<Element> iterator = this.children.iterator(); iterator.hasNext();) { Element element = iterator.next(); - if (!element.getName().equals("field")) { + if (!element.getName().equals("field") && !element.getName().equals("title")) { iterator.remove(); } } @@ -76,10 +76,14 @@ public class Data extends Element { } public void setFormType(String formType) { - this.put("FORM_TYPE",formType); + this.put("FORM_TYPE", formType); } public String getFormType() { return this.getAttribute("FORM_TYPE"); } + + public String getTitle() { + return findChildContent("title"); + } } diff --git a/src/main/java/eu/siacs/conversations/xmpp/forms/Field.java b/src/main/java/eu/siacs/conversations/xmpp/forms/Field.java index ee2c51a9..020b34b9 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/forms/Field.java +++ b/src/main/java/eu/siacs/conversations/xmpp/forms/Field.java @@ -1,7 +1,9 @@ package eu.siacs.conversations.xmpp.forms; +import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; +import java.util.List; import eu.siacs.conversations.xml.Element; @@ -16,7 +18,7 @@ public class Field extends Element { super("field"); } - public String getName() { + public String getFieldName() { return this.getAttribute("var"); } @@ -47,4 +49,33 @@ public class Field extends Element { field.setChildren(element.getChildren()); return field; } + + public String getValue() { + return findChildContent("value"); + } + + public List<String> getValues() { + List<String> values = new ArrayList<>(); + for(Element child : getChildren()) { + if ("value".equals(child.getName())) { + String content = child.getContent(); + if (content != null) { + values.add(content); + } + } + } + return values; + } + + public String getLabel() { + return getAttribute("label"); + } + + public String getType() { + return getAttribute("type"); + } + + public boolean isRequired() { + return hasChild("required"); + } } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jid/Jid.java b/src/main/java/eu/siacs/conversations/xmpp/jid/Jid.java index f989c0c2..a15abe14 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jid/Jid.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jid/Jid.java @@ -176,7 +176,7 @@ public final class Jid { return resourcepart.isEmpty() ? this : fromParts(localpart, domainpart, ""); } catch (final InvalidJidException e) { // This should never happen. - return null; + throw new AssertionError("Jid " + this.toString() + " invalid"); } } @@ -185,7 +185,7 @@ public final class Jid { return resourcepart.isEmpty() && localpart.isEmpty() ? this : fromString(getDomainpart()); } catch (final InvalidJidException e) { // This should never happen. - return null; + throw new AssertionError("Jid " + this.toString() + " invalid"); } } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java index f41c6791..11bfdd8d 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java @@ -1,5 +1,13 @@ package eu.siacs.conversations.xmpp.jingle; +import android.content.Intent; +import android.net.Uri; +import android.util.Log; +import android.util.Pair; + +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.io.OutputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; @@ -17,13 +25,19 @@ import de.thedevstack.conversationsplus.ConversationsPlusPreferences; import de.thedevstack.conversationsplus.utils.MessageUtil; import eu.siacs.conversations.Config; +import eu.siacs.conversations.crypto.axolotl.AxolotlService; +import eu.siacs.conversations.crypto.axolotl.OnMessageCreatedCallback; +import eu.siacs.conversations.crypto.axolotl.XmppAxolotlMessage; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.entities.DownloadableFile; import eu.siacs.conversations.entities.TransferablePlaceholder; import eu.siacs.conversations.entities.Message; +import eu.siacs.conversations.entities.Transferable; +import eu.siacs.conversations.entities.TransferablePlaceholder; import eu.siacs.conversations.persistance.FileBackend; +import eu.siacs.conversations.services.AbstractConnectionManager; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xmpp.OnIqPacketReceived; @@ -64,20 +78,24 @@ public class JingleConnection implements Transferable { private String contentCreator; private int mProgress = 0; - private long mLastGuiRefresh = 0; private boolean receivedCandidate = false; private boolean sentCandidate = false; private boolean acceptedAutomatically = false; + private XmppAxolotlMessage mXmppAxolotlMessage; + private JingleTransport transport = null; + private OutputStream mFileOutputStream; + private InputStream mFileInputStream; + private OnIqPacketReceived responseListener = new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.ERROR) { + if (packet.getType() != IqPacket.TYPE.RESULT) { fail(); } } @@ -89,15 +107,13 @@ public class JingleConnection implements Transferable { public void onFileTransmitted(DownloadableFile file) { if (responder.equals(account.getJid())) { sendSuccess(); + MessageUtil.updateFileParams(message); + mXmppConnectionService.databaseBackend.createMessage(message); + mXmppConnectionService.markMessage(message,Message.STATUS_RECEIVED); if (acceptedAutomatically) { message.markUnread(); - JingleConnection.this.mXmppConnectionService - .getNotificationService().push(message); + JingleConnection.this.mXmppConnectionService.getNotificationService().push(message); } - MessageUtil.updateFileParams(message); - mXmppConnectionService.databaseBackend.createMessage(message); - mXmppConnectionService.markMessage(message, - Message.STATUS_RECEIVED); } else { if (message.getEncryption() == Message.ENCRYPTION_PGP || message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { file.delete(); @@ -108,6 +124,8 @@ public class JingleConnection implements Transferable { Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); intent.setData(Uri.fromFile(file)); mXmppConnectionService.sendBroadcast(intent); + } else { + account.getPgpDecryptionService().add(message); } } @@ -118,6 +136,14 @@ public class JingleConnection implements Transferable { } }; + public InputStream getFileInputStream() { + return this.mFileInputStream; + } + + public OutputStream getFileOutputStream() { + return this.mFileOutputStream; + } + private OnProxyActivated onProxyActivated = new OnProxyActivated() { @Override @@ -199,7 +225,26 @@ public class JingleConnection implements Transferable { mXmppConnectionService.sendIqPacket(account,response,null); } - public void init(Message message) { + public void init(final Message message) { + if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) { + Conversation conversation = message.getConversation(); + conversation.getAccount().getAxolotlService().prepareKeyTransportMessage(conversation.getContact(), new OnMessageCreatedCallback() { + @Override + public void run(XmppAxolotlMessage xmppAxolotlMessage) { + if (xmppAxolotlMessage != null) { + init(message, xmppAxolotlMessage); + } else { + fail(); + } + } + }); + } else { + init(message, null); + } + } + + private void init(Message message, XmppAxolotlMessage xmppAxolotlMessage) { + this.mXmppAxolotlMessage = xmppAxolotlMessage; this.contentCreator = "initiator"; this.contentName = this.mJingleConnectionManager.nextRandomId(); this.message = message; @@ -272,13 +317,16 @@ public class JingleConnection implements Transferable { this.contentCreator = content.getAttribute("creator"); this.contentName = content.getAttribute("name"); this.transportId = content.getTransportId(); - this.mergeCandidates(JingleCandidate.parse(content.socks5transport() - .getChildren())); + this.mergeCandidates(JingleCandidate.parse(content.socks5transport().getChildren())); this.fileOffer = packet.getJingleContent().getFileOffer(); mXmppConnectionService.sendIqPacket(account,packet.generateResponse(IqPacket.TYPE.RESULT),null); if (fileOffer != null) { + Element encrypted = fileOffer.findChild("encrypted", AxolotlService.PEP_PREFIX); + if (encrypted != null) { + this.mXmppAxolotlMessage = XmppAxolotlMessage.fromElement(encrypted, packet.getFrom().toBareJid()); + } Element fileSize = fileOffer.findChild("size"); Element fileNameElement = fileOffer.findChild("name"); if (fileNameElement != null) { @@ -324,10 +372,9 @@ public class JingleConnection implements Transferable { message.setBody(Long.toString(size)); conversation.add(message); mXmppConnectionService.updateConversationUi(); - if (size <= ConversationsPlusPreferences.autoAcceptFileSize() - && mXmppConnectionService.isDownloadAllowedInConnection()) { - Logging.d(Config.LOGTAG, "auto accepting file from " - + packet.getFrom()); + if (mJingleConnectionManager.hasStoragePermission() + && size < this.mJingleConnectionManager.getAutoAcceptFileSize()) { + Logging.d(Config.LOGTAG, "auto accepting file from "+ packet.getFrom()); this.acceptedAutomatically = true; this.sendAccept(); } else { @@ -337,21 +384,36 @@ public class JingleConnection implements Transferable { + size + " allowed size:" + ConversationsPlusPreferences.autoAcceptFileSize()); - this.mXmppConnectionService.getNotificationService() - .push(message); + this.mXmppConnectionService.getNotificationService().push(message); } this.file = FileBackend.getFile(message, false); - if (message.getEncryption() == Message.ENCRYPTION_OTR) { + if (mXmppAxolotlMessage != null) { + XmppAxolotlMessage.XmppAxolotlKeyTransportMessage transportMessage = account.getAxolotlService().processReceivingKeyTransportMessage(mXmppAxolotlMessage); + if (transportMessage != null) { + message.setEncryption(Message.ENCRYPTION_AXOLOTL); + this.file.setKey(transportMessage.getKey()); + this.file.setIv(transportMessage.getIv()); + message.setAxolotlFingerprint(transportMessage.getFingerprint()); + } else { + Logging.d(Config.LOGTAG,"could not process KeyTransportMessage"); + } + } else if (message.getEncryption() == Message.ENCRYPTION_OTR) { byte[] key = conversation.getSymmetricKey(); if (key == null) { this.sendCancel(); this.fail(); return; } else { - this.file.setKey(key); + this.file.setKeyAndIv(key); } } - this.file.setExpectedSize(size); + this.mFileOutputStream = AbstractConnectionManager.createOutputStream(this.file,message.getEncryption() == Message.ENCRYPTION_AXOLOTL); + if (message.getEncryption() == Message.ENCRYPTION_OTR && Config.REPORT_WRONG_FILESIZE_IN_OTR_JINGLE) { + this.file.setExpectedSize((size / 16 + 1) * 16); + } else { + this.file.setExpectedSize(size); + } + Logging.d(Config.LOGTAG, "receiving file: expecting size of " + this.file.getExpectedSize()); } else { this.sendCancel(); this.fail(); @@ -368,17 +430,34 @@ public class JingleConnection implements Transferable { if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) { content.setTransportId(this.transportId); this.file = FileBackend.getFile(message, false); - if (message.getEncryption() == Message.ENCRYPTION_OTR) { - Conversation conversation = this.message.getConversation(); - if (!this.mXmppConnectionService.renewSymmetricKey(conversation)) { - Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": could not set symmetric key"); - cancel(); + Pair<InputStream,Integer> pair; + try { + if (message.getEncryption() == Message.ENCRYPTION_OTR) { + Conversation conversation = this.message.getConversation(); + if (!this.mXmppConnectionService.renewSymmetricKey(conversation)) { + Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not set symmetric key"); + cancel(); + } + this.file.setKeyAndIv(conversation.getSymmetricKey()); + pair = AbstractConnectionManager.createInputStream(this.file, false); + this.file.setExpectedSize(pair.second); + content.setFileOffer(this.file, true); + } else if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) { + this.file.setKey(mXmppAxolotlMessage.getInnerKey()); + this.file.setIv(mXmppAxolotlMessage.getIV()); + pair = AbstractConnectionManager.createInputStream(this.file, true); + this.file.setExpectedSize(pair.second); + content.setFileOffer(this.file, false).addChild(mXmppAxolotlMessage.toElement()); + } else { + pair = AbstractConnectionManager.createInputStream(this.file, false); + this.file.setExpectedSize(pair.second); + content.setFileOffer(this.file, false); } - content.setFileOffer(this.file, true); - this.file.setKey(conversation.getSymmetricKey()); - } else { - content.setFileOffer(this.file, false); + } catch (FileNotFoundException e) { + cancel(); + return; } + this.mFileInputStream = pair.first; this.transportId = this.mJingleConnectionManager.nextRandomId(); content.setTransportId(this.transportId); content.socks5transport().setChildren(getCandidatesAsElements()); @@ -387,7 +466,7 @@ public class JingleConnection implements Transferable { @Override public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() != IqPacket.TYPE.ERROR) { + if (packet.getType() == IqPacket.TYPE.RESULT) { Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": other party received offer"); mJingleStatus = JINGLE_STATUS_INITIATED; mXmppConnectionService.markMessage(message, Message.STATUS_OFFERED); @@ -572,12 +651,11 @@ public class JingleConnection implements Transferable { @Override public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.ERROR) { + if (packet.getType() != IqPacket.TYPE.RESULT) { onProxyActivated.failed(); } else { onProxyActivated.success(); - sendProxyActivated(connection - .getCandidate().getCid()); + sendProxyActivated(connection.getCandidate().getCid()); } } }); @@ -676,8 +754,7 @@ public class JingleConnection implements Transferable { JinglePacket answer = bootstrapPacket("transport-accept"); Content content = new Content("initiator", "a-file-offer"); content.setTransportId(this.transportId); - content.ibbTransport().setAttribute("block-size", - Integer.toString(this.ibbBlockSize)); + content.ibbTransport().setAttribute("block-size",this.ibbBlockSize); answer.setContent(content); this.sendJinglePacket(answer); return true; @@ -750,6 +827,8 @@ public class JingleConnection implements Transferable { if (this.transport != null && this.transport instanceof JingleInbandTransport) { this.transport.disconnect(); } + StreamUtil.close(mFileInputStream); + StreamUtil.close(mFileOutputStream); if (this.message != null) { if (this.responder.equals(account.getJid())) { this.message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_FAILED)); @@ -903,10 +982,7 @@ public class JingleConnection implements Transferable { public void updateProgress(int i) { this.mProgress = i; - if (SystemClock.elapsedRealtime() - this.mLastGuiRefresh > Config.PROGRESS_UI_UPDATE_INTERVAL) { - this.mLastGuiRefresh = SystemClock.elapsedRealtime(); - mXmppConnectionService.updateConversationUi(); - } + mXmppConnectionService.updateConversationUi(); } interface OnProxyActivated { @@ -958,4 +1034,8 @@ public class JingleConnection implements Transferable { public int getProgress() { return this.mProgress; } + + public AbstractConnectionManager getConnectionManager() { + return this.mJingleConnectionManager; + } } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java index b1146543..3be1276d 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java @@ -1,5 +1,8 @@ package eu.siacs.conversations.xmpp.jingle; +import android.annotation.SuppressLint; +import android.util.Log; + import java.math.BigInteger; import java.security.SecureRandom; import java.util.HashMap; @@ -13,6 +16,7 @@ import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.entities.Message; +import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.services.AbstractConnectionManager; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.utils.Xmlns; @@ -83,7 +87,7 @@ public class JingleConnectionManager extends AbstractConnectionManager { public void getPrimaryCandidate(Account account, final OnPrimaryCandidateFound listener) { - if (Config.NO_PROXY_LOOKUP) { + if (Config.DISABLE_PROXY_LOOKUP) { listener.onPrimaryCandidateFound(false, null); return; } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java index b200c4e0..d1cd20fa 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java @@ -1,5 +1,8 @@ package eu.siacs.conversations.xmpp.jingle; +import android.util.Base64; +import android.util.Log; + import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -76,7 +79,7 @@ public class JingleInbandTransport extends JingleTransport { @Override public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.ERROR) { + if (packet.getType() != IqPacket.TYPE.RESULT) { callback.failed(); } else { callback.established(); @@ -95,7 +98,7 @@ public class JingleInbandTransport extends JingleTransport { digest.reset(); file.getParentFile().mkdirs(); file.createNewFile(); - this.fileOutputStream = file.createOutputStream(); + this.fileOutputStream = connection.getFileOutputStream(); if (this.fileOutputStream == null) { Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": could not create output stream"); callback.onFileTransferAborted(); @@ -114,15 +117,11 @@ public class JingleInbandTransport extends JingleTransport { this.onFileTransmissionStatusChanged = callback; this.file = file; try { - if (this.file.getKey() != null) { - this.remainingSize = (this.file.getSize() / 16 + 1) * 16; - } else { - this.remainingSize = this.file.getSize(); - } + this.remainingSize = this.file.getExpectedSize(); this.fileSize = this.remainingSize; this.digest = MessageDigest.getInstance("SHA-1"); this.digest.reset(); - fileInputStream = this.file.createInputStream(); + fileInputStream = connection.getFileInputStream(); if (fileInputStream == null) { Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": could no create input stream"); callback.onFileTransferAborted(); @@ -182,6 +181,7 @@ public class JingleInbandTransport extends JingleTransport { data.setAttribute("sid", this.sessionId); data.setContent(base64); this.account.getXmppConnection().sendIqPacket(iq, this.onAckReceived); + this.account.getXmppConnection().r(); //don't fill up stanza queue too much this.seq++; if (this.remainingSize > 0) { connection.updateProgress((int) ((((double) (this.fileSize - this.remainingSize)) / this.fileSize) * 100)); diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java index 41cf4d96..c4c96ea2 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java @@ -1,10 +1,15 @@ package eu.siacs.conversations.xmpp.jingle; +import android.os.PowerManager; +import android.util.Log; + import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.net.InetAddress; import java.net.InetSocketAddress; +import java.net.Proxy; import java.net.Socket; import java.net.SocketAddress; import java.net.UnknownHostException; @@ -19,6 +24,7 @@ import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.DownloadableFile; import eu.siacs.conversations.persistance.FileBackend; import eu.siacs.conversations.utils.CryptoHelper; +import eu.siacs.conversations.utils.SocksSocketFactory; public class JingleSocks5Transport extends JingleTransport { private JingleCandidate candidate; @@ -59,34 +65,19 @@ public class JingleSocks5Transport extends JingleTransport { @Override public void run() { try { - socket = new Socket(); - SocketAddress address = new InetSocketAddress(candidate.getHost(),candidate.getPort()); - socket.connect(address,Config.SOCKET_TIMEOUT * 1000); - inputStream = socket.getInputStream(); - outputStream = socket.getOutputStream(); - byte[] login = { 0x05, 0x01, 0x00 }; - byte[] expectedReply = { 0x05, 0x00 }; - byte[] reply = new byte[2]; - outputStream.write(login); - inputStream.read(reply); - final String connect = Character.toString('\u0005') - + '\u0001' + '\u0000' + '\u0003' + '\u0028' - + destination + '\u0000' + '\u0000'; - if (Arrays.equals(reply, expectedReply)) { - outputStream.write(connect.getBytes()); - byte[] result = new byte[2]; - inputStream.read(result); - int status = result[1]; - if (status == 0) { - isEstablished = true; - callback.established(); - } else { - callback.failed(); - } + final boolean useTor = connection.getAccount().isOnion() || connection.getConnectionManager().getXmppConnectionService().useTorToConnect(); + if (useTor) { + socket = SocksSocketFactory.createSocketOverTor(candidate.getHost(),candidate.getPort()); } else { - socket.close(); - callback.failed(); + socket = new Socket(); + SocketAddress address = new InetSocketAddress(candidate.getHost(),candidate.getPort()); + socket.connect(address,Config.SOCKET_TIMEOUT * 1000); } + inputStream = socket.getInputStream(); + outputStream = socket.getOutputStream(); + SocksSocketFactory.createSocksConnection(socket,destination,0); + isEstablished = true; + callback.established(); } catch (UnknownHostException e) { callback.failed(); } catch (IOException e) { @@ -97,23 +88,24 @@ public class JingleSocks5Transport extends JingleTransport { } - public void send(final DownloadableFile file, - final OnFileTransmissionStatusChanged callback) { + public void send(final DownloadableFile file, final OnFileTransmissionStatusChanged callback) { new Thread(new Runnable() { @Override public void run() { InputStream fileInputStream = null; + final PowerManager.WakeLock wakeLock = connection.getConnectionManager().createWakeLock("jingle_send_"+connection.getSessionId()); try { + wakeLock.acquire(); MessageDigest digest = MessageDigest.getInstance("SHA-1"); digest.reset(); - fileInputStream = file.createInputStream(); + fileInputStream = connection.getFileInputStream(); if (fileInputStream == null) { Logging.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": could not create input stream"); callback.onFileTransferAborted(); return; } - long size = file.getSize(); + long size = file.getExpectedSize(); long transmitted = 0; int count; byte[] buffer = new byte[8192]; @@ -139,6 +131,7 @@ public class JingleSocks5Transport extends JingleTransport { callback.onFileTransferAborted(); } finally { StreamUtil.close(fileInputStream); + wakeLock.release(); } } }).start(); @@ -151,14 +144,16 @@ public class JingleSocks5Transport extends JingleTransport { @Override public void run() { OutputStream fileOutputStream = null; + final PowerManager.WakeLock wakeLock = connection.getConnectionManager().createWakeLock("jingle_receive_"+connection.getSessionId()); try { + wakeLock.acquire(); MessageDigest digest = MessageDigest.getInstance("SHA-1"); digest.reset(); - inputStream.skip(45); + //inputStream.skip(45); socket.setSoTimeout(30000); file.getParentFile().mkdirs(); file.createNewFile(); - fileOutputStream = file.createOutputStream(); + fileOutputStream = connection.getFileOutputStream(); if (fileOutputStream == null) { callback.onFileTransferAborted(); Logging.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": could not create output stream"); @@ -167,7 +162,7 @@ public class JingleSocks5Transport extends JingleTransport { double size = file.getExpectedSize(); long remainingSize = file.getExpectedSize(); byte[] buffer = new byte[8192]; - int count = buffer.length; + int count; while (remainingSize > 0) { count = inputStream.read(buffer); if (count == -1) { @@ -195,7 +190,9 @@ public class JingleSocks5Transport extends JingleTransport { Logging.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage()); callback.onFileTransferAborted(); } finally { + wakeLock.release(); StreamUtil.close(fileOutputStream); + StreamUtil.close(inputStream); } } }).start(); @@ -210,27 +207,9 @@ public class JingleSocks5Transport extends JingleTransport { } public void disconnect() { - if (this.outputStream != null) { - try { - this.outputStream.close(); - } catch (IOException e) { - - } - } - if (this.inputStream != null) { - try { - this.inputStream.close(); - } catch (IOException e) { - - } - } - if (this.socket != null) { - try { - this.socket.close(); - } catch (IOException e) { - - } - } + StreamUtil.close(inputStream); + StreamUtil.close(outputStream); + StreamUtil.close(socket); } public boolean isEstablished() { diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/OnFileTransmissionStatusChanged.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/OnFileTransmissionStatusChanged.java index e45e7441..91cba39f 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/OnFileTransmissionStatusChanged.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/OnFileTransmissionStatusChanged.java @@ -3,7 +3,7 @@ package eu.siacs.conversations.xmpp.jingle; import eu.siacs.conversations.entities.DownloadableFile; public interface OnFileTransmissionStatusChanged { - public void onFileTransmitted(DownloadableFile file); + void onFileTransmitted(DownloadableFile file); - public void onFileTransferAborted(); + void onFileTransferAborted(); } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/OnJinglePacketReceived.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/OnJinglePacketReceived.java index 2aaf62a1..9a60b392 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/OnJinglePacketReceived.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/OnJinglePacketReceived.java @@ -5,5 +5,5 @@ import eu.siacs.conversations.xmpp.PacketReceived; import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket; public interface OnJinglePacketReceived extends PacketReceived { - public void onJinglePacketReceived(Account account, JinglePacket packet); + void onJinglePacketReceived(Account account, JinglePacket packet); } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/OnPrimaryCandidateFound.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/OnPrimaryCandidateFound.java index 03a437b2..76e33717 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/OnPrimaryCandidateFound.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/OnPrimaryCandidateFound.java @@ -1,6 +1,5 @@ package eu.siacs.conversations.xmpp.jingle; public interface OnPrimaryCandidateFound { - public void onPrimaryCandidateFound(boolean success, - JingleCandidate canditate); + void onPrimaryCandidateFound(boolean success, JingleCandidate canditate); } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java index bcadbe77..f752cc5d 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java @@ -25,17 +25,18 @@ public class Content extends Element { this.transportId = sid; } - public void setFileOffer(DownloadableFile actualFile, boolean otr) { + public Element setFileOffer(DownloadableFile actualFile, boolean otr) { Element description = this.addChild("description", "urn:xmpp:jingle:apps:file-transfer:3"); Element offer = description.addChild("offer"); Element file = offer.addChild("file"); - file.addChild("size").setContent(Long.toString(actualFile.getSize())); + file.addChild("size").setContent(Long.toString(actualFile.getExpectedSize())); if (otr) { file.addChild("name").setContent(actualFile.getName() + ".otr"); } else { file.addChild("name").setContent(actualFile.getName()); } + return file; } public Element getFileOffer() { diff --git a/src/main/java/eu/siacs/conversations/xmpp/pep/Avatar.java b/src/main/java/eu/siacs/conversations/xmpp/pep/Avatar.java index 74da6a9b..38bb5c8f 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/pep/Avatar.java +++ b/src/main/java/eu/siacs/conversations/xmpp/pep/Avatar.java @@ -1,10 +1,10 @@ package eu.siacs.conversations.xmpp.pep; +import android.util.Base64; + import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xmpp.jid.Jid; -import android.util.Base64; - public class Avatar { public enum Origin { PEP, VCARD }; diff --git a/src/main/java/eu/siacs/conversations/xmpp/stanzas/AbstractAcknowledgeableStanza.java b/src/main/java/eu/siacs/conversations/xmpp/stanzas/AbstractAcknowledgeableStanza.java new file mode 100644 index 00000000..fa5e6fbd --- /dev/null +++ b/src/main/java/eu/siacs/conversations/xmpp/stanzas/AbstractAcknowledgeableStanza.java @@ -0,0 +1,31 @@ +package eu.siacs.conversations.xmpp.stanzas; + +import eu.siacs.conversations.xml.Element; + +abstract public class AbstractAcknowledgeableStanza extends AbstractStanza { + + protected AbstractAcknowledgeableStanza(String name) { + super(name); + } + + + public String getId() { + return this.getAttribute("id"); + } + + public void setId(final String id) { + setAttribute("id", id); + } + + public Element getError() { + Element error = findChild("error"); + if (error != null) { + for(Element element : error.getChildren()) { + if (!element.getName().equals("text")) { + return element; + } + } + } + return null; + } +} diff --git a/src/main/java/eu/siacs/conversations/xmpp/stanzas/AbstractStanza.java b/src/main/java/eu/siacs/conversations/xmpp/stanzas/AbstractStanza.java index bd706b57..a6144df2 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/stanzas/AbstractStanza.java +++ b/src/main/java/eu/siacs/conversations/xmpp/stanzas/AbstractStanza.java @@ -18,10 +18,6 @@ public class AbstractStanza extends Element { return getAttributeAsJid("from"); } - public String getId() { - return this.getAttribute("id"); - } - public void setTo(final Jid to) { if (to != null) { setAttribute("to", to.toString()); @@ -34,10 +30,6 @@ public class AbstractStanza extends Element { } } - public void setId(final String id) { - setAttribute("id", id); - } - public boolean fromServer(final Account account) { return getFrom() == null || getFrom().equals(account.getServer()) diff --git a/src/main/java/eu/siacs/conversations/xmpp/stanzas/IqPacket.java b/src/main/java/eu/siacs/conversations/xmpp/stanzas/IqPacket.java index 7b36fc49..302dc78e 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/stanzas/IqPacket.java +++ b/src/main/java/eu/siacs/conversations/xmpp/stanzas/IqPacket.java @@ -2,14 +2,15 @@ package eu.siacs.conversations.xmpp.stanzas; import eu.siacs.conversations.xml.Element; -public class IqPacket extends AbstractStanza { +public class IqPacket extends AbstractAcknowledgeableStanza { - public static enum TYPE { + public enum TYPE { ERROR, SET, RESULT, GET, - INVALID + INVALID, + TIMEOUT } public IqPacket(final TYPE type) { @@ -39,6 +40,9 @@ public class IqPacket extends AbstractStanza { public TYPE getType() { final String type = getAttribute("type"); + if (type == null) { + return TYPE.INVALID; + } switch (type) { case "error": return TYPE.ERROR; @@ -48,6 +52,8 @@ public class IqPacket extends AbstractStanza { return TYPE.SET; case "get": return TYPE.GET; + case "timeout": + return TYPE.TIMEOUT; default: return TYPE.INVALID; } diff --git a/src/main/java/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java b/src/main/java/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java index e32811af..e7539c62 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java +++ b/src/main/java/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java @@ -7,7 +7,7 @@ import java.text.ParseException; import eu.siacs.conversations.parser.AbstractParser; import eu.siacs.conversations.xml.Element; -public class MessagePacket extends AbstractStanza { +public class MessagePacket extends AbstractAcknowledgeableStanza { public static final int TYPE_CHAT = 0; public static final int TYPE_NORMAL = 2; public static final int TYPE_GROUPCHAT = 3; @@ -29,6 +29,11 @@ public class MessagePacket extends AbstractStanza { this.children.add(0, body); } + public void setAxolotlMessage(Element axolotlMessage) { + this.children.remove(findChild("body")); + this.children.add(0, axolotlMessage); + } + public void setType(int type) { switch (type) { case TYPE_CHAT: diff --git a/src/main/java/eu/siacs/conversations/xmpp/stanzas/PresencePacket.java b/src/main/java/eu/siacs/conversations/xmpp/stanzas/PresencePacket.java index 7ea32099..c321498d 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/stanzas/PresencePacket.java +++ b/src/main/java/eu/siacs/conversations/xmpp/stanzas/PresencePacket.java @@ -1,6 +1,6 @@ package eu.siacs.conversations.xmpp.stanzas; -public class PresencePacket extends AbstractStanza { +public class PresencePacket extends AbstractAcknowledgeableStanza { public PresencePacket() { super("presence"); diff --git a/src/main/res/drawable-hdpi/ic_action_done.png b/src/main/res/drawable-hdpi/ic_action_done.png Binary files differnew file mode 100644 index 00000000..58bf9721 --- /dev/null +++ b/src/main/res/drawable-hdpi/ic_action_done.png diff --git a/src/main/res/drawable-hdpi/ic_done_black_24dp.png b/src/main/res/drawable-hdpi/ic_done_black_24dp.png Binary files differnew file mode 100644 index 00000000..d4c06072 --- /dev/null +++ b/src/main/res/drawable-hdpi/ic_done_black_24dp.png diff --git a/src/main/res/drawable-hdpi/ic_link_white_24dp.png b/src/main/res/drawable-hdpi/ic_link_white_24dp.png Binary files differnew file mode 100644 index 00000000..4186e00c --- /dev/null +++ b/src/main/res/drawable-hdpi/ic_link_white_24dp.png diff --git a/src/main/res/drawable-hdpi/ic_notifications_grey600_24dp.png b/src/main/res/drawable-hdpi/ic_notifications_grey600_24dp.png Binary files differnew file mode 100644 index 00000000..96b329c4 --- /dev/null +++ b/src/main/res/drawable-hdpi/ic_notifications_grey600_24dp.png diff --git a/src/main/res/drawable-hdpi/ic_notifications_none_grey600_24dp.png b/src/main/res/drawable-hdpi/ic_notifications_none_grey600_24dp.png Binary files differnew file mode 100644 index 00000000..6cd4dfc9 --- /dev/null +++ b/src/main/res/drawable-hdpi/ic_notifications_none_grey600_24dp.png diff --git a/src/main/res/drawable-hdpi/ic_notifications_off_grey600_24dp.png b/src/main/res/drawable-hdpi/ic_notifications_off_grey600_24dp.png Binary files differnew file mode 100644 index 00000000..09ebc5d2 --- /dev/null +++ b/src/main/res/drawable-hdpi/ic_notifications_off_grey600_24dp.png diff --git a/src/main/res/drawable-hdpi/ic_notifications_paused_grey600_24dp.png b/src/main/res/drawable-hdpi/ic_notifications_paused_grey600_24dp.png Binary files differnew file mode 100644 index 00000000..e92d43ac --- /dev/null +++ b/src/main/res/drawable-hdpi/ic_notifications_paused_grey600_24dp.png diff --git a/src/main/res/drawable-hdpi/ic_refresh_grey600_24dp.png b/src/main/res/drawable-hdpi/ic_refresh_grey600_24dp.png Binary files differnew file mode 100644 index 00000000..51cc4dbd --- /dev/null +++ b/src/main/res/drawable-hdpi/ic_refresh_grey600_24dp.png diff --git a/src/main/res/drawable-hdpi/ic_secure_indicator.png b/src/main/res/drawable-hdpi/ic_secure_indicator.png Binary files differindex 2a2934fb..220463fc 100644 --- a/src/main/res/drawable-hdpi/ic_secure_indicator.png +++ b/src/main/res/drawable-hdpi/ic_secure_indicator.png diff --git a/src/main/res/drawable-hdpi/ic_secure_indicator_white.png b/src/main/res/drawable-hdpi/ic_secure_indicator_white.png Binary files differnew file mode 100644 index 00000000..46eb1195 --- /dev/null +++ b/src/main/res/drawable-hdpi/ic_secure_indicator_white.png diff --git a/src/main/res/drawable-hdpi/message_bubble_received.9.png b/src/main/res/drawable-hdpi/message_bubble_received.9.png Binary files differnew file mode 100644 index 00000000..dfd857cb --- /dev/null +++ b/src/main/res/drawable-hdpi/message_bubble_received.9.png diff --git a/src/main/res/drawable-hdpi/message_bubble_received_warning.9.png b/src/main/res/drawable-hdpi/message_bubble_received_warning.9.png Binary files differnew file mode 100644 index 00000000..fd07bc20 --- /dev/null +++ b/src/main/res/drawable-hdpi/message_bubble_received_warning.9.png diff --git a/src/main/res/drawable-hdpi/message_bubble_received_white.9.png b/src/main/res/drawable-hdpi/message_bubble_received_white.9.png Binary files differnew file mode 100644 index 00000000..bec20798 --- /dev/null +++ b/src/main/res/drawable-hdpi/message_bubble_received_white.9.png diff --git a/src/main/res/drawable-hdpi/message_bubble_sent.9.png b/src/main/res/drawable-hdpi/message_bubble_sent.9.png Binary files differnew file mode 100644 index 00000000..10dc2e29 --- /dev/null +++ b/src/main/res/drawable-hdpi/message_bubble_sent.9.png diff --git a/src/main/res/drawable-hdpi/switch_thumb_disable.png b/src/main/res/drawable-hdpi/switch_thumb_disable.png Binary files differnew file mode 100644 index 00000000..1e9b151b --- /dev/null +++ b/src/main/res/drawable-hdpi/switch_thumb_disable.png diff --git a/src/main/res/drawable-hdpi/switch_thumb_off_normal.png b/src/main/res/drawable-hdpi/switch_thumb_off_normal.png Binary files differnew file mode 100644 index 00000000..b7c1fc11 --- /dev/null +++ b/src/main/res/drawable-hdpi/switch_thumb_off_normal.png diff --git a/src/main/res/drawable-hdpi/switch_thumb_off_pressed.png b/src/main/res/drawable-hdpi/switch_thumb_off_pressed.png Binary files differnew file mode 100644 index 00000000..ca6e4909 --- /dev/null +++ b/src/main/res/drawable-hdpi/switch_thumb_off_pressed.png diff --git a/src/main/res/drawable-hdpi/switch_thumb_on_normal.png b/src/main/res/drawable-hdpi/switch_thumb_on_normal.png Binary files differnew file mode 100644 index 00000000..cbcda5d4 --- /dev/null +++ b/src/main/res/drawable-hdpi/switch_thumb_on_normal.png diff --git a/src/main/res/drawable-hdpi/switch_thumb_on_pressed.png b/src/main/res/drawable-hdpi/switch_thumb_on_pressed.png Binary files differnew file mode 100644 index 00000000..234b12dc --- /dev/null +++ b/src/main/res/drawable-hdpi/switch_thumb_on_pressed.png diff --git a/src/main/res/drawable-mdpi/ic_action_done.png b/src/main/res/drawable-mdpi/ic_action_done.png Binary files differnew file mode 100644 index 00000000..cf5fab3a --- /dev/null +++ b/src/main/res/drawable-mdpi/ic_action_done.png diff --git a/src/main/res/drawable-mdpi/ic_done_black_24dp.png b/src/main/res/drawable-mdpi/ic_done_black_24dp.png Binary files differnew file mode 100644 index 00000000..5e5e7cf2 --- /dev/null +++ b/src/main/res/drawable-mdpi/ic_done_black_24dp.png diff --git a/src/main/res/drawable-mdpi/ic_link_white_24dp.png b/src/main/res/drawable-mdpi/ic_link_white_24dp.png Binary files differnew file mode 100644 index 00000000..6960502e --- /dev/null +++ b/src/main/res/drawable-mdpi/ic_link_white_24dp.png diff --git a/src/main/res/drawable-mdpi/ic_notifications_grey600_24dp.png b/src/main/res/drawable-mdpi/ic_notifications_grey600_24dp.png Binary files differnew file mode 100644 index 00000000..d6c20c20 --- /dev/null +++ b/src/main/res/drawable-mdpi/ic_notifications_grey600_24dp.png diff --git a/src/main/res/drawable-mdpi/ic_notifications_none_grey600_24dp.png b/src/main/res/drawable-mdpi/ic_notifications_none_grey600_24dp.png Binary files differnew file mode 100644 index 00000000..3e8b0805 --- /dev/null +++ b/src/main/res/drawable-mdpi/ic_notifications_none_grey600_24dp.png diff --git a/src/main/res/drawable-mdpi/ic_notifications_off_grey600_24dp.png b/src/main/res/drawable-mdpi/ic_notifications_off_grey600_24dp.png Binary files differnew file mode 100644 index 00000000..af3b6321 --- /dev/null +++ b/src/main/res/drawable-mdpi/ic_notifications_off_grey600_24dp.png diff --git a/src/main/res/drawable-mdpi/ic_notifications_paused_grey600_24dp.png b/src/main/res/drawable-mdpi/ic_notifications_paused_grey600_24dp.png Binary files differnew file mode 100644 index 00000000..9d6308d2 --- /dev/null +++ b/src/main/res/drawable-mdpi/ic_notifications_paused_grey600_24dp.png diff --git a/src/main/res/drawable-mdpi/ic_refresh_grey600_24dp.png b/src/main/res/drawable-mdpi/ic_refresh_grey600_24dp.png Binary files differnew file mode 100644 index 00000000..c136c59f --- /dev/null +++ b/src/main/res/drawable-mdpi/ic_refresh_grey600_24dp.png diff --git a/src/main/res/drawable-mdpi/ic_secure_indicator.png b/src/main/res/drawable-mdpi/ic_secure_indicator.png Binary files differindex 5a73aef4..690d4d03 100644 --- a/src/main/res/drawable-mdpi/ic_secure_indicator.png +++ b/src/main/res/drawable-mdpi/ic_secure_indicator.png diff --git a/src/main/res/drawable-mdpi/ic_secure_indicator_white.png b/src/main/res/drawable-mdpi/ic_secure_indicator_white.png Binary files differnew file mode 100644 index 00000000..e2f894ef --- /dev/null +++ b/src/main/res/drawable-mdpi/ic_secure_indicator_white.png diff --git a/src/main/res/drawable-mdpi/message_bubble_received.9.png b/src/main/res/drawable-mdpi/message_bubble_received.9.png Binary files differnew file mode 100644 index 00000000..9835a736 --- /dev/null +++ b/src/main/res/drawable-mdpi/message_bubble_received.9.png diff --git a/src/main/res/drawable-mdpi/message_bubble_received_warning.9.png b/src/main/res/drawable-mdpi/message_bubble_received_warning.9.png Binary files differnew file mode 100644 index 00000000..ff8f80b6 --- /dev/null +++ b/src/main/res/drawable-mdpi/message_bubble_received_warning.9.png diff --git a/src/main/res/drawable-mdpi/message_bubble_received_white.9.png b/src/main/res/drawable-mdpi/message_bubble_received_white.9.png Binary files differnew file mode 100644 index 00000000..d7a3bb5d --- /dev/null +++ b/src/main/res/drawable-mdpi/message_bubble_received_white.9.png diff --git a/src/main/res/drawable-mdpi/message_bubble_sent.9.png b/src/main/res/drawable-mdpi/message_bubble_sent.9.png Binary files differnew file mode 100644 index 00000000..596699bb --- /dev/null +++ b/src/main/res/drawable-mdpi/message_bubble_sent.9.png diff --git a/src/main/res/drawable-mdpi/switch_thumb_disable.png b/src/main/res/drawable-mdpi/switch_thumb_disable.png Binary files differnew file mode 100644 index 00000000..968de345 --- /dev/null +++ b/src/main/res/drawable-mdpi/switch_thumb_disable.png diff --git a/src/main/res/drawable-mdpi/switch_thumb_off_normal.png b/src/main/res/drawable-mdpi/switch_thumb_off_normal.png Binary files differnew file mode 100644 index 00000000..51fb4d7a --- /dev/null +++ b/src/main/res/drawable-mdpi/switch_thumb_off_normal.png diff --git a/src/main/res/drawable-mdpi/switch_thumb_off_pressed.png b/src/main/res/drawable-mdpi/switch_thumb_off_pressed.png Binary files differnew file mode 100644 index 00000000..ca788445 --- /dev/null +++ b/src/main/res/drawable-mdpi/switch_thumb_off_pressed.png diff --git a/src/main/res/drawable-mdpi/switch_thumb_on_normal.png b/src/main/res/drawable-mdpi/switch_thumb_on_normal.png Binary files differnew file mode 100644 index 00000000..6a93d5f7 --- /dev/null +++ b/src/main/res/drawable-mdpi/switch_thumb_on_normal.png diff --git a/src/main/res/drawable-mdpi/switch_thumb_on_pressed.png b/src/main/res/drawable-mdpi/switch_thumb_on_pressed.png Binary files differnew file mode 100644 index 00000000..e8d7bd0f --- /dev/null +++ b/src/main/res/drawable-mdpi/switch_thumb_on_pressed.png diff --git a/src/main/res/drawable-xhdpi/ic_action_done.png b/src/main/res/drawable-xhdpi/ic_action_done.png Binary files differnew file mode 100644 index 00000000..b8915716 --- /dev/null +++ b/src/main/res/drawable-xhdpi/ic_action_done.png diff --git a/src/main/res/drawable-xhdpi/ic_done_black_24dp.png b/src/main/res/drawable-xhdpi/ic_done_black_24dp.png Binary files differnew file mode 100644 index 00000000..64a4944f --- /dev/null +++ b/src/main/res/drawable-xhdpi/ic_done_black_24dp.png diff --git a/src/main/res/drawable-xhdpi/ic_link_white_24dp.png b/src/main/res/drawable-xhdpi/ic_link_white_24dp.png Binary files differnew file mode 100644 index 00000000..984b572a --- /dev/null +++ b/src/main/res/drawable-xhdpi/ic_link_white_24dp.png diff --git a/src/main/res/drawable-xhdpi/ic_notifications_grey600_24dp.png b/src/main/res/drawable-xhdpi/ic_notifications_grey600_24dp.png Binary files differnew file mode 100644 index 00000000..d441dc7c --- /dev/null +++ b/src/main/res/drawable-xhdpi/ic_notifications_grey600_24dp.png diff --git a/src/main/res/drawable-xhdpi/ic_notifications_none_grey600_24dp.png b/src/main/res/drawable-xhdpi/ic_notifications_none_grey600_24dp.png Binary files differnew file mode 100644 index 00000000..1123cea4 --- /dev/null +++ b/src/main/res/drawable-xhdpi/ic_notifications_none_grey600_24dp.png diff --git a/src/main/res/drawable-xhdpi/ic_notifications_off_grey600_24dp.png b/src/main/res/drawable-xhdpi/ic_notifications_off_grey600_24dp.png Binary files differnew file mode 100644 index 00000000..aef303a0 --- /dev/null +++ b/src/main/res/drawable-xhdpi/ic_notifications_off_grey600_24dp.png diff --git a/src/main/res/drawable-xhdpi/ic_notifications_paused_grey600_24dp.png b/src/main/res/drawable-xhdpi/ic_notifications_paused_grey600_24dp.png Binary files differnew file mode 100644 index 00000000..c68bedd3 --- /dev/null +++ b/src/main/res/drawable-xhdpi/ic_notifications_paused_grey600_24dp.png diff --git a/src/main/res/drawable-xhdpi/ic_refresh_grey600_24dp.png b/src/main/res/drawable-xhdpi/ic_refresh_grey600_24dp.png Binary files differnew file mode 100644 index 00000000..7891efff --- /dev/null +++ b/src/main/res/drawable-xhdpi/ic_refresh_grey600_24dp.png diff --git a/src/main/res/drawable-xhdpi/ic_secure_indicator.png b/src/main/res/drawable-xhdpi/ic_secure_indicator.png Binary files differindex 1f4c9a32..cd0d1391 100644 --- a/src/main/res/drawable-xhdpi/ic_secure_indicator.png +++ b/src/main/res/drawable-xhdpi/ic_secure_indicator.png diff --git a/src/main/res/drawable-xhdpi/ic_secure_indicator_white.png b/src/main/res/drawable-xhdpi/ic_secure_indicator_white.png Binary files differnew file mode 100644 index 00000000..b624a8ce --- /dev/null +++ b/src/main/res/drawable-xhdpi/ic_secure_indicator_white.png diff --git a/src/main/res/drawable-xhdpi/message_bubble_received.9.png b/src/main/res/drawable-xhdpi/message_bubble_received.9.png Binary files differnew file mode 100644 index 00000000..c0eb47eb --- /dev/null +++ b/src/main/res/drawable-xhdpi/message_bubble_received.9.png diff --git a/src/main/res/drawable-xhdpi/message_bubble_received_warning.9.png b/src/main/res/drawable-xhdpi/message_bubble_received_warning.9.png Binary files differnew file mode 100644 index 00000000..fe0324ce --- /dev/null +++ b/src/main/res/drawable-xhdpi/message_bubble_received_warning.9.png diff --git a/src/main/res/drawable-xhdpi/message_bubble_received_white.9.png b/src/main/res/drawable-xhdpi/message_bubble_received_white.9.png Binary files differnew file mode 100644 index 00000000..fdb6be0d --- /dev/null +++ b/src/main/res/drawable-xhdpi/message_bubble_received_white.9.png diff --git a/src/main/res/drawable-xhdpi/message_bubble_sent.9.png b/src/main/res/drawable-xhdpi/message_bubble_sent.9.png Binary files differnew file mode 100644 index 00000000..cb5654b7 --- /dev/null +++ b/src/main/res/drawable-xhdpi/message_bubble_sent.9.png diff --git a/src/main/res/drawable-xhdpi/switch_thumb_disable.png b/src/main/res/drawable-xhdpi/switch_thumb_disable.png Binary files differnew file mode 100644 index 00000000..7f677324 --- /dev/null +++ b/src/main/res/drawable-xhdpi/switch_thumb_disable.png diff --git a/src/main/res/drawable-xhdpi/switch_thumb_off_normal.png b/src/main/res/drawable-xhdpi/switch_thumb_off_normal.png Binary files differnew file mode 100644 index 00000000..4199d322 --- /dev/null +++ b/src/main/res/drawable-xhdpi/switch_thumb_off_normal.png diff --git a/src/main/res/drawable-xhdpi/switch_thumb_off_pressed.png b/src/main/res/drawable-xhdpi/switch_thumb_off_pressed.png Binary files differnew file mode 100644 index 00000000..2b86888f --- /dev/null +++ b/src/main/res/drawable-xhdpi/switch_thumb_off_pressed.png diff --git a/src/main/res/drawable-xhdpi/switch_thumb_on_normal.png b/src/main/res/drawable-xhdpi/switch_thumb_on_normal.png Binary files differnew file mode 100644 index 00000000..daa30990 --- /dev/null +++ b/src/main/res/drawable-xhdpi/switch_thumb_on_normal.png diff --git a/src/main/res/drawable-xhdpi/switch_thumb_on_pressed.png b/src/main/res/drawable-xhdpi/switch_thumb_on_pressed.png Binary files differnew file mode 100644 index 00000000..6aab47c9 --- /dev/null +++ b/src/main/res/drawable-xhdpi/switch_thumb_on_pressed.png diff --git a/src/main/res/drawable-xxhdpi/ic_done_black_24dp.png b/src/main/res/drawable-xxhdpi/ic_done_black_24dp.png Binary files differnew file mode 100644 index 00000000..c9c01741 --- /dev/null +++ b/src/main/res/drawable-xxhdpi/ic_done_black_24dp.png diff --git a/src/main/res/drawable-xxhdpi/ic_link_white_24dp.png b/src/main/res/drawable-xxhdpi/ic_link_white_24dp.png Binary files differnew file mode 100644 index 00000000..8e96c356 --- /dev/null +++ b/src/main/res/drawable-xxhdpi/ic_link_white_24dp.png diff --git a/src/main/res/drawable-xxhdpi/ic_notifications_grey600_24dp.png b/src/main/res/drawable-xxhdpi/ic_notifications_grey600_24dp.png Binary files differnew file mode 100644 index 00000000..7d58d25d --- /dev/null +++ b/src/main/res/drawable-xxhdpi/ic_notifications_grey600_24dp.png diff --git a/src/main/res/drawable-xxhdpi/ic_notifications_none_grey600_24dp.png b/src/main/res/drawable-xxhdpi/ic_notifications_none_grey600_24dp.png Binary files differnew file mode 100644 index 00000000..b8772d37 --- /dev/null +++ b/src/main/res/drawable-xxhdpi/ic_notifications_none_grey600_24dp.png diff --git a/src/main/res/drawable-xxhdpi/ic_notifications_off_grey600_24dp.png b/src/main/res/drawable-xxhdpi/ic_notifications_off_grey600_24dp.png Binary files differnew file mode 100644 index 00000000..e627b30a --- /dev/null +++ b/src/main/res/drawable-xxhdpi/ic_notifications_off_grey600_24dp.png diff --git a/src/main/res/drawable-xxhdpi/ic_notifications_paused_grey600_24dp.png b/src/main/res/drawable-xxhdpi/ic_notifications_paused_grey600_24dp.png Binary files differnew file mode 100644 index 00000000..e38f5217 --- /dev/null +++ b/src/main/res/drawable-xxhdpi/ic_notifications_paused_grey600_24dp.png diff --git a/src/main/res/drawable-xxhdpi/ic_refresh_grey600_24dp.png b/src/main/res/drawable-xxhdpi/ic_refresh_grey600_24dp.png Binary files differnew file mode 100644 index 00000000..9c1e27d7 --- /dev/null +++ b/src/main/res/drawable-xxhdpi/ic_refresh_grey600_24dp.png diff --git a/src/main/res/drawable-xxhdpi/ic_secure_indicator.png b/src/main/res/drawable-xxhdpi/ic_secure_indicator.png Binary files differindex 1ee9b67d..6a74ccbe 100644 --- a/src/main/res/drawable-xxhdpi/ic_secure_indicator.png +++ b/src/main/res/drawable-xxhdpi/ic_secure_indicator.png diff --git a/src/main/res/drawable-xxhdpi/ic_secure_indicator_white.png b/src/main/res/drawable-xxhdpi/ic_secure_indicator_white.png Binary files differnew file mode 100644 index 00000000..4945c959 --- /dev/null +++ b/src/main/res/drawable-xxhdpi/ic_secure_indicator_white.png diff --git a/src/main/res/drawable-xxhdpi/message_bubble_received.9.png b/src/main/res/drawable-xxhdpi/message_bubble_received.9.png Binary files differnew file mode 100644 index 00000000..10e78408 --- /dev/null +++ b/src/main/res/drawable-xxhdpi/message_bubble_received.9.png diff --git a/src/main/res/drawable-xxhdpi/message_bubble_received_warning.9.png b/src/main/res/drawable-xxhdpi/message_bubble_received_warning.9.png Binary files differnew file mode 100644 index 00000000..53ecbecf --- /dev/null +++ b/src/main/res/drawable-xxhdpi/message_bubble_received_warning.9.png diff --git a/src/main/res/drawable-xxhdpi/message_bubble_received_white.9.png b/src/main/res/drawable-xxhdpi/message_bubble_received_white.9.png Binary files differnew file mode 100644 index 00000000..436a1bd3 --- /dev/null +++ b/src/main/res/drawable-xxhdpi/message_bubble_received_white.9.png diff --git a/src/main/res/drawable-xxhdpi/message_bubble_sent.9.png b/src/main/res/drawable-xxhdpi/message_bubble_sent.9.png Binary files differnew file mode 100644 index 00000000..f78425d2 --- /dev/null +++ b/src/main/res/drawable-xxhdpi/message_bubble_sent.9.png diff --git a/src/main/res/drawable-xxhdpi/switch_thumb_disable.png b/src/main/res/drawable-xxhdpi/switch_thumb_disable.png Binary files differnew file mode 100644 index 00000000..db7c1df4 --- /dev/null +++ b/src/main/res/drawable-xxhdpi/switch_thumb_disable.png diff --git a/src/main/res/drawable-xxhdpi/switch_thumb_off_normal.png b/src/main/res/drawable-xxhdpi/switch_thumb_off_normal.png Binary files differnew file mode 100644 index 00000000..f747b559 --- /dev/null +++ b/src/main/res/drawable-xxhdpi/switch_thumb_off_normal.png diff --git a/src/main/res/drawable-xxhdpi/switch_thumb_off_pressed.png b/src/main/res/drawable-xxhdpi/switch_thumb_off_pressed.png Binary files differnew file mode 100644 index 00000000..b9fe6d46 --- /dev/null +++ b/src/main/res/drawable-xxhdpi/switch_thumb_off_pressed.png diff --git a/src/main/res/drawable-xxhdpi/switch_thumb_on_normal.png b/src/main/res/drawable-xxhdpi/switch_thumb_on_normal.png Binary files differnew file mode 100644 index 00000000..88199024 --- /dev/null +++ b/src/main/res/drawable-xxhdpi/switch_thumb_on_normal.png diff --git a/src/main/res/drawable-xxhdpi/switch_thumb_on_pressed.png b/src/main/res/drawable-xxhdpi/switch_thumb_on_pressed.png Binary files differnew file mode 100644 index 00000000..7a4fed54 --- /dev/null +++ b/src/main/res/drawable-xxhdpi/switch_thumb_on_pressed.png diff --git a/src/main/res/drawable-xxxhdpi/ic_done_black_24dp.png b/src/main/res/drawable-xxxhdpi/ic_done_black_24dp.png Binary files differnew file mode 100644 index 00000000..2f6d6386 --- /dev/null +++ b/src/main/res/drawable-xxxhdpi/ic_done_black_24dp.png diff --git a/src/main/res/drawable-xxxhdpi/ic_link_white_24dp.png b/src/main/res/drawable-xxxhdpi/ic_link_white_24dp.png Binary files differnew file mode 100644 index 00000000..df2faf36 --- /dev/null +++ b/src/main/res/drawable-xxxhdpi/ic_link_white_24dp.png diff --git a/src/main/res/drawable-xxxhdpi/ic_notifications_grey600_24dp.png b/src/main/res/drawable-xxxhdpi/ic_notifications_grey600_24dp.png Binary files differnew file mode 100644 index 00000000..98dff2f3 --- /dev/null +++ b/src/main/res/drawable-xxxhdpi/ic_notifications_grey600_24dp.png diff --git a/src/main/res/drawable-xxxhdpi/ic_notifications_none_grey600_24dp.png b/src/main/res/drawable-xxxhdpi/ic_notifications_none_grey600_24dp.png Binary files differnew file mode 100644 index 00000000..ffdb15a8 --- /dev/null +++ b/src/main/res/drawable-xxxhdpi/ic_notifications_none_grey600_24dp.png diff --git a/src/main/res/drawable-xxxhdpi/ic_notifications_off_grey600_24dp.png b/src/main/res/drawable-xxxhdpi/ic_notifications_off_grey600_24dp.png Binary files differnew file mode 100644 index 00000000..ab65f4b2 --- /dev/null +++ b/src/main/res/drawable-xxxhdpi/ic_notifications_off_grey600_24dp.png diff --git a/src/main/res/drawable-xxxhdpi/ic_notifications_paused_grey600_24dp.png b/src/main/res/drawable-xxxhdpi/ic_notifications_paused_grey600_24dp.png Binary files differnew file mode 100644 index 00000000..53162e56 --- /dev/null +++ b/src/main/res/drawable-xxxhdpi/ic_notifications_paused_grey600_24dp.png diff --git a/src/main/res/drawable-xxxhdpi/ic_refresh_grey600_24dp.png b/src/main/res/drawable-xxxhdpi/ic_refresh_grey600_24dp.png Binary files differnew file mode 100644 index 00000000..e44a6d28 --- /dev/null +++ b/src/main/res/drawable-xxxhdpi/ic_refresh_grey600_24dp.png diff --git a/src/main/res/drawable-xxxhdpi/message_bubble_received.9.png b/src/main/res/drawable-xxxhdpi/message_bubble_received.9.png Binary files differnew file mode 100644 index 00000000..c474359e --- /dev/null +++ b/src/main/res/drawable-xxxhdpi/message_bubble_received.9.png diff --git a/src/main/res/drawable-xxxhdpi/message_bubble_received_warning.9.png b/src/main/res/drawable-xxxhdpi/message_bubble_received_warning.9.png Binary files differnew file mode 100644 index 00000000..1421768c --- /dev/null +++ b/src/main/res/drawable-xxxhdpi/message_bubble_received_warning.9.png diff --git a/src/main/res/drawable-xxxhdpi/message_bubble_received_white.9.png b/src/main/res/drawable-xxxhdpi/message_bubble_received_white.9.png Binary files differnew file mode 100644 index 00000000..ee89b670 --- /dev/null +++ b/src/main/res/drawable-xxxhdpi/message_bubble_received_white.9.png diff --git a/src/main/res/drawable-xxxhdpi/message_bubble_sent.9.png b/src/main/res/drawable-xxxhdpi/message_bubble_sent.9.png Binary files differnew file mode 100644 index 00000000..d34038d0 --- /dev/null +++ b/src/main/res/drawable-xxxhdpi/message_bubble_sent.9.png diff --git a/src/main/res/drawable-xxxhdpi/switch_thumb_disable.png b/src/main/res/drawable-xxxhdpi/switch_thumb_disable.png Binary files differnew file mode 100644 index 00000000..3970168c --- /dev/null +++ b/src/main/res/drawable-xxxhdpi/switch_thumb_disable.png diff --git a/src/main/res/drawable-xxxhdpi/switch_thumb_off_normal.png b/src/main/res/drawable-xxxhdpi/switch_thumb_off_normal.png Binary files differnew file mode 100644 index 00000000..ea8d4f89 --- /dev/null +++ b/src/main/res/drawable-xxxhdpi/switch_thumb_off_normal.png diff --git a/src/main/res/drawable-xxxhdpi/switch_thumb_off_pressed.png b/src/main/res/drawable-xxxhdpi/switch_thumb_off_pressed.png Binary files differnew file mode 100644 index 00000000..84d667b1 --- /dev/null +++ b/src/main/res/drawable-xxxhdpi/switch_thumb_off_pressed.png diff --git a/src/main/res/drawable-xxxhdpi/switch_thumb_on_normal.png b/src/main/res/drawable-xxxhdpi/switch_thumb_on_normal.png Binary files differnew file mode 100644 index 00000000..06b190eb --- /dev/null +++ b/src/main/res/drawable-xxxhdpi/switch_thumb_on_normal.png diff --git a/src/main/res/drawable-xxxhdpi/switch_thumb_on_pressed.png b/src/main/res/drawable-xxxhdpi/switch_thumb_on_pressed.png Binary files differnew file mode 100644 index 00000000..79c30d1e --- /dev/null +++ b/src/main/res/drawable-xxxhdpi/switch_thumb_on_pressed.png diff --git a/src/main/res/drawable/message_border.xml b/src/main/res/drawable/account_image_border.xml index 990d0288..990d0288 100644 --- a/src/main/res/drawable/message_border.xml +++ b/src/main/res/drawable/account_image_border.xml diff --git a/src/main/res/drawable/switch_back_off.xml b/src/main/res/drawable/switch_back_off.xml new file mode 100644 index 00000000..20d2fb14 --- /dev/null +++ b/src/main/res/drawable/switch_back_off.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8"?> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + + <item android:state_enabled="false"><shape android:shape="rectangle"> + <solid android:color="#D5D5D5" /> + + <corners android:radius="99dp" /> + </shape></item> + <item android:state_enabled="true"><shape android:shape="rectangle"> + <solid android:color="#939393" /> + + <corners android:radius="99dp" /> + </shape></item> + +</selector>
\ No newline at end of file diff --git a/src/main/res/drawable/switch_back_on.xml b/src/main/res/drawable/switch_back_on.xml new file mode 100644 index 00000000..45117a98 --- /dev/null +++ b/src/main/res/drawable/switch_back_on.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8"?> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_enabled="false"> + <shape android:shape="rectangle"> + <solid android:color="#D5D5D5"/> + <corners android:radius="99dp"/> + </shape> + </item> + <item android:state_enabled="true"> + <shape android:shape="rectangle"> + <!-- 30% accent on white --> + <solid android:color="#b3ddf7"/> + <corners android:radius="99dp"/> + </shape> + </item> +</selector>
\ No newline at end of file diff --git a/src/main/res/drawable/switch_thumb.xml b/src/main/res/drawable/switch_thumb.xml new file mode 100644 index 00000000..ba3d1c45 --- /dev/null +++ b/src/main/res/drawable/switch_thumb.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + + <item android:drawable="@drawable/switch_thumb_disable" android:state_enabled="false"/> + <item android:drawable="@drawable/switch_thumb_on_pressed" android:state_checked="true" android:state_pressed="true"/> + <item android:drawable="@drawable/switch_thumb_on_pressed" android:state_checked="true" android:state_focused="true"/> + <item android:drawable="@drawable/switch_thumb_on_normal" android:state_checked="true"/> + <item android:drawable="@drawable/switch_thumb_off_pressed" android:state_checked="false" android:state_pressed="true"/> + <item android:drawable="@drawable/switch_thumb_off_pressed" android:state_checked="false" android:state_focused="true"/> + <item android:drawable="@drawable/switch_thumb_off_normal" android:state_checked="false"/> + +</selector>
\ No newline at end of file diff --git a/src/main/res/layout/account_row.xml b/src/main/res/layout/account_row.xml index 77138a4a..c6569d5a 100644 --- a/src/main/res/layout/account_row.xml +++ b/src/main/res/layout/account_row.xml @@ -1,20 +1,21 @@ <?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="wrap_content" - android:background="?android:attr/activatedBackgroundIndicator" - android:paddingLeft="8dp" - android:paddingBottom="8dp" - android:paddingTop="8dp"> + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="?android:attr/activatedBackgroundIndicator" + android:paddingLeft="8dp" + android:paddingBottom="8dp" + android:paddingTop="8dp"> - <ImageView + <com.makeramen.roundedimageview.RoundedImageView android:id="@+id/account_image" android:layout_width="48dp" android:layout_height="48dp" android:layout_alignParentLeft="true" android:src="@drawable/ic_profile" - android:contentDescription="@string/account_image_description"> - </ImageView> + android:contentDescription="@string/account_image_description" + app:riv_corner_radius="2dp" /> <LinearLayout android:layout_width="fill_parent" @@ -40,18 +41,19 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/account_status_unknown" - android:textColor="@color/secondaryText" + android:textColor="@color/black54" android:textSize="?attr/TextSizeBody" android:textStyle="bold" /> </LinearLayout> - <Switch + <eu.siacs.conversations.ui.widget.Switch + style="@style/MD" android:id="@+id/tgl_account_status" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_centerVertical="true" - android:padding="8dp" + android:padding="16dp" android:focusable="false"/> </RelativeLayout>
\ No newline at end of file diff --git a/src/main/res/layout/activity_about.xml b/src/main/res/layout/activity_about.xml index 310246b9..8f1113ad 100644 --- a/src/main/res/layout/activity_about.xml +++ b/src/main/res/layout/activity_about.xml @@ -17,5 +17,6 @@ android:paddingBottom="@dimen/activity_vertical_margin" android:textColor="@color/primaryText" android:textSize="?attr/TextSizeBody" - android:typeface="monospace"/> + android:typeface="monospace" + android:fontFamily="monospace"/> </ScrollView> diff --git a/src/main/res/layout/activity_edit_account.xml b/src/main/res/layout/activity_edit_account.xml index 0c9809bc..9bacf9a2 100644 --- a/src/main/res/layout/activity_edit_account.xml +++ b/src/main/res/layout/activity_edit_account.xml @@ -1,108 +1,165 @@ <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="@color/grey200" > + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@color/grey200"> <ScrollView android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_above="@+id/button_bar" - android:layout_alignParentTop="true" > + android:layout_alignParentTop="true"> <LinearLayout + android:id="@+id/account_main_layout" android:layout_width="match_parent" android:layout_height="wrap_content" - android:orientation="vertical" > + android:orientation="vertical"> <RelativeLayout android:id="@+id/editor" android:layout_width="fill_parent" android:layout_height="wrap_content" + android:layout_marginBottom="@dimen/activity_vertical_margin" android:layout_marginLeft="@dimen/activity_horizontal_margin" android:layout_marginRight="@dimen/activity_horizontal_margin" android:layout_marginTop="@dimen/activity_vertical_margin" - android:layout_marginBottom="@dimen/activity_vertical_margin" android:background="@drawable/infocard_border" android:orientation="vertical" android:padding="@dimen/infocard_padding"> - <ImageView android:id="@+id/avater" + <com.makeramen.roundedimageview.RoundedImageView + android:id="@+id/avater" android:layout_width="72dp" android:layout_height="72dp" android:layout_alignParentTop="true" android:layout_marginRight="16dp" - android:contentDescription="@string/account_image_description"/> + android:contentDescription="@string/account_image_description" + app:riv_corner_radius="2dp"/> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" - android:orientation="vertical" android:layout_toRightOf="@+id/avater" + android:orientation="vertical" android:id="@+id/editAccountBoxes"> - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/account_settings_jabber_id" - android:textColor="@color/primaryText" - android:textSize="?attr/TextSizeBody" /> + <TextView + android:id="@+id/account_jid_label" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/account_settings_jabber_id" + android:textColor="@color/primaryText" + android:textSize="?attr/TextSizeBody"/> - <AutoCompleteTextView - android:id="@+id/account_jid" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:hint="@string/account_settings_example_jabber_id" - android:inputType="textEmailAddress" - android:textColor="@color/primaryText" - android:textColorHint="@color/secondaryText" - android:textSize="?attr/TextSizeBody" /> + <AutoCompleteTextView + android:id="@+id/account_jid" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:hint="@string/account_settings_example_jabber_id" + android:inputType="textEmailAddress" + android:textColor="@color/primaryText" + android:textColorHint="@color/secondaryText" + android:textSize="?attr/TextSizeBody"/> - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="8dp" - android:text="@string/account_settings_password" - android:textColor="@color/primaryText" - android:textSize="?attr/TextSizeBody" /> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:text="@string/account_settings_password" + android:textColor="@color/primaryText" + android:textSize="?attr/TextSizeBody"/> - <EditText - android:id="@+id/account_password" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:hint="@string/password" - android:inputType="textPassword" - android:textColor="@color/primaryText" - android:textColorHint="@color/secondaryText" - android:textSize="?attr/TextSizeBody" /> + <EditText + android:id="@+id/account_password" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:hint="@string/password" + android:inputType="textPassword" + android:textColor="@color/primaryText" + android:textColorHint="@color/secondaryText" + android:textSize="?attr/TextSizeBody"/> - <CheckBox - android:id="@+id/account_register_new" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="8dp" - android:text="@string/register_account" - android:textColor="@color/primaryText" - android:textSize="?attr/TextSizeBody" /> + <LinearLayout + android:id="@+id/name_port" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:orientation="horizontal" + android:weightSum="1"> + <LinearLayout + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="0.8" + android:orientation="vertical"> + <TextView + android:id="@+id/textView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/account_settings_hostname" + android:textColor="@color/primaryText" + android:textSize="?attr/TextSizeBody"/> + <EditText + android:id="@+id/hostname" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:hint="@string/hostname_or_onion" + android:inputType="textNoSuggestions" + android:textColor="@color/primaryText" + android:textColorHint="@color/secondaryText" + android:textSize="?attr/TextSizeBody"/> + </LinearLayout> + <LinearLayout + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="0.2" + android:orientation="vertical" + > + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/account_settings_port" + android:textColor="@color/primaryText" + android:textSize="?attr/TextSizeBody"/> + <EditText + android:id="@+id/port" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:inputType="number" + android:maxLength="5" + android:textColor="@color/primaryText" + android:textColorHint="@color/secondaryText" + android:textSize="?attr/TextSizeBody"/> + </LinearLayout> + </LinearLayout> + <CheckBox + android:id="@+id/account_register_new" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:text="@string/register_account" + android:textColor="@color/primaryText" + android:textSize="?attr/TextSizeBody"/> - <TextView - android:id="@+id/account_confirm_password_desc" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/account_settings_confirm_password" - android:textColor="@color/primaryText" - android:textSize="?attr/TextSizeBody" - android:visibility="gone" /> + <TextView + android:id="@+id/account_confirm_password_desc" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/account_settings_confirm_password" + android:textColor="@color/primaryText" + android:textSize="?attr/TextSizeBody" + android:visibility="gone"/> - <EditText - android:id="@+id/account_password_confirm" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="8dp" - android:hint="@string/confirm_password" - android:inputType="textPassword" - android:visibility="gone" - android:textColor="@color/primaryText" - android:textColorHint="@color/secondaryText" - android:textSize="?attr/TextSizeBody" /> + <EditText + android:id="@+id/account_password_confirm" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:hint="@string/confirm_password" + android:inputType="textPassword" + android:textColor="@color/primaryText" + android:textColorHint="@color/secondaryText" + android:textSize="?attr/TextSizeBody" + android:visibility="gone"/> </LinearLayout> <LinearLayout android:layout_width="fill_parent" @@ -122,23 +179,66 @@ </LinearLayout> </RelativeLayout> - <LinearLayout + <RelativeLayout + android:id="@+id/battery_optimization" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="@dimen/activity_vertical_margin" + android:layout_marginLeft="@dimen/activity_horizontal_margin" + android:layout_marginRight="@dimen/activity_horizontal_margin" + android:layout_marginTop="@dimen/activity_vertical_margin" + android:background="@drawable/infocard_border" + android:orientation="vertical" + android:padding="@dimen/infocard_padding" + android:visibility="gone"> + <TextView + android:id="@+id/batt_op_headline" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/battery_optimizations_enabled" + android:textColor="@color/primaryText" + android:textSize="?attr/TextSizeHeadline" + android:textStyle="bold"/> + <TextView + android:id="@+id/batt_op_body" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@+id/batt_op_headline" + android:layout_marginBottom="8dp" + android:layout_marginTop="8dp" + android:text="@string/battery_optimizations_enabled_explained" + android:textColor="@color/primaryText" + android:textSize="?attr/TextSizeBody"/> + <Button + android:id="@+id/batt_op_disable" + style="?android:attr/borderlessButtonStyle" + android:layout_marginRight="-8dp" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentBottom="true" + android:layout_alignParentEnd="true" + android:layout_alignParentRight="true" android:layout_below="@+id/batt_op_body" + android:text="@string/disable" android:textColor="@color/accent"/> + </RelativeLayout> + + + <LinearLayout android:id="@+id/stats" android:layout_width="fill_parent" android:layout_height="fill_parent" + android:layout_marginBottom="@dimen/activity_vertical_margin" android:layout_marginLeft="@dimen/activity_horizontal_margin" android:layout_marginRight="@dimen/activity_horizontal_margin" android:layout_marginTop="@dimen/activity_vertical_margin" - android:layout_marginBottom="@dimen/activity_vertical_margin" android:background="@drawable/infocard_border" android:orientation="vertical" android:padding="@dimen/infocard_padding" - android:visibility="gone" > + android:visibility="gone"> <TableLayout android:layout_width="match_parent" android:layout_height="wrap_content" - android:stretchColumns="1" > + android:stretchColumns="1"> <TableRow android:layout_width="fill_parent" @@ -150,7 +250,7 @@ android:layout_height="wrap_content" android:text="@string/server_info_session_established" android:textColor="@color/primaryText" - android:textSize="?attr/TextSizeBody" /> + android:textSize="?attr/TextSizeBody"/> <TextView android:id="@+id/session_est" @@ -168,18 +268,18 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:stretchColumns="1" - android:visibility="gone" > + android:visibility="gone"> <TableRow android:layout_width="fill_parent" - android:layout_height="wrap_content" > + android:layout_height="wrap_content"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/server_info_pep" android:textColor="@color/primaryText" - android:textSize="?attr/TextSizeBody" /> + android:textSize="?attr/TextSizeBody"/> <TextView android:id="@+id/server_info_pep" @@ -193,14 +293,14 @@ <TableRow android:layout_width="fill_parent" - android:layout_height="wrap_content" > + android:layout_height="wrap_content"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/server_info_blocking" android:textColor="@color/primaryText" - android:textSize="?attr/TextSizeBody" /> + android:textSize="?attr/TextSizeBody"/> <TextView android:id="@+id/server_info_blocking" @@ -214,14 +314,14 @@ <TableRow android:layout_width="fill_parent" - android:layout_height="wrap_content" > + android:layout_height="wrap_content"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/server_info_stream_management" android:textColor="@color/primaryText" - android:textSize="?attr/TextSizeBody" /> + android:textSize="?attr/TextSizeBody"/> <TextView android:id="@+id/server_info_sm" @@ -235,14 +335,14 @@ <TableRow android:layout_width="fill_parent" - android:layout_height="wrap_content" > + android:layout_height="wrap_content"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/server_info_roster_version" android:textColor="@color/primaryText" - android:textSize="?attr/TextSizeBody" /> + android:textSize="?attr/TextSizeBody"/> <TextView android:id="@+id/server_info_roster_version" @@ -256,14 +356,14 @@ <TableRow android:layout_width="fill_parent" - android:layout_height="wrap_content" > + android:layout_height="wrap_content"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/server_info_carbon_messages" android:textColor="@color/primaryText" - android:textSize="?attr/TextSizeBody" /> + android:textSize="?attr/TextSizeBody"/> <TextView android:id="@+id/server_info_carbons" @@ -277,14 +377,14 @@ <TableRow android:layout_width="fill_parent" - android:layout_height="wrap_content" > + android:layout_height="wrap_content"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/server_info_mam" android:textColor="@color/primaryText" - android:textSize="?attr/TextSizeBody" /> + android:textSize="?attr/TextSizeBody"/> <TextView android:id="@+id/server_info_mam" @@ -298,14 +398,14 @@ <TableRow android:layout_width="fill_parent" - android:layout_height="wrap_content" > + android:layout_height="wrap_content"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/server_info_csi" android:textColor="@color/primaryText" - android:textSize="?attr/TextSizeBody" /> + android:textSize="?attr/TextSizeBody"/> <TextView android:id="@+id/server_info_csi" @@ -316,12 +416,32 @@ android:textSize="?attr/TextSizeBody" tools:ignore="RtlHardcoded"/> </TableRow> + <TableRow + android:layout_width="fill_parent" + android:layout_height="wrap_content"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/server_info_http_upload" + android:textColor="@color/primaryText" + android:textSize="?attr/TextSizeBody"/> + + <TextView + android:id="@+id/server_info_http_upload" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="right" + android:textColor="@color/primaryText" + android:textSize="?attr/TextSizeBody" + tools:ignore="RtlHardcoded"/> + </TableRow> </TableLayout> <RelativeLayout + android:id="@+id/otr_fingerprint_box" android:layout_width="wrap_content" android:layout_height="match_parent" - android:id="@+id/otr_fingerprint_box" android:layout_marginTop="32dp"> <LinearLayout @@ -335,16 +455,17 @@ android:id="@+id/otr_fingerprint" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:fontFamily="monospace" android:textColor="@color/primaryText" android:textSize="?attr/TextSizeBody" - android:typeface="monospace" /> + android:typeface="monospace"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" + android:text="@string/otr_fingerprint" android:textColor="@color/secondaryText" - android:textSize="?attr/TextSizeInfo" - android:text="@string/otr_fingerprint"/> + android:textSize="?attr/TextSizeInfo"/> </LinearLayout> <ImageButton @@ -354,11 +475,101 @@ android:layout_alignParentRight="true" android:layout_centerVertical="true" android:background="?android:selectableItemBackground" + android:contentDescription="@string/copy_otr_clipboard_description" android:padding="@dimen/image_button_padding" android:src="?attr/icon_copy" - android:visibility="visible" - android:contentDescription="@string/copy_otr_clipboard_description"/> + android:visibility="visible"/> </RelativeLayout> + <RelativeLayout + android:id="@+id/axolotl_fingerprint_box" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_marginTop="32dp"> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentLeft="true" + android:layout_toLeftOf="@+id/axolotl_actions" + android:orientation="vertical"> + + <TextView + android:id="@+id/axolotl_fingerprint" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:fontFamily="monospace" + android:textColor="@color/primaryText" + android:textSize="?attr/TextSizeBody" + android:typeface="monospace"/> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/this_device_omemo_fingerprint" + android:textColor="@color/secondaryText" + android:textSize="?attr/TextSizeInfo"/> + </LinearLayout> + + <LinearLayout + android:id="@+id/axolotl_actions" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentRight="true" + android:layout_centerVertical="true" + android:orientation="vertical"> + + <ImageButton + android:id="@+id/action_copy_axolotl_to_clipboard" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:background="?android:selectableItemBackground" + android:contentDescription="@string/copy_omemo_clipboard_description" + android:padding="@dimen/image_button_padding" + android:src="?attr/icon_copy" + android:visibility="visible"/> + <ImageButton + android:id="@+id/action_regenerate_axolotl_key" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:background="?android:selectableItemBackground" + android:contentDescription="@string/regenerate_omemo_key" + android:padding="@dimen/image_button_padding" + android:src="?attr/icon_refresh" + android:visibility="gone"/> + + </LinearLayout> + </RelativeLayout> + </LinearLayout> + <LinearLayout + android:id="@+id/other_device_keys_card" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="@dimen/activity_vertical_margin" + android:layout_marginLeft="@dimen/activity_horizontal_margin" + android:layout_marginRight="@dimen/activity_horizontal_margin" + android:layout_marginTop="@dimen/activity_vertical_margin" + android:background="@drawable/infocard_border" + android:orientation="vertical" + android:padding="@dimen/infocard_padding" + android:visibility="gone"> + + <TextView + android:id="@+id/other_device_keys_title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/other_devices" + android:textColor="@color/primaryText" + android:textSize="?attr/TextSizeHeadline" + android:textStyle="bold"/> + + <LinearLayout + android:id="@+id/other_device_keys" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:divider="?android:dividerHorizontal" + android:orientation="vertical" + android:showDividers="middle"> + </LinearLayout> </LinearLayout> </LinearLayout> </ScrollView> @@ -368,10 +579,10 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" - android:layout_alignParentStart="true" android:layout_alignParentEnd="true" android:layout_alignParentLeft="true" - android:layout_alignParentRight="true" > + android:layout_alignParentRight="true" + android:layout_alignParentStart="true"> <Button android:id="@+id/cancel_button" @@ -380,14 +591,14 @@ android:layout_height="wrap_content" android:layout_weight="1" android:text="@string/cancel" - android:textColor="@color/primaryText" /> + android:textColor="@color/primaryText"/> <View android:layout_width="1dp" android:layout_height="fill_parent" android:layout_marginBottom="7dp" android:layout_marginTop="7dp" - android:background="@color/black12" /> + android:background="@color/black12"/> <Button android:id="@+id/save_button" @@ -397,7 +608,7 @@ android:layout_weight="1" android:enabled="false" android:text="@string/save" - android:textColor="@color/secondaryText" /> + android:textColor="@color/secondaryText"/> </LinearLayout> </RelativeLayout> diff --git a/src/main/res/layout/activity_muc_details.xml b/src/main/res/layout/activity_muc_details.xml index a35e3301..4c6b0301 100644 --- a/src/main/res/layout/activity_muc_details.xml +++ b/src/main/res/layout/activity_muc_details.xml @@ -1,10 +1,12 @@ <?xml version="1.0" encoding="utf-8"?> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="fill_parent" - android:layout_height="fill_parent" - android:background="@color/grey200"> + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:background="@color/grey200"> <LinearLayout + android:id="@+id/muc_main_layout" android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="vertical"> @@ -35,13 +37,13 @@ android:layout_height="wrap_content" android:layout_marginBottom="32dp"> - <ImageView + <com.makeramen.roundedimageview.RoundedImageView android:id="@+id/your_photo" android:layout_width="48dp" android:layout_height="48dp" android:layout_alignParentLeft="true" - android:src="@drawable/ic_profile"> - </ImageView> + android:src="@drawable/ic_profile" + app:riv_corner_radius="2dp" /> <LinearLayout android:layout_width="fill_parent" @@ -86,7 +88,7 @@ android:id="@+id/muc_conference_type" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="@string/private_conference" + android:text="@string/private_conference" android:layout_centerVertical="true" android:textColor="@color/primaryText" android:textSize="?attr/TextSizeBody" @@ -102,10 +104,66 @@ android:layout_alignParentRight="true" android:layout_centerVertical="true" android:background="?android:selectableItemBackground" - android:padding="@dimen/image_button_padding" + android:padding="8dp" android:src="?attr/icon_settings"/> </RelativeLayout> + <RelativeLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content"> + <TextView + android:id="@+id/notification_status_text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/notify_on_all_messages" + android:layout_centerVertical="true" + android:textColor="@color/primaryText" + android:textSize="?attr/TextSizeBody" + android:layout_alignParentLeft="true" + android:layout_toLeftOf="@+id/notification_status_button" + /> + <ImageButton + android:id="@+id/notification_status_button" + style="?android:attr/buttonStyleSmall" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:layout_alignParentRight="true" + android:layout_centerVertical="true" + android:background="?android:selectableItemBackground" + android:padding="@dimen/image_button_padding" + android:src="@drawable/ic_notifications_grey600_24dp"/> + </RelativeLayout> + + <TableLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:id="@+id/muc_info_more" + android:stretchColumns="1" + android:visibility="gone"> + + <TableRow + android:layout_width="fill_parent" + android:layout_height="match_parent"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/server_info_mam" + android:textColor="@color/primaryText" + android:textSize="?attr/TextSizeBody" /> + + <TextView + android:id="@+id/muc_info_mam" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="right" + android:textColor="@color/primaryText" + android:textSize="?attr/TextSizeBody" /> + </TableRow> + + </TableLayout> + <TextView android:id="@+id/details_account" android:layout_width="wrap_content" diff --git a/src/main/res/layout/activity_publish_profile_picture.xml b/src/main/res/layout/activity_publish_profile_picture.xml index 45f979e3..1520b330 100644 --- a/src/main/res/layout/activity_publish_profile_picture.xml +++ b/src/main/res/layout/activity_publish_profile_picture.xml @@ -12,12 +12,12 @@ android:layout_centerHorizontal="true" android:layout_marginBottom="8dp" android:layout_marginTop="24dp" - android:background="@drawable/message_border" > + android:background="@drawable/account_image_border" > <ImageView android:id="@+id/account_image" - android:layout_width="194dp" - android:layout_height="194dp" /> + android:layout_width="192dp" + android:layout_height="192dp" /> </LinearLayout> <TextView diff --git a/src/main/res/layout/activity_trust_keys.xml b/src/main/res/layout/activity_trust_keys.xml new file mode 100644 index 00000000..6f8a1bc9 --- /dev/null +++ b/src/main/res/layout/activity_trust_keys.xml @@ -0,0 +1,152 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@color/grey200" > + <ScrollView + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_above="@+id/button_bar" + android:layout_alignParentTop="true" > + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <LinearLayout + android:id="@+id/key_error_message_card" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="@dimen/activity_horizontal_margin" + android:layout_marginRight="@dimen/activity_horizontal_margin" + android:layout_marginTop="@dimen/activity_vertical_margin" + android:layout_marginBottom="@dimen/activity_vertical_margin" + android:background="@drawable/infocard_border" + android:orientation="vertical" + android:padding="@dimen/infocard_padding" + android:visibility="gone"> + + <TextView + android:id="@+id/key_error_message_title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="@color/black87" + android:textSize="?attr/TextSizeHeadline" + android:textStyle="bold" + android:text="@string/error_trustkeys_title"/> + + <TextView + android:id="@+id/key_error_message" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="@color/black87" + android:textSize="?attr/TextSizeBody" + android:padding="8dp"/> + + </LinearLayout> + + <LinearLayout + android:id="@+id/own_keys_card" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="@dimen/activity_horizontal_margin" + android:layout_marginRight="@dimen/activity_horizontal_margin" + android:layout_marginTop="@dimen/activity_vertical_margin" + android:layout_marginBottom="@dimen/activity_vertical_margin" + android:background="@drawable/infocard_border" + android:orientation="vertical" + android:padding="@dimen/infocard_padding" + android:visibility="gone"> + + <TextView + android:id="@+id/own_keys_title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="@color/black87" + android:textSize="?attr/TextSizeHeadline" + android:textStyle="bold"/> + + <LinearLayout + android:id="@+id/own_keys_details" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:divider="?android:dividerHorizontal" + android:showDividers="middle" + android:orientation="vertical"> + </LinearLayout> + + </LinearLayout> + + <LinearLayout + android:id="@+id/foreign_keys_card" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="@dimen/activity_horizontal_margin" + android:layout_marginRight="@dimen/activity_horizontal_margin" + android:layout_marginTop="@dimen/activity_vertical_margin" + android:layout_marginBottom="@dimen/activity_vertical_margin" + android:background="@drawable/infocard_border" + android:orientation="vertical" + android:padding="@dimen/infocard_padding" + android:visibility="gone"> + + <TextView + android:id="@+id/foreign_keys_title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="@color/black87" + android:textSize="?attr/TextSizeHeadline" + android:textStyle="bold"/> + + <LinearLayout + android:id="@+id/foreign_keys_details" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:divider="?android:dividerHorizontal" + android:showDividers="middle" + android:orientation="vertical"> + </LinearLayout> + + </LinearLayout> + + </LinearLayout> + </ScrollView> + <LinearLayout + android:id="@+id/button_bar" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentBottom="true" + android:layout_alignParentStart="true" + android:layout_alignParentEnd="true" + android:layout_alignParentLeft="true" + android:layout_alignParentRight="true" > + + <Button + android:id="@+id/cancel_button" + style="?android:attr/borderlessButtonStyle" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:text="@string/cancel" + android:textColor="@color/black87" /> + + <View + android:layout_width="1dp" + android:layout_height="fill_parent" + android:layout_marginBottom="7dp" + android:layout_marginTop="7dp" + android:background="@color/black12" /> + + <Button + android:id="@+id/save_button" + style="?android:attr/borderlessButtonStyle" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:enabled="true" + android:textColor="@color/black54" + android:text="@string/done"/> + </LinearLayout> +</RelativeLayout> diff --git a/src/main/res/layout/activity_verify_otr.xml b/src/main/res/layout/activity_verify_otr.xml index ce8b831e..2d5821a4 100644 --- a/src/main/res/layout/activity_verify_otr.xml +++ b/src/main/res/layout/activity_verify_otr.xml @@ -36,7 +36,8 @@ android:layout_height="wrap_content" android:textColor="@color/primaryText" android:textSize="?attr/TextSizeBody" - android:typeface="monospace"/> + android:typeface="monospace" + android:fontFamily="monospace"/> <TextView android:layout_width="wrap_content" @@ -52,7 +53,8 @@ android:layout_marginTop="20dp" android:textColor="@color/primaryText" android:textSize="?attr/TextSizeBody" - android:typeface="monospace"/> + android:typeface="monospace" + android:fontFamily="monospace"/> <TextView android:layout_width="wrap_content" diff --git a/src/main/res/layout/certificate_information.xml b/src/main/res/layout/certificate_information.xml new file mode 100644 index 00000000..4c085459 --- /dev/null +++ b/src/main/res/layout/certificate_information.xml @@ -0,0 +1,88 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:padding="16dp"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/certificate_subject" + android:textColor="@color/black87" + android:textSize="?attr/TextSizeHeadline"/> + <TextView + android:layout_marginTop="8dp" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/certificate_cn" + android:textColor="@color/black87" + android:textSize="?attr/TextSizeBody"/> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/subject_cn" + android:textColor="@color/black54" + android:textSize="?attr/TextSizeBody"/> + <TextView + android:layout_marginTop="8dp" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/certificate_o" + android:textColor="@color/black87" + android:textSize="?attr/TextSizeBody"/> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/subject_o" + android:textColor="@color/black54" + android:textSize="?attr/TextSizeBody"/> + <TextView + android:layout_marginTop="16dp" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/certificate_issuer" + android:textColor="@color/black87" + android:textSize="?attr/TextSizeHeadline"/> + <TextView + android:layout_marginTop="8dp" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/certificate_cn" + android:textColor="@color/black87" + android:textSize="?attr/TextSizeBody"/> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/issuer_cn" + android:textColor="@color/black54" + android:textSize="?attr/TextSizeBody"/> + <TextView + android:layout_marginTop="8dp" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/certificate_o" + android:textColor="@color/black87" + android:textSize="?attr/TextSizeBody"/> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/issuer_o" + android:textColor="@color/black54" + android:textSize="?attr/TextSizeBody"/> + <TextView + android:layout_marginTop="16dp" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/certificate_sha1" + android:textColor="@color/black87" + android:textSize="?attr/TextSizeBody"/> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/sha1" + android:textColor="@color/black54" + android:textSize="?attr/TextSizeBody" + android:typeface="monospace" + android:fontFamily="monospace"/> +</LinearLayout>
\ No newline at end of file diff --git a/src/main/res/layout/contact.xml b/src/main/res/layout/contact.xml index 853387f3..faf017d0 100644 --- a/src/main/res/layout/contact.xml +++ b/src/main/res/layout/contact.xml @@ -1,17 +1,19 @@ <?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="wrap_content" - android:background="?android:attr/activatedBackgroundIndicator" - android:padding="8dp" > + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="?android:attr/activatedBackgroundIndicator" + android:padding="8dp"> - <ImageView + <com.makeramen.roundedimageview.RoundedImageView android:id="@+id/contact_photo" android:layout_width="48dp" android:layout_height="48dp" android:layout_alignParentLeft="true" android:scaleType="centerCrop" - android:src="@drawable/ic_profile" /> + android:src="@drawable/ic_profile" + app:riv_corner_radius="2dp" /> <TextView android:layout_width="48dp" android:layout_height="4dp" @@ -58,6 +60,7 @@ android:textColor="@color/primaryText" android:textSize="?attr/TextSizeHeadline" android:typeface="monospace" + android:fontFamily="monospace" android:visibility="gone" /> </LinearLayout> diff --git a/src/main/res/layout/contact_key.xml b/src/main/res/layout/contact_key.xml index 22e4e576..6d61026d 100644 --- a/src/main/res/layout/contact_key.xml +++ b/src/main/res/layout/contact_key.xml @@ -3,39 +3,67 @@ android:layout_width="wrap_content" android:layout_height="match_parent" > - <LinearLayout + <RelativeLayout + android:id="@+id/key_data" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" - android:layout_toLeftOf="@+id/button_remove" - android:orientation="vertical" - android:padding="8dp" > + android:paddingTop="8dp" + android:paddingLeft="8dp" + android:paddingBottom="8dp"> <TextView android:id="@+id/key" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="@color/primaryText" + android:layout_alignParentLeft="true" + android:layout_toLeftOf="@+id/tgl_trust" android:textSize="?attr/TextSizeBody" - android:typeface="monospace" /> + android:typeface="monospace" + android:fontFamily="monospace"/> <TextView android:id="@+id/key_type" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="@color/secondaryText" + android:layout_alignParentLeft="true" + android:layout_below="@+id/key" + android:maxLines="1" + android:textSize="?attr/TextSizeInfo"/> + + <TextView + android:id="@+id/key_trust" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentRight="true" + android:layout_below="@+id/key" + android:visibility="gone" + android:textColor="@color/secondaryText" android:textSize="?attr/TextSizeInfo"/> - </LinearLayout> <ImageButton android:id="@+id/button_remove" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" + android:layout_toRightOf="@+id/key" android:layout_centerVertical="true" android:background="?android:selectableItemBackground" android:padding="@dimen/image_button_padding" android:src="?attr/icon_remove" - android:visibility="invisible" /> + android:visibility="gone" /> + + + <eu.siacs.conversations.ui.widget.Switch + android:id="@+id/tgl_trust" + android:visibility="invisible" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentRight="true" + android:layout_centerVertical="true" + style="@style/MD"/> + </RelativeLayout> </RelativeLayout>
\ No newline at end of file diff --git a/src/main/res/layout/conversation_list_row.xml b/src/main/res/layout/conversation_list_row.xml index 4ac7440c..cde94087 100644 --- a/src/main/res/layout/conversation_list_row.xml +++ b/src/main/res/layout/conversation_list_row.xml @@ -9,30 +9,32 @@ android:id="@+id/conversationListRowFrame" android:background="@color/primaryBackground"> - <View - android:layout_width="fill_parent" - android:layout_height="fill_parent" - android:background="@color/red500"/> + <View + android:layout_width="fill_parent" + android:layout_height="match_parent" + android:background="@color/red500"/> - <FrameLayout - android:id="@+id/swipeable_item" - android:layout_width="fill_parent" - android:layout_height="fill_parent" - android:background="@color/grey50"> + <FrameLayout + android:id="@+id/swipeable_item" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:background="@color/grey50"> - <RelativeLayout - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:orientation="horizontal" - android:padding="8dp" + <RelativeLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:background="?android:selectableItemBackground" + android:orientation="horizontal" + android:padding="8dp" android:id="@+id/conversationListRowContent"> - <ImageView - android:id="@+id/conversation_image" - android:layout_width="56dp" - android:layout_height="56dp" - android:layout_alignParentLeft="true" - android:scaleType="centerCrop" /> + <com.makeramen.roundedimageview.RoundedImageView + android:id="@+id/conversation_image" + android:layout_width="56dp" + android:layout_height="56dp" + android:layout_alignParentLeft="true" + android:scaleType="centerCrop" + app:riv_corner_radius="2dp"/> <TextView android:layout_width="56dp" @@ -43,61 +45,82 @@ android:paddingTop="8dp" android:paddingRight="8dp"/> - <RelativeLayout - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:layout_centerVertical="true" - android:layout_toRightOf="@+id/conversation_image" - android:paddingLeft="8dp"> + <RelativeLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + android:layout_toRightOf="@+id/conversation_image" + android:paddingLeft="8dp"> + <TextView + android:id="@+id/conversation_name" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignLeft="@+id/conversation_lastwrapper" + android:layout_toLeftOf="@+id/conversation_lastupdate" + android:paddingRight="4dp" + android:singleLine="true" + android:text="Awesome groupchat" + android:textColor="@color/primaryText" + android:textSize="?attr/TextSizeHeadline" + android:typeface="sans"/> - <TextView - android:id="@+id/conversation_name" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignLeft="@+id/conversation_lastwrapper" - android:layout_toLeftOf="@+id/conversation_lastupdate" - android:singleLine="true" - android:textColor="@color/primaryText" - android:textSize="?attr/TextSizeHeadline" - android:typeface="sans" /> + <RelativeLayout + android:id="@+id/conversation_lastwrapper" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@id/conversation_name" + android:layout_marginTop="4dp"> - <LinearLayout - android:id="@+id/conversation_lastwrapper" - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:layout_below="@id/conversation_name" - android:orientation="vertical" - android:paddingTop="3dp" > + <LinearLayout android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_alignParentLeft="true" + android:layout_centerVertical="true" + android:layout_toLeftOf="@+id/notification_status" + android:orientation="vertical"> <github.ankushsachdeva.emojicon.EmojiconTextView android:id="@+id/conversation_lastmsg" - android:layout_width="fill_parent" + android:layout_width="match_parent" android:layout_height="wrap_content" android:scrollHorizontally="false" android:singleLine="true" + android:text="This is a placeholder text to show the last messages" android:textColor="@color/primaryText" android:textSize="?attr/TextSizeBody" emojicon:emojiconSize="20sp" /> - <ImageView - android:id="@+id/conversation_lastimage" - android:layout_width="fill_parent" - android:layout_height="36dp" - android:background="@color/primaryText" - android:scaleType="centerCrop" /> - </LinearLayout> + <com.makeramen.roundedimageview.RoundedImageView + android:id="@+id/conversation_lastimage" + android:layout_width="fill_parent" + android:layout_height="36dp" + android:background="@color/primaryText" + android:scaleType="centerCrop" + android:visibility="gone" + app:riv_corner_radius="2dp"/> + </LinearLayout> + <ImageView + android:id="@+id/notification_status" + android:layout_width="16sp" + android:layout_height="16sp" + android:layout_alignParentRight="true" + android:layout_centerVertical="true" + android:layout_marginLeft="4dp" + android:src="@drawable/ic_notifications_grey600_24dp" + /> + </RelativeLayout> - <TextView - android:id="@+id/conversation_lastupdate" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignBaseline="@+id/conversation_name" - android:layout_alignParentRight="true" - android:gravity="right" - android:textColor="@color/secondaryText" - android:textSize="?attr/TextSizeInfo" /> - </RelativeLayout> - </RelativeLayout> - </FrameLayout> -</FrameLayout> + <TextView + android:id="@+id/conversation_lastupdate" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignBaseline="@+id/conversation_name" + android:layout_alignParentRight="true" + android:gravity="right" + android:text="23:42" + android:textColor="@color/secondaryText" + android:textSize="?attr/TextSizeInfo"/> + </RelativeLayout> + </RelativeLayout> + </FrameLayout> +</FrameLayout>
\ No newline at end of file diff --git a/src/main/res/layout/create_contact_dialog.xml b/src/main/res/layout/enter_jid_dialog.xml index d4fba603..f75f2674 100644 --- a/src/main/res/layout/create_contact_dialog.xml +++ b/src/main/res/layout/enter_jid_dialog.xml @@ -1,9 +1,12 @@ <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical" - android:padding="8dp" > + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:paddingBottom="8dp" + android:paddingLeft="24dp" + android:paddingRight="24dp" + android:paddingTop="16dp"> <TextView android:id="@+id/your_account" @@ -11,12 +14,12 @@ android:layout_height="wrap_content" android:text="@string/your_account" android:textColor="@color/primaryText" - android:textSize="?attr/TextSizeBody" /> + android:textSize="?attr/TextSizeBody"/> <Spinner android:id="@+id/account" android:layout_width="fill_parent" - android:layout_height="wrap_content" /> + android:layout_height="wrap_content"/> <TextView android:id="@+id/jabber_id" @@ -25,7 +28,7 @@ android:layout_marginTop="8dp" android:text="@string/account_settings_jabber_id" android:textColor="@color/primaryText" - android:textSize="?attr/TextSizeBody" /> + android:textSize="?attr/TextSizeBody"/> <AutoCompleteTextView android:id="@+id/jid" @@ -34,6 +37,7 @@ android:hint="@string/account_settings_example_jabber_id" android:inputType="textEmailAddress" android:textColor="@color/primaryText" - android:textColorHint="@color/secondaryText" /> + android:textColorHint="@color/secondaryText" + android:textSize="?attr/TextSizeBody" /> </LinearLayout>
\ No newline at end of file diff --git a/src/main/res/layout/form_boolean.xml b/src/main/res/layout/form_boolean.xml new file mode 100644 index 00000000..fd553acb --- /dev/null +++ b/src/main/res/layout/form_boolean.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:paddingTop="8dp" + android:orientation="vertical"> + <CheckBox + android:id="@+id/field" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textColor="@color/black87" + android:textSize="?attr/TextSizeBody"/> +</LinearLayout>
\ No newline at end of file diff --git a/src/main/res/layout/form_text.xml b/src/main/res/layout/form_text.xml new file mode 100644 index 00000000..31b521e8 --- /dev/null +++ b/src/main/res/layout/form_text.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:paddingTop="8dp" + android:orientation="vertical"> + <TextView + android:id="@+id/label" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="@color/black87" + android:textSize="?attr/TextSizeBody"/> + + <EditText + android:id="@+id/field" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textColor="@color/black87" + android:textColorHint="@color/black54" + android:textSize="?attr/TextSizeBody"/> +</LinearLayout>
\ No newline at end of file diff --git a/src/main/res/layout/fragment_conversation.xml b/src/main/res/layout/fragment_conversation.xml index a17cba72..d4c380d3 100644 --- a/src/main/res/layout/fragment_conversation.xml +++ b/src/main/res/layout/fragment_conversation.xml @@ -4,7 +4,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" - android:background="@color/grey200"> + android:background="@color/grey200" > <com.orangegangsters.github.swipyrefreshlayout.library.SwipyRefreshLayout android:layout_width="fill_parent" @@ -27,7 +27,7 @@ android:listSelector="@android:color/transparent" android:stackFromBottom="true" android:transcriptMode="normal" - tools:listitem="@layout/message_sent" > + tools:listitem="@layout/message_sent"> </ListView> </com.orangegangsters.github.swipyrefreshlayout.library.SwipyRefreshLayout> @@ -43,6 +43,7 @@ android:id="@+id/textinput" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_alignParentLeft="true" android:layout_toLeftOf="@+id/textSendButton" android:layout_toRightOf="@+id/emoji_btn" android:background="@color/grey50" diff --git a/src/main/res/layout/join_conference_dialog.xml b/src/main/res/layout/join_conference_dialog.xml index ef113017..d172142a 100644 --- a/src/main/res/layout/join_conference_dialog.xml +++ b/src/main/res/layout/join_conference_dialog.xml @@ -1,9 +1,12 @@ <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical" - android:padding="8dp" > + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:paddingBottom="8dp" + android:paddingLeft="24dp" + android:paddingRight="24dp" + android:paddingTop="16dp"> <TextView android:id="@+id/your_account" @@ -34,7 +37,8 @@ android:hint="@string/conference_address_example" android:inputType="textEmailAddress" android:textColor="@color/primaryText" - android:textColorHint="@color/secondaryText" /> + android:textColorHint="@color/secondaryText" + android:textSize="?attr/TextSizeBody"/> <CheckBox android:id="@+id/bookmark" @@ -42,6 +46,8 @@ android:layout_height="wrap_content" android:layout_marginTop="8dp" android:checked="true" - android:text="@string/save_as_bookmark" /> + android:text="@string/save_as_bookmark" + android:textColor="@color/primaryText" + android:textSize="?attr/TextSizeBody"/> </LinearLayout>
\ No newline at end of file diff --git a/src/main/res/layout/message_received.xml b/src/main/res/layout/message_received.xml index f16f551a..389b0f48 100644 --- a/src/main/res/layout/message_received.xml +++ b/src/main/res/layout/message_received.xml @@ -1,14 +1,23 @@ <?xml version="1.0" encoding="utf-8"?> -<RelativeLayout - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:emojicon="http://schemas.android.com/apk/res-auto" - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:orientation="vertical" - android:paddingBottom="4dp" - android:paddingLeft="8dp" - android:paddingRight="8dp" - android:paddingTop="4dp" > +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:emojicon="http://schemas.android.com/apk/res-auto" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:paddingBottom="3dp" + android:paddingLeft="8dp" + android:paddingRight="8dp" + android:paddingTop="3dp"> + + <com.makeramen.roundedimageview.RoundedImageView + android:id="@+id/message_photo" + android:layout_width="48dp" + android:layout_height="48dp" + android:layout_alignParentLeft="true" + android:layout_alignParentTop="true" + android:scaleType="fitXY" + android:src="@drawable/ic_profile" + app:riv_corner_radius="2dp" /> <LinearLayout android:id="@+id/message_box" @@ -16,28 +25,28 @@ android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_toRightOf="@+id/message_photo" - android:background="@drawable/message_border" - android:minHeight="48dp" + android:background="@drawable/message_bubble_received" + android:minHeight="53dp" + android:layout_marginTop="-2dp" + android:layout_marginRight="-4dp" android:longClickable="true"> <LinearLayout android:layout_width="wrap_content" android:layout_height="fill_parent" - android:background="@color/grey50" + android:background="@color/primarybackground" android:gravity="center_vertical" android:orientation="vertical" - android:paddingBottom="4dp" - android:paddingLeft="5dp" - android:paddingRight="5dp" - android:paddingTop="4dp" > + android:padding="2dp"> <ImageView android:id="@+id/message_image" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:layout_marginBottom="4dp" android:adjustViewBounds="true" - android:background="@color/primaryText" - android:paddingBottom="2dp" + android:background="@color/black87" android:scaleType="centerCrop" /> <github.ankushsachdeva.emojicon.EmojiconTextView @@ -45,7 +54,9 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:autoLink="web|phone|email" - android:textColor="@color/primaryText" + android:textColorLink="@color/white" + android:textColor="@color/white" + android:textColorHighlight="@color/grey800" android:textSize="?attr/TextSizeBody" emojicon:emojiconSize="28sp" /> @@ -59,8 +70,20 @@ <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_gravity="left" android:orientation="horizontal" - android:paddingTop="1dp" > + android:paddingBottom="2dp"> + + <TextView + android:id="@+id/message_encryption" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:gravity="center_vertical" + android:layout_marginRight="4sp" + android:textColor="@color/white70" + android:textStyle="bold" + android:textSize="?attr/TextSizeInfo" /> <ImageView android:id="@+id/security_indicator" @@ -68,9 +91,9 @@ android:layout_height="?attr/TextSizeInfo" android:layout_gravity="center_vertical" android:layout_marginRight="4sp" - android:alpha="0.54" + android:alpha="0.70" android:gravity="center_vertical" - android:src="@drawable/ic_secure_indicator" /> + android:src="@drawable/ic_secure_indicator_white" /> <TextView android:id="@+id/message_time" @@ -79,21 +102,10 @@ android:layout_gravity="center_vertical" android:gravity="center_vertical" android:text="@string/sending" - android:textColor="@color/secondaryText" + android:textColor="@color/white70" android:textSize="?attr/TextSizeInfo" /> </LinearLayout> </LinearLayout> </LinearLayout> - <ImageView - android:id="@+id/message_photo" - android:layout_width="48dp" - android:layout_height="48dp" - android:layout_alignParentLeft="true" - android:layout_alignParentTop="true" - android:layout_marginRight="-1.5dp" - android:padding="0dp" - android:scaleType="fitXY" - android:src="@drawable/ic_profile" /> - </RelativeLayout>
\ No newline at end of file diff --git a/src/main/res/layout/message_sent.xml b/src/main/res/layout/message_sent.xml index 00368194..d75f4bdb 100644 --- a/src/main/res/layout/message_sent.xml +++ b/src/main/res/layout/message_sent.xml @@ -1,14 +1,25 @@ <?xml version="1.0" encoding="utf-8"?> -<RelativeLayout - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:emojicon="http://schemas.android.com/apk/res-auto" - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:orientation="vertical" - android:paddingBottom="4dp" - android:paddingLeft="8dp" - android:paddingRight="8dp" - android:paddingTop="4dp" > +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:emojicon="http://schemas.android.com/apk/res-auto" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:paddingBottom="3dp" + android:paddingLeft="8dp" + android:paddingRight="8dp" + android:paddingTop="3dp"> + + <com.makeramen.roundedimageview.RoundedImageView + android:id="@+id/message_photo" + android:layout_width="48dp" + android:layout_height="48dp" + android:scaleType="fitXY" + android:paddingBottom="3dp" + android:src="@drawable/ic_profile" + android:layout_alignParentBottom="true" + android:layout_alignParentRight="true" + android:layout_alignParentEnd="true" + app:riv_corner_radius="2dp" /> <LinearLayout android:id="@+id/message_box" @@ -16,40 +27,39 @@ android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_toLeftOf="@+id/message_photo" - android:background="@drawable/message_border" - android:minHeight="48dp" + android:background="@drawable/message_bubble_sent" + android:minHeight="53dp" + android:layout_marginLeft="-4dp" android:longClickable="true"> <LinearLayout android:layout_width="wrap_content" android:layout_height="fill_parent" - android:background="@color/grey50" android:gravity="center_vertical" android:orientation="vertical" - android:paddingBottom="4dp" - android:paddingLeft="5dp" - android:paddingRight="5dp" - android:paddingTop="4dp" > + android:padding="2dp"> <ImageView android:id="@+id/message_image" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:layout_marginBottom="4dp" android:adjustViewBounds="true" android:background="@color/primaryText" - android:paddingBottom="2dp" android:scaleType="centerCrop" /> - <github.ankushsachdeva.emojicon.EmojiconTextView + <TextView android:id="@+id/message_body" android:layout_width="wrap_content" android:layout_height="wrap_content" android:autoLink="web|phone|email" + android:textColorLink="@color/primaryText" android:textColor="@color/primaryText" - android:textSize="?attr/TextSizeBody" - emojicon:emojiconSize="28sp" /> - - <Button + android:textColorHighlight="@color/grey500" + android:textSize="?attr/TextSizeBody" /> + + <Button android:id="@+id/download_button" style="?android:attr/buttonStyleSmall" android:layout_width="wrap_content" @@ -61,7 +71,7 @@ android:layout_height="wrap_content" android:layout_gravity="right" android:orientation="horizontal" - android:paddingTop="1dp" > + android:paddingBottom="2dp"> <TextView android:id="@+id/message_time" @@ -96,15 +106,4 @@ </LinearLayout> </LinearLayout> - <ImageView - android:id="@+id/message_photo" - android:layout_width="48dp" - android:layout_height="48dp" - android:layout_alignParentBottom="true" - android:layout_alignParentRight="true" - android:layout_marginLeft="-1.5dp" - android:padding="0dp" - android:scaleType="fitXY" - android:src="@drawable/ic_profile" /> - </RelativeLayout>
\ No newline at end of file diff --git a/src/main/res/layout/message_status.xml b/src/main/res/layout/message_status.xml index 412fbde0..aa02e154 100644 --- a/src/main/res/layout/message_status.xml +++ b/src/main/res/layout/message_status.xml @@ -1,14 +1,15 @@ <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:orientation="vertical" - android:paddingBottom="6dp" - android:paddingLeft="8dp" - android:paddingRight="6dp" - android:paddingTop="6dp" > + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:paddingBottom="5dp" + android:paddingLeft="8dp" + android:paddingRight="8dp" + android:paddingTop="5dp"> - <ImageView + <com.makeramen.roundedimageview.RoundedImageView android:id="@+id/message_photo" android:layout_width="32dp" android:layout_height="32dp" @@ -17,7 +18,8 @@ android:layout_marginRight="-1.5dp" android:padding="0dp" android:scaleType="fitXY" - android:src="@drawable/ic_profile" /> + android:src="@drawable/ic_profile" + app:riv_corner_radius="1dp"/> <TextView android:id="@+id/status_message" diff --git a/src/main/res/menu/choose_contact.xml b/src/main/res/menu/choose_contact.xml index c025d00f..94b6479b 100644 --- a/src/main/res/menu/choose_contact.xml +++ b/src/main/res/menu/choose_contact.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<menu xmlns:android="http://schemas.android.com/apk/res/android" > +<menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/action_search" @@ -8,4 +8,10 @@ android:showAsAction="collapseActionView|always" android:title="@string/search"/> -</menu>
\ No newline at end of file + <item + android:id="@+id/action_create_contact" + android:icon="?attr/icon_add_person" + android:showAsAction="always" + android:title="@string/create_contact" + android:visible="false"/> +</menu> diff --git a/src/main/res/menu/editaccount.xml b/src/main/res/menu/editaccount.xml index 4ce9e1f3..b7a40418 100644 --- a/src/main/res/menu/editaccount.xml +++ b/src/main/res/menu/editaccount.xml @@ -11,6 +11,13 @@ android:showAsAction="never" /> <item + android:id="@+id/action_renew_certificate" + android:title="@string/action_renew_certificate" + android:visible="false" + android:showAsAction="never" /> + /> + + <item android:id="@+id/action_server_info_show_more" android:title="@string/server_info_show_more" android:checkable="true" @@ -20,4 +27,13 @@ <item android:id="@+id/action_change_password_on_server" android:title="@string/change_password" android:showAsAction="never" /> + + <item android:id="@+id/action_clear_devices" + android:title="@string/clear_other_devices" + android:showAsAction="never"/> + <item + android:id="@+id/action_settings" + android:orderInCategory="100" + android:showAsAction="never" + android:title="@string/action_settings"/> </menu>
\ No newline at end of file diff --git a/src/main/res/menu/encryption_choices.xml b/src/main/res/menu/encryption_choices.xml index adf0ad8d..ab42a206 100644 --- a/src/main/res/menu/encryption_choices.xml +++ b/src/main/res/menu/encryption_choices.xml @@ -4,7 +4,10 @@ <group android:checkableBehavior="single" > <item android:id="@+id/encryption_choice_none" - android:title="@string/encryption_choice_none"/> + android:title="@string/encryption_choice_unencrypted"/> + <item + android:id="@+id/encryption_choice_axolotl" + android:title="@string/encryption_choice_omemo"/> <item android:id="@+id/encryption_choice_otr" android:title="@string/encryption_choice_otr"/> diff --git a/src/main/res/menu/manageaccounts.xml b/src/main/res/menu/manageaccounts.xml index f8a30ff7..ffa692a0 100644 --- a/src/main/res/menu/manageaccounts.xml +++ b/src/main/res/menu/manageaccounts.xml @@ -7,6 +7,12 @@ android:showAsAction="always" android:title="@string/action_add_account"/> <item + android:id="@+id/action_add_account_with_cert" + android:showAsAction="never" + android:icon="?attr/icon_add_person" + android:title="@string/action_add_account_with_certificate" + android:visible="true"/> + <item android:id="@+id/action_enable_all" android:title="@string/enable_all_accounts"/> <item diff --git a/src/main/res/menu/muc_details_context.xml b/src/main/res/menu/muc_details_context.xml index dc0f5d3e..af5e691b 100644 --- a/src/main/res/menu/muc_details_context.xml +++ b/src/main/res/menu/muc_details_context.xml @@ -3,30 +3,38 @@ <item android:id="@+id/start_conversation" android:title="@string/start_conversation" - android:visible="false" /> - <item - android:id="@+id/give_membership" + android:visible="false"/> + <item + android:id="@+id/action_contact_details" + android:title="@string/action_contact_details" + android:visible="false"/> + <item + android:id="@+id/send_private_message" + android:title="@string/send_private_message" + android:visible="false"/> + <item + android:id="@+id/give_membership" android:title="@string/grant_membership" - android:visible="false" /> - <item - android:id="@+id/give_admin_privileges" + android:visible="false"/> + <item + android:id="@+id/give_admin_privileges" android:title="@string/grant_admin_privileges" - android:visible="false"/> - <item - android:id="@+id/remove_admin_privileges" + android:visible="false"/> + <item + android:id="@+id/remove_admin_privileges" android:title="@string/remove_admin_privileges" - android:visible="false"/> + android:visible="false"/> - <item - android:id="@+id/remove_membership" - android:title="@string/remove_membership" - android:visible="false"/> - <item - android:id="@+id/ban_from_conference" - android:title="@string/ban_from_conference" - android:visible="false" /> - <item - android:id="@+id/remove_from_room" + <item + android:id="@+id/remove_membership" + android:title="@string/remove_membership" + android:visible="false"/> + <item + android:id="@+id/ban_from_conference" + android:title="@string/ban_from_conference" + android:visible="false"/> + <item + android:id="@+id/remove_from_room" android:title="@string/remove_from_room" - android:visible="false"/> -</menu>
\ No newline at end of file + android:visible="false"/> +</menu> diff --git a/src/main/res/values-ar-rEG/strings.xml b/src/main/res/values-ar-rEG/strings.xml deleted file mode 100644 index 79590fd6..00000000 --- a/src/main/res/values-ar-rEG/strings.xml +++ /dev/null @@ -1,319 +0,0 @@ -<?xml version='1.0' encoding='UTF-8'?> -<resources> - <string name="action_settings">إعدادات</string> - <string name="action_add">محادثة جديدة</string> - <string name="action_accounts">إدارة الحسابات</string> - <string name="action_end_conversation">إنهاء المحادثة</string> - <string name="action_contact_details">بيانات جهة الإتصال</string> - <string name="action_muc_details">بيانات الغرفة</string> - <string name="action_secure">تشفير المحادثة</string> - <string name="action_add_account">إضافة حساب</string> - <string name="action_edit_contact">تعديل الإسم</string> - <string name="action_add_phone_book">إضافة لسجل الهاتف</string> - <string name="action_delete_contact">حذف من الإضافات</string> - <string name="action_block_contact">حجب جهة إتصال</string> - <string name="action_unblock_contact">إنهاء حجب جهة اتصال</string> - <string name="action_block_domain">حجب دومين</string> - <string name="action_unblock_domain">إنهاء حجب دومين</string> - <string name="title_activity_manage_accounts">إدارة الحسابات</string> - <string name="title_activity_settings">إعدادات</string> - <string name="title_activity_conference_details">بيانات الغرفه</string> - <string name="title_activity_contact_details">بيانات جهة الإتصال</string> - <string name="title_activity_sharewith">مشاركة مع محادثة</string> - <string name="title_activity_start_conversation">بدأ محادثة</string> - <string name="title_activity_choose_contact">إختيار جهة اتصال</string> - <string name="title_activity_block_list">قائمة المحجوبين</string> - <string name="just_now">الآن</string> - <string name="minute_ago">منذ 1 دقيقة</string> - <string name="minutes_ago">دقائق %d منذ</string> - <string name="unread_conversations">محادثات غير مقروءة</string> - <string name="sending">ارسال</string> - <string name="encrypted_message">رسالة مشفّرة .. الرجاء الإنتظار</string> - <string name="nick_in_use">اللقب مستخدم من قبل</string> - <string name="admin">مدير</string> - <string name="owner">مالك</string> - <string name="moderator">مشرف</string> - <string name="participant">مشترك</string> - <string name="visitor">زائر</string> - <string name="remove_contact_text">هل ترغب بحذف جهة الإتصال %s من اضافاتك? ستبقى محادثاتك معه محفوظه</string> - <string name="block_contact_text">هل ترغب في حجب %s من ارسال الرسائل لك?</string> - <string name="unblock_contact_text">هل ترغب في انهاء حجب %s والسماح له بمراسلتك?</string> - <string name="block_domain_text">هل تريد حجب جميع جهات الإتصال من %s?</string> - <string name="unblock_domain_text">الغاء حجب جميع جهات الإتصال من %s?</string> - <string name="contact_blocked">جهة الاتصال محجوبه</string> - <string name="remove_bookmark_text">هل ترغب في حذف %s من المفضلات? المحادثات المحفوظه ستبقى كما هي</string> - <string name="register_account">تسجيل حساب جديد في سيرفر</string> - <string name="change_password_on_server">تغيير كلمة المرور في سيرفر</string> - <string name="share_with">مشاركة مع</string> - <string name="start_conversation">بدأ محادثة</string> - <string name="invite_contact">دعوة صديق</string> - <string name="contacts">جهات الإتصال</string> - <string name="cancel">الغاء</string> - <string name="set">تعيين</string> - <string name="add">اضافة</string> - <string name="edit">تعديل</string> - <string name="delete">حذف</string> - <string name="block">حجب</string> - <string name="unblock">الغاء حجب</string> - <string name="save">حفظ</string> - <string name="ok">موافق</string> - <string name="crash_report_title">توقف التطبيق عن العمل</string> - <string name="crash_report_message">بموافقتك على ارسال تقارير الأخطاء ستساعد مبرمجي التطبيق على تحسين جودة التطبيق\n<b>تنويه:</b> سيرسل تقارير الأخطاء ان وجدت بأحد الحسابات التي أضفتها.</string> - <string name="send_now">ارسال الآن</string> - <string name="send_never">لا تسألني ثانية</string> - <string name="problem_connecting_to_account">لا يمكن تسجيل الدخول لحسابك</string> - <string name="problem_connecting_to_accounts">لا يمكن تسجيل الدخول بحساباتك</string> - <string name="touch_to_fix">المس الشاشه لعرض المحادثات</string> - <string name="attach_file">ارفاق ملف</string> - <string name="not_in_roster">جهة الاتصال ليست مضافه لديك هل ترغب في إضافتها ؟؟</string> - <string name="add_contact">اضافة جهة اتصال</string> - <string name="send_failed">فشل التسليم</string> - <string name="send_rejected">مرفوض</string> - <string name="preparing_image">اعداد صورة للإرسال</string> - <string name="action_clear_history">حذف سجل المحفوظات</string> - <string name="clear_conversation_history">حذف سجل المحفوظات للمحادثة</string> - <string name="clear_histor_msg">هل ترغب بحذف جميع الرسائل في تلك المحادثة?\n\n<b>تنويه:</b> هذا لن يؤثر على الرسائل المخزنة على الأجهزة أو أي أماكن أخرى.</string> - <string name="delete_messages">حذف الرسائل</string> - <string name="also_end_conversation">انهاء هذه المحادثة بعد الكلمات</string> - <string name="choose_presence">اختيار ظهورك لجهات الإتصال</string> - <string name="send_plain_text_message">ارسال رسالة غير مشفّرة</string> - <string name="send_otr_message">OTRارساله رساله مشفره عبر</string> - <string name="send_pgp_message">OpenPGPارساله رساله مشفره عبر</string> - <string name="your_nick_has_been_changed">تم تغيير لقبك بنجاح</string> - <string name="send_unencrypted">إرسال بدون تشفير</string> - <string name="decryption_failed">فشل فك التشفير. ربما لم يكن لديك المفتاح الخاص الصحيح.</string> - <string name="openkeychain_required">OpenKeychain</string> - <string name="openkeychain_required_long">Conversations :: يستخدم تطبيق آخر يسمى <b> OpenKeychain </b> لتشفير وفك تشفير الرسائل وإدارة المفاتيح العامة الخاصة بك \n\nOpenKeychain تحت الرخصة GPLv3 و لتحميل التطبيق من جوجل بلاي \n\n <small>(وأعد تشغيل التطبيق مرة أخرى)</small></string> - <string name="restart">اعادة تشغيل</string> - <string name="install">تثبيت</string> - <string name="offering">عرض ..</string> - <string name="waiting">انتظار ..</string> - <string name="no_pgp_key">OpenPGP-لايوجد مفتاح</string> - <string name="contact_has_no_pgp_key">Conversations::لا يستطيع تشفير الرساله\n\n<small>من فضلك أخبر صديقك بتنصيب تطبيق OpenPGP.</small></string> - <string name="no_pgp_keys">OpenPGP-لايوجد مفاتيح</string> - <string name="contacts_have_no_pgp_keys">Conversations::لا يستطيع تشفير الرساله\n\n<small>من فضلك أخبر صديقك بتنصيب تطبيق OpenPGP.</small></string> - <string name="encrypted_message_received"><i>تلقيت رساله مشفّرة .. لمسه بأناملك لعرضها.</i></string> - <string name="pref_general">عام</string> - <string name="pref_xmpp_resource">الريسورس</string> - <string name="pref_accept_files">ضبط استقبال الملفات</string> - <string name="pref_accept_files_size_summary">اقبل تلقائيا الملفات أقل من</string> - <string name="pref_notification_settings">اعدادات الإشعارات</string> - <string name="pref_notifications">الإشعارات</string> - <string name="pref_notifications_summary">أخبرني عندما تصل رساله جديده</string> - <string name="pref_vibrate">إعداد الإهتزاز</string> - <string name="pref_vibrate_summary">تفعيل الاهتزاز عندما تصل رساله جديده</string> - <string name="pref_sound">التبيه الصوتي</string> - <string name="pref_sound_summary">سماع صوت عندما تصل رساله</string> - <string name="pref_conference_notifications">الاشعارات من الغرف</string> - <string name="pref_advanced_options">اعدادات متقدمّة</string> - <string name="pref_never_send_crash">لا ترسل تقارير أخطاء</string> - <string name="pref_never_send_crash_summary">الغاء ارسال تقارير الأخطاء يقلل من فرص حل المشكلة سريعا فكن متعاون</string> - <string name="pref_confirm_messages">تأكيد الرسالة</string> - <string name="accept">قبول</string> - <string name="error">حدث خطأ ما</string> - <string name="pref_grant_presence_updates">منح تحديثات الظهور</string> - <string name="pref_grant_presence_updates_summary">اسأل واقبل السؤال عن تحديثات الظهور</string> - <string name="your_account">حسابك</string> - <string name="keys">مفاتيح</string> - <string name="send_presence_updates">ارسال تحديثات الظهور</string> - <string name="attach_choose_picture">اختيار صورة</string> - <string name="attach_take_picture">التقاط صورة</string> - <string name="error_file_not_found">الملف غير موجود</string> - <string name="account_status_unknown">غير معروف</string> - <string name="account_status_online">متصل</string> - <string name="account_status_offline">غير متصل</string> - <string name="account_status_unauthorized">غير مصرح له</string> - <string name="account_status_not_found">لا يمكن الاتصال بالسرفر</string> - <string name="account_status_no_internet">تحقق من اتصالك بالانترنت</string> - <string name="account_status_regis_fail">فشل تسجيل حساب بالسيرفر</string> - <string name="account_status_regis_conflict">اسم المستخدم مستخدم من قبل</string> - <string name="account_status_regis_success">تم تسجيل حسابك بنجاح</string> - <string name="account_status_regis_not_sup">تسجيل الحسابات غير متاح على هذا السرفر</string> - <string name="encryption_choice_none">رساله عادية</string> - <string name="encryption_choice_otr">OTRرسالة مشفرة عبر</string> - <string name="encryption_choice_pgp">OpenPGPرسالة مشفرة عبر</string> - <string name="mgmt_account_edit">تعديل الحساب</string> - <string name="mgmt_account_delete">حذف الحساب</string> - <string name="mgmt_account_publish_avatar">نشر الصورة الرمزية</string> - <string name="mgmt_account_enable">تفعيل الحساب</string> - <string name="mgmt_account_are_you_sure">هل أنت متأكد ؟</string> - <string name="mgmt_account_delete_confirm_text">اذا مسحت حسابك ستفقد جميع الرسائل المحفوظه !!</string> - <string name="attach_record_voice">تسجيل صوت</string> - <string name="account_settings_jabber_id">حساب جابر</string> - <string name="account_settings_password">كلمة السر</string> - <string name="account_settings_example_jabber_id">username@example.com</string> - <string name="account_settings_confirm_password">تأكيد كلمة السر</string> - <string name="password">كلمة السر</string> - <string name="confirm_password">تأكيد كلمة السر</string> - <string name="passwords_do_not_match">الكلمتان غير متطابقتان</string> - <string name="invalid_jid">حساب جابر غير صالح</string> - <string name="contact_status_online">متصل</string> - <string name="contact_status_free_to_chat">متاح للدردشة</string> - <string name="contact_status_away">بعيد</string> - <string name="contact_status_extended_away">بعيد جدا</string> - <string name="contact_status_do_not_disturb">مشغول</string> - <string name="contact_status_offline">غير متصل</string> - <string name="muc_details_conference">الغرف</string> - <string name="muc_details_other_members">المشتركين</string> - <string name="server_info_show_more">معلومات السرفر</string> - <string name="server_info_available">متاح</string> - <string name="server_info_unavailable">غير متاح</string> - <string name="last_seen_now">آخر ظهور الآن</string> - <string name="last_seen_min">آخر ظهور منذ 1 دقيقة</string> - <string name="last_seen_mins">آخر ظهور منذ %d دقيقة</string> - <string name="last_seen_hour">آخر ظهور منذ 1 ساعة</string> - <string name="last_seen_hours">آخر ظهور منذ %d ساعة</string> - <string name="last_seen_day">آخر ظهور منذ 1 يوم</string> - <string name="last_seen_days">آخر ظهور منذ %d يوم</string> - <string name="never_seen">لم يظهر متصلا حتى الآن</string> - <string name="verify">تأكيد</string> - <string name="conferences">الغرف</string> - <string name="search">بحث</string> - <string name="create_contact">اضافة جهة اتصال</string> - <string name="join_conference">دخول الغرف</string> - <string name="delete_contact">حذف جهة اتصال</string> - <string name="view_contact_details">عرض بيانات جهة الاتصال</string> - <string name="block_contact">حجب جهة اتصال</string> - <string name="unblock_contact">الغاء حجب جهة اتصال</string> - <string name="create">أضف</string> - <string name="contact_already_exists">جهة الاتصال موجودة لديك مسبقا</string> - <string name="join">دخول</string> - <string name="conference_address">اسم الغرفة كامل</string> - <string name="conference_address_example">room@conference.example.com</string> - <string name="save_as_bookmark">حفظ بالمفضلة</string> - <string name="delete_bookmark">حذف من المفضلة</string> - <string name="bookmark_already_exists">موجوده بالمفضلة سابقا</string> - <string name="you">انت</string> - <string name="action_edit_subject">تعديل موضوع الغرفة</string> - <string name="conference_not_found">الغرفة غير متاحه .. تأكد من عنوان الغرفة</string> - <string name="leave">غادر</string> - <string name="contact_added_you">جهة اتصال أضافتك </string> - <string name="publish">نشر</string> - <string name="publishing">نشر ...</string> - <string name="private_message_to">الى %s</string> - <string name="send_private_message_to">ارسال رسالة خاصة الى %s</string> - <string name="connect">اتصال</string> - <string name="account_already_exists">الحساب موجود من قبل</string> - <string name="next">التالي</string> - <string name="additional_information">معلومات اضافية</string> - <string name="skip">تجاهل</string> - <string name="disable_notifications">ايقاف التنبيهات</string> - <string name="disable_notifications_for_this_conversation">ايقاف التنبيهات لتلك المحادثة</string> - <string name="notifications_disabled">التنبيهات غير فعاله</string> - <string name="enable">تفعيل</string> - <string name="conference_requires_password">الغرفة تطلب كلمة مرور</string> - <string name="enter_password">أدخل كلمة المرور</string> - <string name="request_now">اطلب الآن</string> - <string name="ignore">تجاهل</string> - <string name="pref_expert_options_other">أخرى</string> - <string name="pref_conference_name">اسم الغرفة</string> - <string name="conference_banned">أنت مفصول في هذه الغرفة</string> - <string name="conference_members_only">الغرفة للأعضاء فقط</string> - <string name="conference_kicked">تم طردك من الغرفة</string> - <string name="not_connected_try_again">انقطع الإتصال .. حاول مرة أخرى</string> - <string name="message_options">خيارات الرساله</string> - <string name="copy_text">نسخ النص</string> - <string name="message_text">نص الرسالة</string> - <string name="account_details">تفاصيل الحساب</string> - <string name="scan">سكان</string> - <string name="confirm">تأكيد</string> - <string name="in_progress">جاري الاتصال</string> - <string name="failed">فشل الاتصال</string> - <string name="try_again">حاول مرة أخرى</string> - <string name="finish">انهاء</string> - <string name="verified">تأكيد!</string> - <string name="conversations_foreground_service">Conversations</string> - <string name="pref_keep_foreground_service">احتفظ بالتطبيق يعمل في المقدمة</string> - <string name="pref_keep_foreground_service_summary">منع نظام التشغيل من انهاء اتصالك</string> - <string name="choose_file">اختيار ملف</string> - <string name="receiving_x_file">اكتمل الإستلام %1$s (%2$d%% بنسبة)</string> - <string name="download_x_file">تنزيل %s</string> - <string name="file">ملف</string> - <string name="open_x_file">فتح %s</string> - <string name="sending_file">إكتمل الإرسال (%1$d%% بنسبة)</string> - <string name="preparing_file">إعداد ارسال الملفات</string> - <string name="x_file_offered_for_download">%s عرض وتنزيل</string> - <string name="cancel_transmission">الغاء الارسال</string> - <string name="file_transmission_failed">تعذر ارسال الملف</string> - <string name="file_deleted">تم مسح الملف</string> - <string name="no_application_found_to_open_file">لا يوجد تطبيق متاح لعرض الملف</string> - <string name="could_not_verify_fingerprint">لا يمكن التحقق من البصمة</string> - <string name="manually_verify">تأكيد يدوي</string> - <string name="are_you_sure_verify_fingerprint">هل ترغب في تأكيد بصمات OTR لجهات اتصالك ؟</string> - <string name="pref_show_dynamic_tags">عرض العلامات التلقائية</string> - <string name="pref_show_dynamic_tags_summary">عرض العلامات للقراءة فقط أسفل بيانات جهات الإتصال </string> - <string name="enable_notifications">تفعيل الإشعارات</string> - <string name="conference_with">انشاء غرفة بــ </string> - <string name="no_conference_server_found">سيرفر الغرف غير موجود</string> - <string name="conference_creation_failed">فشل انشاء الغرفة</string> - <string name="conference_created">تم انشاء الرغرفة بنجاح</string> - <string name="secret_accepted">الاجابة السريّة مقبولة</string> - <string name="reset">إعادة تعيين</string> - <string name="account_image_description">الصورة الرمزية للحساب</string> - <string name="copy_otr_clipboard_description">نسخ OTR بصمات الأصابع إلى الحافظة</string> - <string name="fetching_history_from_server">جلب المحفوظات من السرفر</string> - <string name="no_more_history_on_server">لا مزيد من المحفوظات بالسرفر</string> - <string name="updating">جاري التحديث..</string> - <string name="password_changed">تم تغيير كلمة السر!</string> - <string name="could_not_change_password">لايمكن تغيير كلمة السر</string> - <string name="otr_session_not_started">ارسل رساله لبدأ محادثة مشفّرة</string> - <string name="ask_question">أسال سؤال</string> - <string name="smp_explain_answer">سترغب جهة الإتصال بتأكيد بصمتك عبر السر المشترك بينكما لذلك أخبره تلميحا أو إسأله سؤالا يذكره بالسر ليكتبه برده</string> - <string name="shared_secret_hint_should_not_be_empty">التلميح الذي يراه صديقك لا يمكن ان يكون فارغ</string> - <string name="shared_secret_can_not_be_empty">السر المشترك بينكما لا يمكن ان يترك فارغا !!</string> - <string name="manual_verification_explanation">بعناية قارن بين بصمتك المعروضه أدناه وبصمة جهات اتصالك\n يمكنك استخدام أي شكل موثوق به للاتصال مثل بريد إلكتروني مشفر أو مكالمة هاتفية لتبادلها.</string> - <string name="change_password">تغيير كلمة المرور</string> - <string name="current_password">كلمة المرور الحالية</string> - <string name="new_password">كلمة مرور جديدة</string> - <string name="password_should_not_be_empty">كلمة المرور لا يمكن ان تبقى فارغة</string> - <string name="enable_all_accounts">تفعيل كل الحسابات</string> - <string name="disable_all_accounts">تعطيل كل الحسابات</string> - <string name="perform_action_with">تنفيذ الإجراء مع</string> - <string name="no_affiliation">زائر</string> - <string name="no_role">لا دور</string> - <string name="outcast">مفصول</string> - <string name="member">عضو</string> - <string name="advanced_mode">الوضع المتقدم</string> - <string name="grant_membership">منح عضوية</string> - <string name="remove_membership">إلغاء عضوية</string> - <string name="grant_admin_privileges">منح امتيازات الإداره</string> - <string name="remove_admin_privileges">إلغاء امتيازات الإدارة</string> - <string name="remove_from_room">إزالة من الغرفة</string> - <string name="could_not_change_affiliation">لا يمكن تغيير انتساب %s</string> - <string name="ban_from_conference">حظر من الغرفة</string> - <string name="removing_from_public_conference">تحاول انهاء تواجد %s الغرفه. سيتم فصله</string> - <string name="ban_now">حظر الآن</string> - <string name="could_not_change_role">لا يمكن تغيير دول %s</string> - <string name="public_conference">الوصول العام للغرفة</string> - <string name="private_conference">الخاص , اعضاء الغرفة فقط</string> - <string name="conference_options">اعدادت المؤتمر</string> - <string name="members_only">الخاص (الأعضاء فقط)</string> - <string name="non_anonymous">لا تسمح للمجهولين بالدخول</string> - <string name="modified_conference_options">تم تعديل اعدادات المؤتمر!</string> - <string name="could_not_modify_conference_options">لا يمكن تعديل اعدادات المؤتمر</string> - <string name="never">أبداً</string> - <string name="thirty_minutes">30 دقيقة</string> - <string name="one_hour">ساعة</string> - <string name="two_hours">ساعتين</string> - <string name="eight_hours">8 ساعات</string> - <string name="until_further_notice">حتى إشعار آخر</string> - <string name="pref_input_options">خيارات الادخال</string> - <string name="pref_enter_is_send">أدخل للإرسال</string> - <string name="pref_enter_is_send_summary">استخدام مفتاح الدخول لإرسال رسالة</string> - <string name="pref_display_enter_key">عرض مفتاح الادخال</string> - <string name="pref_display_enter_key_summary">تغيير مفتاح الرموز إلى مفتاح الدخول</string> - <string name="audio">صوت</string> - <string name="video">فيديو</string> - <string name="image">صورة</string> - <string name="pdf_document">مستند PDF</string> - <string name="apk">تطبيق اندرويد</string> - <string name="vcard">تواصل</string> - <string name="received_x_file">تم التلقي %s</string> - <string name="disable_foreground_service">ايقاف عرض تنويهات الخدمات على رئيسية الهاتف</string> - <string name="touch_to_open_conversations">ألمس لفتح المحادثات</string> - <string name="avatar_has_been_published">تم نشر الصورة!</string> - <string name="sending_x_file">ارسال %s</string> - <string name="offering_x_file">عرض %s</string> - <string name="hide_offline">اخفاء غير المتصلين</string> - <string name="disable_account">ايقاف الحساب</string> -</resources> diff --git a/src/main/res/values-ar-rSY/strings.xml b/src/main/res/values-ar-rSY/strings.xml deleted file mode 100644 index c757504a..00000000 --- a/src/main/res/values-ar-rSY/strings.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version='1.0' encoding='UTF-8'?> -<resources/> diff --git a/src/main/res/values-ar/strings.xml b/src/main/res/values-ar/strings.xml index c757504a..b87e7179 100644 --- a/src/main/res/values-ar/strings.xml +++ b/src/main/res/values-ar/strings.xml @@ -1,2 +1,311 @@ <?xml version='1.0' encoding='UTF-8'?> -<resources/> +<resources> + <string name="action_settings">إعدادات</string> + <string name="action_add">محادثة جديدة</string> + <string name="action_accounts">إدارة الحسابات</string> + <string name="action_end_conversation">إنهاء المحادثة</string> + <string name="action_contact_details">بيانات جهة الإتصال</string> + <string name="action_muc_details">بيانات الغرفة</string> + <string name="action_secure">تشفير المحادثة</string> + <string name="action_add_account">إضافة حساب</string> + <string name="action_edit_contact">تعديل الإسم</string> + <string name="action_delete_contact">حذف من الإضافات</string> + <string name="action_block_contact">حجب جهة إتصال</string> + <string name="action_unblock_contact">إنهاء حجب جهة اتصال</string> + <string name="action_block_domain">حجب دومين</string> + <string name="action_unblock_domain">إنهاء حجب دومين</string> + <string name="title_activity_manage_accounts">إدارة الحسابات</string> + <string name="title_activity_settings">إعدادات</string> + <string name="title_activity_conference_details">بيانات الغرفه</string> + <string name="title_activity_contact_details">بيانات جهة الإتصال</string> + <string name="title_activity_sharewith">مشاركة مع محادثة</string> + <string name="title_activity_start_conversation">بدأ محادثة</string> + <string name="title_activity_choose_contact">إختيار جهة اتصال</string> + <string name="title_activity_block_list">قائمة المحجوبين</string> + <string name="just_now">الآن</string> + <string name="minute_ago">منذ 1 دقيقة</string> + <string name="minutes_ago">دقائق %d منذ</string> + <string name="unread_conversations">محادثات غير مقروءة</string> + <string name="sending">ارسال</string> + <string name="nick_in_use">اللقب مستخدم من قبل</string> + <string name="admin">مدير</string> + <string name="owner">مالك</string> + <string name="moderator">مشرف</string> + <string name="participant">مشترك</string> + <string name="visitor">زائر</string> + <string name="remove_contact_text">هل ترغب بحذف جهة الإتصال %s من اضافاتك? ستبقى محادثاتك معه محفوظه</string> + <string name="block_contact_text">هل ترغب في حجب %s من ارسال الرسائل لك?</string> + <string name="unblock_contact_text">هل ترغب في انهاء حجب %s والسماح له بمراسلتك?</string> + <string name="block_domain_text">هل تريد حجب جميع جهات الإتصال من %s?</string> + <string name="unblock_domain_text">الغاء حجب جميع جهات الإتصال من %s?</string> + <string name="contact_blocked">جهة الاتصال محجوبه</string> + <string name="remove_bookmark_text">هل ترغب في حذف %s من المفضلات? المحادثات المحفوظه ستبقى كما هي</string> + <string name="register_account">تسجيل حساب جديد في سيرفر</string> + <string name="change_password_on_server">تغيير كلمة المرور في سيرفر</string> + <string name="share_with">مشاركة مع</string> + <string name="start_conversation">بدأ محادثة</string> + <string name="invite_contact">دعوة صديق</string> + <string name="contacts">جهات الإتصال</string> + <string name="cancel">الغاء</string> + <string name="set">تعيين</string> + <string name="add">اضافة</string> + <string name="edit">تعديل</string> + <string name="delete">حذف</string> + <string name="block">حجب</string> + <string name="unblock">الغاء حجب</string> + <string name="save">حفظ</string> + <string name="ok">موافق</string> + <string name="crash_report_title">توقف التطبيق عن العمل</string> + <string name="crash_report_message">بموافقتك على ارسال تقارير الأخطاء ستساعد مبرمجي التطبيق على تحسين جودة التطبيق\n<b>تنويه:</b> سيرسل تقارير الأخطاء ان وجدت بأحد الحسابات التي أضفتها.</string> + <string name="send_now">ارسال الآن</string> + <string name="send_never">لا تسألني ثانية</string> + <string name="problem_connecting_to_account">لا يمكن تسجيل الدخول لحسابك</string> + <string name="problem_connecting_to_accounts">لا يمكن تسجيل الدخول بحساباتك</string> + <string name="touch_to_fix">المس الشاشه لعرض المحادثات</string> + <string name="attach_file">ارفاق ملف</string> + <string name="not_in_roster">جهة الاتصال ليست مضافه لديك هل ترغب في إضافتها ؟؟</string> + <string name="add_contact">اضافة جهة اتصال</string> + <string name="send_failed">فشل التسليم</string> + <string name="send_rejected">مرفوض</string> + <string name="preparing_image">اعداد صورة للإرسال</string> + <string name="action_clear_history">حذف سجل المحفوظات</string> + <string name="clear_conversation_history">حذف سجل المحفوظات للمحادثة</string> + <string name="clear_histor_msg">هل ترغب بحذف جميع الرسائل في تلك المحادثة?\n\n<b>تنويه:</b> هذا لن يؤثر على الرسائل المخزنة على الأجهزة أو أي أماكن أخرى.</string> + <string name="delete_messages">حذف الرسائل</string> + <string name="choose_presence">اختيار ظهورك لجهات الإتصال</string> + <string name="send_otr_message">OTRارساله رساله مشفره عبر</string> + <string name="send_pgp_message">OpenPGPارساله رساله مشفره عبر</string> + <string name="your_nick_has_been_changed">تم تغيير لقبك بنجاح</string> + <string name="send_unencrypted">إرسال بدون تشفير</string> + <string name="decryption_failed">فشل فك التشفير. ربما لم يكن لديك المفتاح الخاص الصحيح.</string> + <string name="openkeychain_required">OpenKeychain</string> + <string name="openkeychain_required_long">Conversations :: يستخدم تطبيق آخر يسمى <b> OpenKeychain </b> لتشفير وفك تشفير الرسائل وإدارة المفاتيح العامة الخاصة بك \n\nOpenKeychain تحت الرخصة GPLv3 و لتحميل التطبيق من جوجل بلاي \n\n <small>(وأعد تشغيل التطبيق مرة أخرى)</small></string> + <string name="restart">اعادة تشغيل</string> + <string name="install">تثبيت</string> + <string name="offering">عرض ..</string> + <string name="waiting">انتظار ..</string> + <string name="no_pgp_key">OpenPGP-لايوجد مفتاح</string> + <string name="contact_has_no_pgp_key">Conversations::لا يستطيع تشفير الرساله\n\n<small>من فضلك أخبر صديقك بتنصيب تطبيق OpenPGP.</small></string> + <string name="no_pgp_keys">OpenPGP-لايوجد مفاتيح</string> + <string name="contacts_have_no_pgp_keys">Conversations::لا يستطيع تشفير الرساله\n\n<small>من فضلك أخبر صديقك بتنصيب تطبيق OpenPGP.</small></string> + <string name="pref_general">عام</string> + <string name="pref_xmpp_resource">الريسورس</string> + <string name="pref_accept_files">ضبط استقبال الملفات</string> + <string name="pref_accept_files_summary">اقبل تلقائيا الملفات أقل من</string> + <string name="pref_notification_settings">اعدادات الإشعارات</string> + <string name="pref_notifications">الإشعارات</string> + <string name="pref_notifications_summary">أخبرني عندما تصل رساله جديده</string> + <string name="pref_vibrate">إعداد الإهتزاز</string> + <string name="pref_vibrate_summary">تفعيل الاهتزاز عندما تصل رساله جديده</string> + <string name="pref_sound">التبيه الصوتي</string> + <string name="pref_sound_summary">سماع صوت عندما تصل رساله</string> + <string name="pref_advanced_options">اعدادات متقدمّة</string> + <string name="pref_never_send_crash">لا ترسل تقارير أخطاء</string> + <string name="pref_never_send_crash_summary">الغاء ارسال تقارير الأخطاء يقلل من فرص حل المشكلة سريعا فكن متعاون</string> + <string name="pref_confirm_messages">تأكيد الرسالة</string> + <string name="accept">قبول</string> + <string name="error">حدث خطأ ما</string> + <string name="pref_grant_presence_updates">منح تحديثات الظهور</string> + <string name="pref_grant_presence_updates_summary">اسأل واقبل السؤال عن تحديثات الظهور</string> + <string name="your_account">حسابك</string> + <string name="keys">مفاتيح</string> + <string name="send_presence_updates">ارسال تحديثات الظهور</string> + <string name="attach_choose_picture">اختيار صورة</string> + <string name="attach_take_picture">التقاط صورة</string> + <string name="error_file_not_found">الملف غير موجود</string> + <string name="account_status_unknown">غير معروف</string> + <string name="account_status_online">متصل</string> + <string name="account_status_offline">غير متصل</string> + <string name="account_status_unauthorized">غير مصرح له</string> + <string name="account_status_not_found">لا يمكن الاتصال بالسرفر</string> + <string name="account_status_no_internet">تحقق من اتصالك بالانترنت</string> + <string name="account_status_regis_fail">فشل تسجيل حساب بالسيرفر</string> + <string name="account_status_regis_conflict">اسم المستخدم مستخدم من قبل</string> + <string name="account_status_regis_success">تم تسجيل حسابك بنجاح</string> + <string name="account_status_regis_not_sup">تسجيل الحسابات غير متاح على هذا السرفر</string> + <string name="encryption_choice_otr">OTRرسالة مشفرة عبر</string> + <string name="encryption_choice_pgp">OpenPGPرسالة مشفرة عبر</string> + <string name="mgmt_account_edit">تعديل الحساب</string> + <string name="mgmt_account_delete">حذف الحساب</string> + <string name="mgmt_account_publish_avatar">نشر الصورة الرمزية</string> + <string name="mgmt_account_enable">تفعيل الحساب</string> + <string name="mgmt_account_are_you_sure">هل أنت متأكد ؟</string> + <string name="mgmt_account_delete_confirm_text">اذا مسحت حسابك ستفقد جميع الرسائل المحفوظه !!</string> + <string name="attach_record_voice">تسجيل صوت</string> + <string name="account_settings_jabber_id">حساب جابر</string> + <string name="account_settings_password">كلمة السر</string> + <string name="account_settings_example_jabber_id">username@example.com</string> + <string name="account_settings_confirm_password">تأكيد كلمة السر</string> + <string name="password">كلمة السر</string> + <string name="confirm_password">تأكيد كلمة السر</string> + <string name="passwords_do_not_match">الكلمتان غير متطابقتان</string> + <string name="invalid_jid">حساب جابر غير صالح</string> + <string name="contact_status_online">متصل</string> + <string name="contact_status_free_to_chat">متاح للدردشة</string> + <string name="contact_status_away">بعيد</string> + <string name="contact_status_extended_away">بعيد جدا</string> + <string name="contact_status_do_not_disturb">مشغول</string> + <string name="contact_status_offline">غير متصل</string> + <string name="muc_details_conference">الغرف</string> + <string name="muc_details_other_members">المشتركين</string> + <string name="server_info_show_more">معلومات السرفر</string> + <string name="server_info_available">متاح</string> + <string name="server_info_unavailable">غير متاح</string> + <string name="last_seen_now">آخر ظهور الآن</string> + <string name="last_seen_min">آخر ظهور منذ 1 دقيقة</string> + <string name="last_seen_mins">آخر ظهور منذ %d دقيقة</string> + <string name="last_seen_hour">آخر ظهور منذ 1 ساعة</string> + <string name="last_seen_hours">آخر ظهور منذ %d ساعة</string> + <string name="last_seen_day">آخر ظهور منذ 1 يوم</string> + <string name="last_seen_days">آخر ظهور منذ %d يوم</string> + <string name="never_seen">لم يظهر متصلا حتى الآن</string> + <string name="verify">تأكيد</string> + <string name="conferences">الغرف</string> + <string name="search">بحث</string> + <string name="create_contact">اضافة جهة اتصال</string> + <string name="join_conference">دخول الغرف</string> + <string name="delete_contact">حذف جهة اتصال</string> + <string name="view_contact_details">عرض بيانات جهة الاتصال</string> + <string name="block_contact">حجب جهة اتصال</string> + <string name="unblock_contact">الغاء حجب جهة اتصال</string> + <string name="create">أضف</string> + <string name="contact_already_exists">جهة الاتصال موجودة لديك مسبقا</string> + <string name="join">دخول</string> + <string name="conference_address">اسم الغرفة كامل</string> + <string name="conference_address_example">room@conference.example.com</string> + <string name="save_as_bookmark">حفظ بالمفضلة</string> + <string name="delete_bookmark">حذف من المفضلة</string> + <string name="bookmark_already_exists">موجوده بالمفضلة سابقا</string> + <string name="you">انت</string> + <string name="action_edit_subject">تعديل موضوع الغرفة</string> + <string name="conference_not_found">الغرفة غير متاحه .. تأكد من عنوان الغرفة</string> + <string name="leave">غادر</string> + <string name="contact_added_you">جهة اتصال أضافتك </string> + <string name="publish">نشر</string> + <string name="publishing">نشر ...</string> + <string name="private_message_to">الى %s</string> + <string name="send_private_message_to">ارسال رسالة خاصة الى %s</string> + <string name="connect">اتصال</string> + <string name="account_already_exists">الحساب موجود من قبل</string> + <string name="next">التالي</string> + <string name="additional_information">معلومات اضافية</string> + <string name="skip">تجاهل</string> + <string name="disable_notifications">ايقاف التنبيهات</string> + <string name="disable_notifications_for_this_conversation">ايقاف التنبيهات لتلك المحادثة</string> + <string name="enable">تفعيل</string> + <string name="conference_requires_password">الغرفة تطلب كلمة مرور</string> + <string name="enter_password">أدخل كلمة المرور</string> + <string name="request_now">اطلب الآن</string> + <string name="ignore">تجاهل</string> + <string name="pref_expert_options_other">أخرى</string> + <string name="pref_conference_name">اسم الغرفة</string> + <string name="conference_banned">أنت مفصول في هذه الغرفة</string> + <string name="conference_members_only">الغرفة للأعضاء فقط</string> + <string name="conference_kicked">تم طردك من الغرفة</string> + <string name="not_connected_try_again">انقطع الإتصال .. حاول مرة أخرى</string> + <string name="message_options">خيارات الرساله</string> + <string name="copy_text">نسخ النص</string> + <string name="message_text">نص الرسالة</string> + <string name="account_details">تفاصيل الحساب</string> + <string name="scan">سكان</string> + <string name="confirm">تأكيد</string> + <string name="in_progress">جاري الاتصال</string> + <string name="failed">فشل الاتصال</string> + <string name="try_again">حاول مرة أخرى</string> + <string name="finish">انهاء</string> + <string name="verified">تأكيد!</string> + <string name="conversations_foreground_service">Conversations</string> + <string name="pref_keep_foreground_service">احتفظ بالتطبيق يعمل في المقدمة</string> + <string name="pref_keep_foreground_service_summary">منع نظام التشغيل من انهاء اتصالك</string> + <string name="choose_file">اختيار ملف</string> + <string name="receiving_x_file">اكتمل الإستلام %1$s (%2$d%% بنسبة)</string> + <string name="download_x_file">تنزيل %s</string> + <string name="file">ملف</string> + <string name="open_x_file">فتح %s</string> + <string name="sending_file">إكتمل الإرسال (%1$d%% بنسبة)</string> + <string name="preparing_file">إعداد ارسال الملفات</string> + <string name="x_file_offered_for_download">%s عرض وتنزيل</string> + <string name="cancel_transmission">الغاء الارسال</string> + <string name="file_transmission_failed">تعذر ارسال الملف</string> + <string name="file_deleted">تم مسح الملف</string> + <string name="no_application_found_to_open_file">لا يوجد تطبيق متاح لعرض الملف</string> + <string name="could_not_verify_fingerprint">لا يمكن التحقق من البصمة</string> + <string name="manually_verify">تأكيد يدوي</string> + <string name="are_you_sure_verify_fingerprint">هل ترغب في تأكيد بصمات OTR لجهات اتصالك ؟</string> + <string name="pref_show_dynamic_tags">عرض العلامات التلقائية</string> + <string name="pref_show_dynamic_tags_summary">عرض العلامات للقراءة فقط أسفل بيانات جهات الإتصال </string> + <string name="enable_notifications">تفعيل الإشعارات</string> + <string name="conference_with">انشاء غرفة بــ </string> + <string name="no_conference_server_found">سيرفر الغرف غير موجود</string> + <string name="conference_creation_failed">فشل انشاء الغرفة</string> + <string name="conference_created">تم انشاء الرغرفة بنجاح</string> + <string name="secret_accepted">الاجابة السريّة مقبولة</string> + <string name="reset">إعادة تعيين</string> + <string name="account_image_description">الصورة الرمزية للحساب</string> + <string name="copy_otr_clipboard_description">نسخ OTR بصمات الأصابع إلى الحافظة</string> + <string name="fetching_history_from_server">جلب المحفوظات من السرفر</string> + <string name="no_more_history_on_server">لا مزيد من المحفوظات بالسرفر</string> + <string name="updating">جاري التحديث..</string> + <string name="password_changed">تم تغيير كلمة السر!</string> + <string name="could_not_change_password">لايمكن تغيير كلمة السر</string> + <string name="otr_session_not_started">ارسل رساله لبدأ محادثة مشفّرة</string> + <string name="ask_question">أسال سؤال</string> + <string name="smp_explain_answer">سترغب جهة الإتصال بتأكيد بصمتك عبر السر المشترك بينكما لذلك أخبره تلميحا أو إسأله سؤالا يذكره بالسر ليكتبه برده</string> + <string name="shared_secret_hint_should_not_be_empty">التلميح الذي يراه صديقك لا يمكن ان يكون فارغ</string> + <string name="shared_secret_can_not_be_empty">السر المشترك بينكما لا يمكن ان يترك فارغا !!</string> + <string name="manual_verification_explanation">بعناية قارن بين بصمتك المعروضه أدناه وبصمة جهات اتصالك\n يمكنك استخدام أي شكل موثوق به للاتصال مثل بريد إلكتروني مشفر أو مكالمة هاتفية لتبادلها.</string> + <string name="change_password">تغيير كلمة المرور</string> + <string name="current_password">كلمة المرور الحالية</string> + <string name="new_password">كلمة مرور جديدة</string> + <string name="password_should_not_be_empty">كلمة المرور لا يمكن ان تبقى فارغة</string> + <string name="enable_all_accounts">تفعيل كل الحسابات</string> + <string name="disable_all_accounts">تعطيل كل الحسابات</string> + <string name="perform_action_with">تنفيذ الإجراء مع</string> + <string name="no_affiliation">زائر</string> + <string name="no_role">لا دور</string> + <string name="outcast">مفصول</string> + <string name="member">عضو</string> + <string name="advanced_mode">الوضع المتقدم</string> + <string name="grant_membership">منح عضوية</string> + <string name="remove_membership">إلغاء عضوية</string> + <string name="grant_admin_privileges">منح امتيازات الإداره</string> + <string name="remove_admin_privileges">إلغاء امتيازات الإدارة</string> + <string name="remove_from_room">إزالة من الغرفة</string> + <string name="could_not_change_affiliation">لا يمكن تغيير انتساب %s</string> + <string name="ban_from_conference">حظر من الغرفة</string> + <string name="removing_from_public_conference">تحاول انهاء تواجد %s الغرفه. سيتم فصله</string> + <string name="ban_now">حظر الآن</string> + <string name="could_not_change_role">لا يمكن تغيير دول %s</string> + <string name="public_conference">الوصول العام للغرفة</string> + <string name="private_conference">الخاص , اعضاء الغرفة فقط</string> + <string name="conference_options">اعدادت المؤتمر</string> + <string name="non_anonymous">لا تسمح للمجهولين بالدخول</string> + <string name="modified_conference_options">تم تعديل اعدادات المؤتمر!</string> + <string name="could_not_modify_conference_options">لا يمكن تعديل اعدادات المؤتمر</string> + <string name="never">أبداً</string> + <string name="thirty_minutes">30 دقيقة</string> + <string name="one_hour">ساعة</string> + <string name="two_hours">ساعتين</string> + <string name="eight_hours">8 ساعات</string> + <string name="until_further_notice">حتى إشعار آخر</string> + <string name="pref_input_options">خيارات الادخال</string> + <string name="pref_enter_is_send">أدخل للإرسال</string> + <string name="pref_enter_is_send_summary">استخدام مفتاح الدخول لإرسال رسالة</string> + <string name="pref_display_enter_key">عرض مفتاح الادخال</string> + <string name="pref_display_enter_key_summary">تغيير مفتاح الرموز إلى مفتاح الدخول</string> + <string name="audio">صوت</string> + <string name="video">فيديو</string> + <string name="image">صورة</string> + <string name="pdf_document">مستند PDF</string> + <string name="apk">تطبيق اندرويد</string> + <string name="vcard">تواصل</string> + <string name="received_x_file">تم التلقي %s</string> + <string name="disable_foreground_service">ايقاف عرض تنويهات الخدمات على رئيسية الهاتف</string> + <string name="touch_to_open_conversations">ألمس لفتح المحادثات</string> + <string name="avatar_has_been_published">تم نشر الصورة!</string> + <string name="sending_x_file">ارسال %s</string> + <string name="offering_x_file">عرض %s</string> + <string name="hide_offline">اخفاء غير المتصلين</string> + <string name="disable_account">ايقاف الحساب</string> + <string name="dialog_manage_certs_negativebutton">الغاء</string> +</resources> diff --git a/src/main/res/values-bg/strings.xml b/src/main/res/values-bg/strings.xml index 73e64331..01d019a6 100644 --- a/src/main/res/values-bg/strings.xml +++ b/src/main/res/values-bg/strings.xml @@ -9,7 +9,7 @@ <string name="action_secure">Защитен разговор</string> <string name="action_add_account">Добавяне на профил</string> <string name="action_edit_contact">Редактиране на името</string> - <string name="action_add_phone_book">Добавяне към списъка с телефонни номера</string> + <string name="action_add_phone_book">Добавяне към адресния указател</string> <string name="action_delete_contact">Изтриване от списъка</string> <string name="action_block_contact">Блокиране на контакта</string> <string name="action_unblock_contact">Деблокиране на контакта</string> @@ -27,8 +27,9 @@ <string name="minute_ago">преди 1 минута</string> <string name="minutes_ago">преди %d минути</string> <string name="unread_conversations">непрочетени разговори</string> - <string name="sending">изпращане...</string> - <string name="encrypted_message">Дешифроване на съобщението. Моля, изчакайте...</string> + <string name="sending">изпращане…</string> + <string name="message_decrypting">Дешифроване на съобщението. Моля, изчакайте…</string> + <string name="pgp_message">Съобщение, шифр. чрез OpenPGP</string> <string name="nick_in_use">Псевдонимът вече се използва</string> <string name="admin">Администратор</string> <string name="owner">Собственик</string> @@ -44,7 +45,7 @@ <string name="remove_bookmark_text">Искате ли да премахнете отметката за %s? Разговорът, свързан с тази отметка, няма да бъде премахнат.</string> <string name="register_account">Регистриране на нов профил на сървъра</string> <string name="change_password_on_server">Промяна на паролата в сървъра</string> - <string name="share_with">Споделяне с...</string> + <string name="share_with">Споделяне с…</string> <string name="start_conversation">Започване на разговор</string> <string name="invite_contact">Канене на контакт</string> <string name="contacts">Контакти</string> @@ -74,11 +75,13 @@ <string name="clear_conversation_history">Изчистване на историята на разговорите</string> <string name="clear_histor_msg">Искате ли да изтриете всички съобщения от този разговор?\n\n<b>Внимание:</b> Това няма да изтрие съобщенията, съхранявани на други устройства или на сървърите.</string> <string name="delete_messages">Изтриване на съобщенията</string> - <string name="also_end_conversation">Този разговор да приключи след това</string> + <string name="also_end_conversation">Приключване на този разговор след това</string> <string name="choose_presence">Изберете присъствие за контакта</string> - <string name="send_plain_text_message">Изпращане на обикновено текстово съобщение</string> - <string name="send_otr_message">Изпращане на съобщение, шифровано чрез OTP</string> - <string name="send_pgp_message">Изпращане на съобщение, шифровано чрез OpenPGP</string> + <string name="send_unencrypted_message">Изпр. на нешифр. съобщение</string> + <string name="send_otr_message">Изпр. на съобщение, шифр. чрез OTP</string> + <string name="send_omemo_message">Изпр. на съобщение, шифр. чрез OMEMO</string> + <string name="send_omemo_x509_message">Изпр. на съобщение, шифр. чрез v\\OMEMO</string> + <string name="send_pgp_message">Изпр. на съобщение, шифр. чрез OpenPGP</string> <string name="your_nick_has_been_changed">Псевдонимът Ви беше променен</string> <string name="send_unencrypted">Изпращане нешифровано</string> <string name="decryption_failed">Неуспешно дешифроване. Възможно е да нямате правилния частен ключ.</string> @@ -86,18 +89,19 @@ <string name="openkeychain_required_long">Conversations използва външно приложение с име <b>OpenKeychain</b>, за да шифрова и дешифрова съобщенията и да управлява публичните Ви ключове.\n\nOpenKeychain е лицензирано под условията на GPLv3 и е налично в F-Droid и Google Play.\n\n<small>(Моля, рестартирайте Conversations след това.)</small></string> <string name="restart">Рестартиране</string> <string name="install">Инсталиране</string> - <string name="offering">предлагане...</string> - <string name="waiting">изчакване...</string> + <string name="openkeychain_not_installed">Моля, инсталирайте OpenKeychain</string> + <string name="offering">предлагане…</string> + <string name="waiting">изчакване…</string> <string name="no_pgp_key">Не е открит OpenPGP ключ</string> <string name="contact_has_no_pgp_key">Conversations не може да шифрова съобщенията Ви, тъй като Вашият контакт не обявява публичния си ключ.\n\n<small>Моля, помолете го/я да инсталира и настрои OpenPGP.</small></string> <string name="no_pgp_keys">Не са открити OpenPGP ключове</string> <string name="contacts_have_no_pgp_keys">Conversations не може да шифрова съобщенията Ви, тъй като Вашите контакти не обявяват публичните си ключове.\n\n<small>Моля, помолете го да инсталират и настроят OpenPGP.</small></string> - <string name="encrypted_message_received"><i>Получено е шифровано съобщение. Докоснете, за да го прегледате и дешифровате.</i></string> + <string name="encrypted_message_received"><i>Получено е шифровано съобщение. Докоснете, за да го дешифровате.</i></string> <string name="pref_general">Общи</string> <string name="pref_xmpp_resource">XMPP ресурс</string> <string name="pref_xmpp_resource_summary">Името, с което се определя този клиент</string> <string name="pref_accept_files">Приемане на файлове</string> - <string name="pref_accept_files_size_summary">Автоматично приемане на файлове с размер, по-малък от...</string> + <string name="pref_accept_files_summary">Автоматично приемане на файлове с размер, по-малък от…</string> <string name="pref_notification_settings">Настройки за известията</string> <string name="pref_notifications">Известия</string> <string name="pref_notifications_summary">Известяване при получаване на ново съобщение</string> @@ -105,8 +109,6 @@ <string name="pref_vibrate_summary">Също така да има и вибрация при получаване на ново съобщение</string> <string name="pref_sound">Звук</string> <string name="pref_sound_summary">Изпълнение на звук с известието</string> - <string name="pref_conference_notifications">Известия за беседите</string> - <string name="pref_conference_notifications_summary">Известяване винаги, когато пристигне ново съобщение в беседа, а не само когато тя е отбелязана</string> <string name="pref_notification_grace_period">Продължителност на отсрочване на известията</string> <string name="pref_notification_grace_period_summary">Изключва известията за кратко, след като бъде получено копие на съобщение</string> <string name="pref_advanced_options">Разширени настройки</string> @@ -149,9 +151,10 @@ <string name="account_status_regis_not_sup">Сървърът не поддържа регистриране</string> <string name="account_status_security_error">Грешка в сигурността</string> <string name="account_status_incompatible_server">Несъвместим сървър</string> - <string name="encryption_choice_none">Обикновен текст</string> + <string name="encryption_choice_unencrypted">Нешифровано</string> <string name="encryption_choice_otr">OTR</string> <string name="encryption_choice_pgp">OpenPGP</string> + <string name="encryption_choice_omemo">OMEMO</string> <string name="mgmt_account_edit">Редактиране на профила</string> <string name="mgmt_account_delete">Изтриване на профила</string> <string name="mgmt_account_disable">Временно деактивиране</string> @@ -170,7 +173,7 @@ <string name="passwords_do_not_match">Паролите са различни</string> <string name="invalid_jid">Това не е правилен Jabber идентификатор</string> <string name="error_out_of_memory">Няма достатъчно памет. Изображението е твърде голямо.</string> - <string name="add_phone_book_text">Искате ли да добавите %s в списъка си от телефонни контакти?</string> + <string name="add_phone_book_text">Искате ли да добавите %s към адресния си указател?</string> <string name="contact_status_online">на линия</string> <string name="contact_status_free_to_chat">свободен за разговор</string> <string name="contact_status_away">отсъстващ</string> @@ -186,7 +189,8 @@ <string name="server_info_blocking">XEP-0191: Команда за блокиране</string> <string name="server_info_roster_version">XEP-0237: Поддържане на версия на списъка</string> <string name="server_info_stream_management">XEP-0198: Управление на потоците</string> - <string name="server_info_pep">XEP-0163: PEP (Аватари)</string> + <string name="server_info_pep">XEP-0163: PEP (Аватари / OMEMO)</string> + <string name="server_info_http_upload">XEP-0363: Качване на файл през HTTP</string> <string name="server_info_available">налично</string> <string name="server_info_unavailable">не е налично</string> <string name="missing_public_keys">Липсват обявления за публичен ключ</string> @@ -199,22 +203,33 @@ <string name="last_seen_days">последно видян преди %d дни</string> <string name="never_seen">не е виждан никога</string> <string name="install_openkeychain">Шифровано съобщение. Моля, инсталирайте OpenKeychain, за да го дешифровате.</string> - <string name="unknown_otr_fingerprint">Непознат OTR отпечатък</string> + <string name="unknown_otr_fingerprint">Непознат отпечатък OTR</string> <string name="openpgp_messages_found">Открити са съобщения, шифровани чрез OpenPGP</string> <string name="reception_failed">Неуспешно получаване</string> <string name="your_fingerprint">Вашият отпечатък</string> - <string name="otr_fingerprint">OTR отпечатък</string> + <string name="otr_fingerprint">Отпечатък OTR</string> + <string name="omemo_fingerprint">Отпечатък OMEMO</string> + <string name="omemo_fingerprint_x509">Отпечатък v\\OMEMO</string> + <string name="omemo_fingerprint_selected_message">Отпечатък OMEMO на съобщението</string> + <string name="omemo_fingerprint_x509_selected_message">Отпечатък v\\OMEMO на съобщението</string> + <string name="this_device_omemo_fingerprint">Собствен отпечатък OMEMO</string> + <string name="other_devices">Други устройства</string> + <string name="trust_omemo_fingerprints">Доверяване на отпечатъци OMEMO</string> + <string name="fetching_keys">Изтегляне на ключове…</string> + <string name="done">Готово</string> <string name="verify">Потвърждаване</string> <string name="decrypt">Дешифроване</string> <string name="conferences">Беседи</string> <string name="search">Търсене</string> <string name="create_contact">Създаване на контакт</string> + <string name="enter_contact">Въведете контакт</string> <string name="join_conference">Присъединяване към беседа</string> <string name="delete_contact">Изтриване на контакт</string> <string name="view_contact_details">Преглед на подр. за контакта</string> <string name="block_contact">Блокиране на контакт</string> <string name="unblock_contact">Деблокиране на контакт</string> <string name="create">Създаване</string> + <string name="select">Избиране</string> <string name="contact_already_exists">Контактът вече съществува</string> <string name="join">Присъединяване</string> <string name="conference_address">Адрес на беседата</string> @@ -232,7 +247,7 @@ <string name="publish">Публикуване</string> <string name="touch_to_choose_picture">Докоснете аватара, за да изберете изображение от галерията</string> <string name="publish_avatar_explanation">Забележка: Всеки, абониран за актуализации на присъствието Ви, ще може да вижда тази снимка.</string> - <string name="publishing">Публикуване...</string> + <string name="publishing">Публикуване…</string> <string name="error_publish_avatar_server_reject">Сървърът отказа Вашето публикуване</string> <string name="error_publish_avatar_converting">Нещо се обърка при преобразуването на снимката Ви</string> <string name="error_saving_avatar">Неуспешно запазване на аватара на диска</string> @@ -249,7 +264,6 @@ <string name="skip">Пропускане</string> <string name="disable_notifications">Изключване на известията</string> <string name="disable_notifications_for_this_conversation">Изключване на известията за този разговор</string> - <string name="notifications_disabled">Известията са изключени</string> <string name="enable">Включване</string> <string name="conference_requires_password">Беседата изисква парола</string> <string name="enter_password">Въведете парола</string> @@ -283,7 +297,8 @@ <string name="pref_expert_options_other">Други</string> <string name="pref_conference_name">Име на беседата</string> <string name="pref_conference_name_summary">Използване на темата на стаята вместо JID идентификатора за беседите</string> - <string name="toast_message_otr_fingerprint">OTR отпечатъкът е копиран!</string> + <string name="toast_message_otr_fingerprint">Отпечатъкът OTR е копиран!</string> + <string name="toast_message_omemo_fingerprint">Отпечатъкът OMEMO е копиран!</string> <string name="conference_banned">Достъпът Ви до тази беседа беше забранен</string> <string name="conference_members_only">Тази беседа е само за членове</string> <string name="conference_kicked">Бяхте изритан от тази конференция</string> @@ -307,7 +322,6 @@ <string name="verify_otr">Проверка на OTR</string> <string name="remote_fingerprint">Отдалечен отпечатък</string> <string name="scan">сканиране</string> - <string name="or_touch_phones">(или докоснете телефоните)</string> <string name="smp">Протокол „Socialist Millionaire“</string> <string name="shared_secret_hint">Подсказка или въпрос</string> <string name="shared_secret_secret">Обща тайна</string> @@ -324,9 +338,12 @@ <string name="conversations_foreground_service">Conversations</string> <string name="pref_keep_foreground_service">Услугата да е на преден план</string> <string name="pref_keep_foreground_service_summary">Предотвратява прекъсването на връзката Ви от операционната система</string> + <string name="pref_export_logs">Изнасяне на журналите</string> + <string name="pref_export_logs_summary">Записване на журналите в картата с памет</string> + <string name="notification_export_logs_title">Записване на журналите в картата с памет</string> <string name="choose_file">Изберете файл</string> <string name="receiving_x_file">Получаване на %1$s (%2$d%% завършено)</string> - <string name="download_x_file">Изтегляне на %s</string> + <string name="download_x_file">Сваляне на %s</string> <string name="file">файл</string> <string name="open_x_file">Отваряне на %s</string> <string name="sending_file">изпращане (%1$d%% завършено)</string> @@ -338,21 +355,32 @@ <string name="no_application_found_to_open_file">Няма намерено приложение за отваряне на файла</string> <string name="could_not_verify_fingerprint">Неуспешна проверка на отпечатъка</string> <string name="manually_verify">Ръчна проверка</string> - <string name="are_you_sure_verify_fingerprint">Сигурни ли сте, че искате да проверите OTR отпечатъка на контактите си?</string> + <string name="are_you_sure_verify_fingerprint">Сигурни ли сте, че искате да проверите отпечатъка OTR на контактите си?</string> <string name="pref_show_dynamic_tags">Динамични етикети</string> <string name="pref_show_dynamic_tags_summary">Показване на етикети, предназначени само за четене под контактите</string> <string name="enable_notifications">Включване на известията</string> - <string name="conference_with">Започване на беседа с...</string> + <string name="conference_with">Започване на беседа с…</string> <string name="no_conference_server_found">Не е открит сървър за беседата</string> <string name="conference_creation_failed">Неуспешно създаване на беседа!</string> <string name="conference_created">Беседата беше създадена!</string> <string name="secret_accepted">Тайната е приета!</string> <string name="reset">Възстановяване</string> <string name="account_image_description">Аватар на профила</string> - <string name="copy_otr_clipboard_description">Копиране на OTR отпечатъка</string> + <string name="copy_otr_clipboard_description">Копиране на отпечатъка OTR</string> + <string name="copy_omemo_clipboard_description">Копиране на отпечатъка OMEMO</string> + <string name="regenerate_omemo_key">Повторно създаване на ключа OMEMO</string> + <string name="wipe_omemo_pep">Изчистване на другите устройства от PEP</string> + <string name="clear_other_devices">Премахване на устройствата</string> + <string name="clear_other_devices_desc">Сигурни ли сте, че искате да премахнете всички останали устройства от обявлението OMEMO? Следващия път, когато устройствата Ви се свържат, те ще обявят себе си отново, но може да не получат съобщенията, изпратени междувременно.</string> + <string name="purge_key">Изтриване на ключа</string> + <string name="purge_key_desc_part1">Сигурни ли сте, че искате да изтриете този ключ?</string> + <string name="purge_key_desc_part2">След това той ще бъде необратимо приет за грешен и повече няма да можете да създавате сесии с него.</string> + <string name="error_no_keys_to_trust_server_error">Няма ключове, които могат да бъдат използвани за този контакт.\nИзтеглянето на нови ключове от сървъра беше неуспешно. Възможно е да има проблем със сървъра на контактите Ви.</string> + <string name="error_no_keys_to_trust">Няма ключове, които могат да бъдат използвани за този контакт. Ако сте изчистили някои от ключовете му, то той трябва да създаде нови.</string> + <string name="error_trustkeys_title">Грешка</string> <string name="fetching_history_from_server">Получаване на историята от сървъра</string> <string name="no_more_history_on_server">Няма повече история на сървъра</string> - <string name="updating">Актуализиране...</string> + <string name="updating">Актуализиране…</string> <string name="password_changed">Паролата е променена!</string> <string name="could_not_change_password">Неуспешна промяна на паролата</string> <string name="otr_session_not_started">Изпратете съобщение, за да започнете нешифрован разговор</string> @@ -387,8 +415,10 @@ <string name="public_conference">Публично достъпни беседи</string> <string name="private_conference">Частни беседи, само за членове</string> <string name="conference_options">Настройки на беседата</string> - <string name="members_only">Частна (само за членове)</string> + <string name="members_only">Частно, само за членове</string> <string name="non_anonymous">Не-анонимна</string> + <string name="moderated">С модератор</string> + <string name="you_are_not_participating">Вие не участвате</string> <string name="modified_conference_options">Настройките на беседата бяха променени!</string> <string name="could_not_modify_conference_options">Неуспешна промяна на настройките на беседата</string> <string name="never">Никога</string> @@ -416,7 +446,7 @@ <string name="offering_x_file">Предлагане на %s</string> <string name="hide_offline">Скриване на тези извън линия</string> <string name="disable_account">Деактивиране на профила</string> - <string name="contact_is_typing">%s пише...</string> + <string name="contact_is_typing">%s пише…</string> <string name="contact_has_stopped_typing">%s спря да пише</string> <string name="pref_chat_states">Известия за писането</string> <string name="pref_chat_states_summary">Позволяване на контакта Ви да вижда, когато пишете ново съобщение</string> @@ -427,7 +457,6 @@ <string name="received_location">Получено местоположение</string> <string name="title_undo_swipe_out_conversation">Conversation се затвори</string> <string name="title_undo_swipe_out_muc">Напуснахте беседата</string> - <string name="pref_certificate_options">Настройки на сертификата</string> <string name="pref_dont_trust_system_cas_title">Да не се вярва на системните сертификати</string> <string name="pref_dont_trust_system_cas_summary">Всички сертификати трябва да бъдат одобрени на ръка</string> <string name="pref_remove_trusted_certificates_title">Премахване на сертификатите</string> @@ -449,6 +478,73 @@ <string name="none">Нищо</string> <string name="recently_used">Използвани наскоро</string> <string name="choose_quick_action">Изберете бързо действие</string> - <string name="file_not_found_on_remote_host">Файлът не е открит на отдалечения сървър</string> <string name="search_for_contacts_or_groups">Търсене на контакти или групи</string> + <string name="send_private_message">Изпращане на лично съобщение</string> + <string name="user_has_left_conference">%s напусна беседата!</string> + <string name="username">Потребителско име</string> + <string name="username_hint">Потребителско име</string> + <string name="invalid_username">Това не е правилно потребителско име</string> + <string name="download_failed_server_not_found">Неуспешно сваляне: Сървърът не е открит</string> + <string name="download_failed_file_not_found">Неуспешно сваляне: Файлът не е открит</string> + <string name="download_failed_could_not_connect">Неуспешно сваляне: Неуспешна връзка със сървъра</string> + <string name="pref_use_white_background">Използване на бял фон</string> + <string name="pref_use_white_background_summary">Показване на получените съобщения с черен текст на бял фон</string> + <string name="account_status_tor_unavailable">Мрежата на Тор е недостъпна</string> + <string name="server_info_broken">Повредено</string> + <string name="pref_presence_settings">Настройки за присъствието</string> + <string name="pref_away_when_screen_off">Отсъстващ, когато екранът е изключен</string> + <string name="pref_away_when_screen_off_summary">Преминава в състояние „отсъстващ“ когато екранът бъде изключен</string> + <string name="pref_xa_on_silent_mode">Недостъпен, в тих режим</string> + <string name="pref_xa_on_silent_mode_summary">Преминава в състояние „недостъпен“ когато устройството е в тих режим</string> + <string name="action_add_account_with_certificate">Добавяне на профил със сертификат</string> + <string name="unable_to_parse_certificate">Неуспешно прочитане на сертификата</string> + <string name="authenticate_with_certificate">Оставете празно за удостоверяване със сертификат</string> + <string name="captcha_ocr">Текст за проверка</string> + <string name="captcha_required">Проверката е задължителна</string> + <string name="captcha_hint">въведете текста от изображението</string> + <string name="certificate_chain_is_not_trusted">Сертификатът не е потвърден</string> + <string name="jid_does_not_match_certificate">Jabber идентификатора не съответства на сертификата</string> + <string name="action_renew_certificate">Подновяване на сертификата</string> + <string name="error_fetching_omemo_key">Грешка при получаването на ключа за OMEMO!</string> + <string name="verified_omemo_key_with_certificate">Ключът за OMEMO беше потвърден със сертификат!</string> + <string name="device_does_not_support_certificates">Устройството Ви не поддържа избраните клиентски сертификати!</string> + <string name="pref_connection_options">Настройки за връзката</string> + <string name="pref_use_tor">Свързване през Тор</string> + <string name="pref_use_tor_summary">Всички връзки да минават през мрежата на Тор. Изисква Орбот</string> + <string name="account_settings_hostname">Име на сървър</string> + <string name="account_settings_port">Порт</string> + <string name="hostname_or_onion">Адрес на сървър или .onion</string> + <string name="not_a_valid_port">Това не е правилен номер на порт</string> + <string name="not_valid_hostname">Това не е правилно име на сървър</string> + <string name="connected_accounts">%1$d от %2$d свързани профила</string> + <plurals name="x_messages"> + <item quantity="one">%d съобщение</item> + <item quantity="other">%d съобщения</item> + </plurals> + <string name="shared_file_with_x">Файлът е споделен с %s</string> + <string name="shared_image_with_x">Изображението е споделено с %s</string> + <string name="no_storage_permission">Conversations се нуждае от достъп до външно място за съхранение</string> + <string name="sync_with_contacts">Синхронизиране с контактите</string> + <string name="sync_with_contacts_long">Conversations иска да съчетае Вашия списък в XMPP с контактите Ви, за да показва пълните им имена и снимки..\n\nConversations единствено ще чете контактите Ви и ще ги използва вътрешно, без да ги качва на сървъра Ви.\n\nЩе бъдете помолен/а за позволение за достъп до контактите Ви.</string> + <string name="certificate_information">Информация за сертификата</string> + <string name="certificate_subject">Обект</string> + <string name="certificate_issuer">Издател</string> + <string name="certificate_cn">Име</string> + <string name="certificate_o">Организация</string> + <string name="certificate_sha1">Контролна сума SHA1</string> + <string name="certicate_info_not_available">(Няма)</string> + <string name="certificate_not_found">Няма намерен сертификат</string> + <string name="notify_on_all_messages">Известяване за всички съобщения</string> + <string name="notify_only_when_highlighted">Известяване само на осветените</string> + <string name="notify_never">Известията са изключени</string> + <string name="notify_paused">Известията са спрени временно</string> + <string name="pref_picture_compression">Компресиране на снимките</string> + <string name="pref_picture_compression_summary">Преоразмеряване и компресиране на снимките</string> + <string name="always">Винаги</string> + <string name="automatically">Автоматично</string> + <string name="battery_optimizations_enabled">Оптимизациите за използв. на батерията са вкл.</string> + <string name="battery_optimizations_enabled_explained">Устройството Ви прилага сериозни оптимизации за използването на батерията върху Conversations, а те може да доведат до забавени известия и дори пропуснати съобщения.\nПрепоръчително е до ги изключите.</string> + <string name="battery_optimizations_enabled_dialog">Устройството Ви прилага сериозни оптимизации за използването на батерията върху Conversations, а те може да доведат до забавени известия и дори пропуснати съобщения.\n\nСега ще Ви бъде предложено да ги изключите.</string> + <string name="disable">Изключване</string> + <string name="selection_too_large">Избраната област е твърде голяма</string> </resources> diff --git a/src/main/res/values-ca/strings.xml b/src/main/res/values-ca/strings.xml index 6b673071..6e91e2b6 100644 --- a/src/main/res/values-ca/strings.xml +++ b/src/main/res/values-ca/strings.xml @@ -9,7 +9,6 @@ <string name="action_secure">Conversa segura</string> <string name="action_add_account">Afegir compte</string> <string name="action_edit_contact">Edita el nom</string> - <string name="action_add_phone_book">Afegir a l\'agenda</string> <string name="action_delete_contact">Elimina de la llista de contactes</string> <string name="action_block_contact">Bloqueja contacte</string> <string name="action_unblock_contact">Desbloqueja contacte</string> @@ -28,7 +27,6 @@ <string name="minutes_ago">%de minuts abans</string> <string name="unread_conversations">Converses sense llegir o no llegides</string> <string name="sending">enviant…</string> - <string name="encrypted_message">Desxifrant missatge. Espera si us plau…</string> <string name="nick_in_use">El sobrenom ja està en ús</string> <string name="admin">Administrador</string> <string name="owner">Propietari</string> @@ -74,9 +72,7 @@ <string name="clear_conversation_history">Netejar historial de conversa</string> <string name="clear_histor_msg">Vols esborrar tots els missatges d\'aquesta conversa?\n\n<b>Avís:</b> Això no afectarà els missatges desats en altres dispositius o servidors.</string> <string name="delete_messages">Esborrar missatges</string> - <string name="also_end_conversation">Finalitzar aquesta conversa més tard</string> <string name="choose_presence">Selecciona recurs del contacte</string> - <string name="send_plain_text_message">Enviar missatge de text</string> <string name="send_otr_message">Enviar missatge xifrat amb OTR</string> <string name="send_pgp_message">Enviar missatge xifrat amb OpenPGP</string> <string name="your_nick_has_been_changed">El teu sobrenom s\'ha modificat</string> @@ -92,12 +88,11 @@ <string name="contact_has_no_pgp_key">Conversations no ha pogut xifrar els teus missatges perquè el teu contacte no està anunciant la seva clau pública.\n\n<small>Si us plau, demana al teu contacte que configuri OpenPGP.</small></string> <string name="no_pgp_keys">No hi ha claus OPENPGP trobades</string> <string name="contacts_have_no_pgp_keys">Coversations no és possible xifrar les teves converses perquè els teus contactes no han mostrat la seva clau pública.\n\n<small> Si us plau, pregunti als seus contactes per configurar OpenPGP .</small></string> - <string name="encrypted_message_received"><i>Missatge xifrat rebut. Prem per desxifrar i veure-ho.</i></string> <string name="pref_general">General</string> <string name="pref_xmpp_resource">Recursos XMPP</string> <string name="pref_xmpp_resource_summary">El nom que identifica aquest client amb</string> <string name="pref_accept_files">Acceptar fitxers</string> - <string name="pref_accept_files_size_summary">Accepta fitxers automàticament amb una mida menor a…</string> + <string name="pref_accept_files_summary">Accepta fitxers automàticament amb una mida menor a…</string> <string name="pref_notification_settings">Ajustos de notificacions</string> <string name="pref_notifications">Notificacions</string> <string name="pref_notifications_summary">Notifica quan arriba un nou missatge</string> @@ -105,8 +100,6 @@ <string name="pref_vibrate_summary">Vibra quan arriba un nou missatge</string> <string name="pref_sound">So</string> <string name="pref_sound_summary">Reprodueix el to de trucada amb la notificació</string> - <string name="pref_conference_notifications">Notificacions de conferència</string> - <string name="pref_conference_notifications_summary">Sempre notifica quan arriba un nou missatge de conferència en comptes de només quan està destacat</string> <string name="pref_notification_grace_period">Notificació del període d\'espera</string> <string name="pref_notification_grace_period_summary">Desactiva les notificacions durant un breu termini després de rebre una còpia de missatges carbon</string> <string name="pref_advanced_options">Opcions avançades</string> @@ -149,7 +142,6 @@ <string name="account_status_regis_not_sup">El servidor no admet el registre</string> <string name="account_status_security_error">Error de seguretat</string> <string name="account_status_incompatible_server">Servidor incompatible</string> - <string name="encryption_choice_none">Text pla</string> <string name="encryption_choice_otr">OTR</string> <string name="encryption_choice_pgp">OpenPGP</string> <string name="mgmt_account_edit">Edita compte</string> @@ -170,7 +162,6 @@ <string name="passwords_do_not_match">Contrasenyes no coincideixen</string> <string name="invalid_jid">Aquesta identificació de Jabber no és vàlida</string> <string name="error_out_of_memory">Fora de la capacitat de la mèmoria. L\'imatge és massa gran</string> - <string name="add_phone_book_text">Voleu afegir %s a la teva llista de contactes del telèfon?</string> <string name="contact_status_online">En línia</string> <string name="contact_status_free_to_chat">Lliure per xatejar</string> <string name="contact_status_away">Fora</string> @@ -186,7 +177,6 @@ <string name="server_info_blocking">XEP-0191: Ordre de bloqueix</string> <string name="server_info_roster_version">XEP-0237: Plantilla de les versions</string> <string name="server_info_stream_management">XEP-0198: Gestió de la transmissió</string> - <string name="server_info_pep">XEP-0163: PEP(Avatars)</string> <string name="server_info_available">Disponible</string> <string name="server_info_unavailable">No disponible</string> <string name="missing_public_keys">Clau pública d\'invitació perduda</string> @@ -249,7 +239,6 @@ <string name="skip">Saltar</string> <string name="disable_notifications">Deshabilitar notificacions</string> <string name="disable_notifications_for_this_conversation">Deshabilitar notificacions per aquesta conversació</string> - <string name="notifications_disabled">Les notificacions estàn deshabilitades</string> <string name="enable">Habilitat</string> <string name="conference_requires_password">La sala requereix una contrasenya</string> <string name="enter_password">Introduir-ho la contrasenya</string> @@ -304,7 +293,6 @@ <string name="verify_otr">Verificar OTR</string> <string name="remote_fingerprint">Empremta digital remota</string> <string name="scan">Escanejar</string> - <string name="or_touch_phones">( o toca altres mòbils)</string> <string name="smp">Protocol de socialistes millionaris</string> <string name="shared_secret_hint">Pista o pregunta</string> <string name="shared_secret_secret">Secret compartit</string> @@ -384,7 +372,6 @@ <string name="public_conference">Comferència de la conversació d\'accés pùblic</string> <string name="private_conference">Privada, únicament els membres de la conferència de conversació</string> <string name="conference_options">Opcions de la sala</string> - <string name="members_only">Privat( Nomès membres)</string> <string name="non_anonymous">Sense anonimat</string> <string name="modified_conference_options">Modificat opcions de la sala!</string> <string name="could_not_modify_conference_options">No s\'ha pogut modificar les opcions de la sala</string> @@ -413,7 +400,6 @@ <string name="offering_x_file">Oferint %s</string> <string name="hide_offline">Amaga el fora de línia</string> <string name="disable_account">Deshabilita el compte</string> - <string name="contact_is_typing">%s està escrivint...</string> <string name="contact_has_stopped_typing">%s ha deixat d\'escriure</string> <string name="pref_chat_states">Notificacions d\'escriptura</string> <string name="pref_chat_states_summary">Permet el teu contacte saber quan estàs escrivint un missatge nou</string> @@ -424,7 +410,6 @@ <string name="received_location">Localització rebuda</string> <string name="title_undo_swipe_out_conversation">Conversa tancada</string> <string name="title_undo_swipe_out_muc">S\'ha sortit de la conferència</string> - <string name="pref_certificate_options">Opcions de certificats</string> <string name="pref_dont_trust_system_cas_title">No confiar en les CAs del sistema</string> <string name="pref_dont_trust_system_cas_summary">Tots els certificats han de ser aprovats manualment</string> <string name="pref_remove_trusted_certificates_title">Eliminar certificats</string> diff --git a/src/main/res/values-cs/strings.xml b/src/main/res/values-cs/strings.xml index 816abba8..c64b8d26 100644 --- a/src/main/res/values-cs/strings.xml +++ b/src/main/res/values-cs/strings.xml @@ -9,7 +9,7 @@ <string name="action_secure">Zabezpečená konverzace</string> <string name="action_add_account">Přidat účet</string> <string name="action_edit_contact">Upravit jméno</string> - <string name="action_add_phone_book">Přidat do telefonního seznamu</string> + <string name="action_add_phone_book">Přidat do adresáře</string> <string name="action_delete_contact">Smazat ze seznamu</string> <string name="action_block_contact">Zablokovat kontakt</string> <string name="action_unblock_contact">Odblokovat kontakt</string> @@ -28,7 +28,8 @@ <string name="minutes_ago">před %d minutami</string> <string name="unread_conversations">nepřečtené konverzace</string> <string name="sending">odesílám…</string> - <string name="encrypted_message">Dešifruji zprávu. Chvíli strpení…</string> + <string name="message_decrypting">Dešifrování zprávy. Chvíli strpení...</string> + <string name="pgp_message">OpenPGP šifrovaná zpráva</string> <string name="nick_in_use">Přezdívka se již používá</string> <string name="admin">Administrátor</string> <string name="owner">Vlastník</string> @@ -74,10 +75,12 @@ <string name="clear_conversation_history">Smaže historii konverzací</string> <string name="clear_histor_msg">Chcete smazat všechny zprávy v této konverzaci?\n\n<b>Varování:</b> Toto neovlivní zprávy uložené na jiných přístrojích nebo serverech.</string> <string name="delete_messages">Smazat zprávy</string> - <string name="also_end_conversation">Poté ukončit i tuto konverzaci</string> + <string name="also_end_conversation">Poté ukončit tuto konverzaci</string> <string name="choose_presence">Vybrat aktualizaci stavu pro kontakt</string> - <string name="send_plain_text_message">Poslat textovou zprávu</string> + <string name="send_unencrypted_message">Odeslat nešifrovanou zprávu</string> <string name="send_otr_message">Poslat OTR šifrovanou zprávu</string> + <string name="send_omemo_message">Poslat OMEMO šifrovanou zprávu</string> + <string name="send_omemo_x509_message">Odeslat v\\OMEMO šifrovanou zprávu</string> <string name="send_pgp_message">Poslat OpenPGP šifrovanou zprávu</string> <string name="your_nick_has_been_changed">Přezdívka byla změněna</string> <string name="send_unencrypted">Poslat nešifrované</string> @@ -86,18 +89,19 @@ <string name="openkeychain_required_long">Konverzace využívá aplikaci třetí strany, <b>OpenKeychain</b>, k šifrování a dešifrování zpráv a ke správě veřejných klíčů.\n\nOpenKeychain je licencován pod GPLv3 a dostupný na F-Droid a Google Play.\n\n<small>(Po instalaci prosím restartujte aplikaci Konverzace.)</small></string> <string name="restart">Restartovat</string> <string name="install">Instalovat</string> + <string name="openkeychain_not_installed">Nainstalujte prosím OpenKeychain</string> <string name="offering">nabízí…</string> <string name="waiting">čekám…</string> <string name="no_pgp_key">Nebyl nalezen žádný OpenPGP klíč</string> <string name="contact_has_no_pgp_key">Není možné zašifrovat zprávu v aplikaci Konverzace, protože druhá strana neoznamuje svůj veřejný klíč.\n\n<small>Požádejte svůj kontakt ať si nastaví OpenPGP.</small></string> <string name="no_pgp_keys">Nebyly nalezeny žádné OpenPGP klíče</string> <string name="contacts_have_no_pgp_keys">Není možné zašifrovat zprávy v aplikaci Konverzace, protože kontakty neoznamují svůj veřejný klíč.\n\n<small>Požádejte své kontakty ať si nastaví OpenPGP.</small></string> - <string name="encrypted_message_received"><i>Byla přijata šifrovaná zpráva. Ťukni pro dešifrování a přečtení.</i></string> + <string name="encrypted_message_received"><i>Obdržena šifrovaná zpráva. Dešifrovat dotykem.</i></string> <string name="pref_general">Obecné</string> <string name="pref_xmpp_resource">XMPP zdroj</string> <string name="pref_xmpp_resource_summary">Jméno se kterým se tento klient identifikuje</string> <string name="pref_accept_files">Přijímat soubory</string> - <string name="pref_accept_files_size_summary">Automaticky přijímat soubory menší než…</string> + <string name="pref_accept_files_summary">Automaticky přijímat soubory menší než…</string> <string name="pref_notification_settings">Nastavení upozornění</string> <string name="pref_notifications">Upozornění</string> <string name="pref_notifications_summary">Upozornit při přijetí nové zprávy</string> @@ -105,8 +109,6 @@ <string name="pref_vibrate_summary">Vibrovat při přijetí nové zprávy</string> <string name="pref_sound">Zvuk</string> <string name="pref_sound_summary">Přehrát zvuk společně s upozorněním</string> - <string name="pref_conference_notifications">Upozornění při konferencích</string> - <string name="pref_conference_notifications_summary">Vždy upozorňovat při nové konferenční zprávě, nejen pokud je vybrána</string> <string name="pref_notification_grace_period">Četnost upozornění</string> <string name="pref_notification_grace_period_summary">Neupozorňovat krátce poté co byla obdržena kopie zprávy</string> <string name="pref_advanced_options">Pokročilé nastavení</string> @@ -149,9 +151,10 @@ <string name="account_status_regis_not_sup">Server nepodporuje registrace</string> <string name="account_status_security_error">Bezpečnostní chyba</string> <string name="account_status_incompatible_server">Nekompatibilní server</string> - <string name="encryption_choice_none">Čistý text</string> + <string name="encryption_choice_unencrypted">Nešifrováno</string> <string name="encryption_choice_otr">OTR</string> <string name="encryption_choice_pgp">OpenPGP</string> + <string name="encryption_choice_omemo">OMEMO</string> <string name="mgmt_account_edit">Upravit účet</string> <string name="mgmt_account_delete">Smazat účet</string> <string name="mgmt_account_disable">Dočasně vypnout</string> @@ -170,7 +173,7 @@ <string name="passwords_do_not_match">Hesla nesouhlasí</string> <string name="invalid_jid">Toto není platné Jabber ID</string> <string name="error_out_of_memory">Nedostatek paměti. Obrázek je příliš velký</string> - <string name="add_phone_book_text">Chcete přidat %s do svého telefonního seznamu?</string> + <string name="add_phone_book_text">Chcete přidat %s do svého adresáře?</string> <string name="contact_status_online">online</string> <string name="contact_status_free_to_chat">volný pro chat</string> <string name="contact_status_away">pryč</string> @@ -186,7 +189,8 @@ <string name="server_info_blocking">XEP-0191: Příkaz blokování</string> <string name="server_info_roster_version">XEP-0237: Verzování seznamu</string> <string name="server_info_stream_management">XEP-0198: Nastavení proudu</string> - <string name="server_info_pep">XEP-0163: PEP (Avatars)</string> + <string name="server_info_pep">XEP-0163: PEP (Avatars / OMEMO)</string> + <string name="server_info_http_upload">XEP-0363: HTTP File Upload</string> <string name="server_info_available">dostupný</string> <string name="server_info_unavailable">nedostupný</string> <string name="missing_public_keys">Chybí oznámení o veřejném klíči</string> @@ -204,17 +208,28 @@ <string name="reception_failed">Příjem selhal</string> <string name="your_fingerprint">Váš identifikátor</string> <string name="otr_fingerprint">OTR identifikátor</string> + <string name="omemo_fingerprint">OMEMO otisk</string> + <string name="omemo_fingerprint_x509">v\\OMEMO otisk</string> + <string name="omemo_fingerprint_selected_message">OMEMO otisk zprávy</string> + <string name="omemo_fingerprint_x509_selected_message">v\\OMEMO otisk zprávy</string> + <string name="this_device_omemo_fingerprint">Můj OMEMO otisk</string> + <string name="other_devices">Ostatní přístroje</string> + <string name="trust_omemo_fingerprints">Věřit OMEMO otiskům</string> + <string name="fetching_keys">Získávání klíčů...</string> + <string name="done">Hotovo</string> <string name="verify">Ověřit</string> <string name="decrypt">Dešifrovat</string> <string name="conferences">Konference</string> <string name="search">Hledat</string> <string name="create_contact">Vytvořit kontakt</string> + <string name="enter_contact">Vložit kontakt</string> <string name="join_conference">Připojit ke konferenci</string> <string name="delete_contact">Smazat kontakt</string> <string name="view_contact_details">Zobrazit detaily kontaktu</string> <string name="block_contact">Zablokovat kontakt</string> <string name="unblock_contact">Odblokovat kontakt</string> <string name="create">Vytvořit</string> + <string name="select">Vybrat</string> <string name="contact_already_exists">Kontakt již existuje</string> <string name="join">Vstoupit</string> <string name="conference_address">Adresa konference</string> @@ -249,7 +264,6 @@ <string name="skip">Přeskočit</string> <string name="disable_notifications">Vypnout upozornění</string> <string name="disable_notifications_for_this_conversation">Vypnout upozornění pro tuto konverzaci</string> - <string name="notifications_disabled">Upozornění jsou vypnuta</string> <string name="enable">Povolit</string> <string name="conference_requires_password">Konference vyžaduje heslo</string> <string name="enter_password">Vložit heslo</string> @@ -284,6 +298,7 @@ <string name="pref_conference_name">Jméno konference</string> <string name="pref_conference_name_summary">Pro identifikaci konferencí použít téma místnosti místo jejího JID</string> <string name="toast_message_otr_fingerprint">OTR otisk zkopírován do schránky!</string> + <string name="toast_message_omemo_fingerprint">OMEMO otisk zkopírován do schránky!</string> <string name="conference_banned">Vstup do konference byl zakázán</string> <string name="conference_members_only">Tato konference je pouze pro členy</string> <string name="conference_kicked">Vykopli tě z této konference</string> @@ -307,7 +322,6 @@ <string name="verify_otr">Ověřit OTR</string> <string name="remote_fingerprint">Vzdálený otisk</string> <string name="scan">skenovat</string> - <string name="or_touch_phones">(nebo dotykové telefony)</string> <string name="smp">Socialist Millionaire Protocol</string> <string name="shared_secret_hint">Nápověda nebo Otázka</string> <string name="shared_secret_secret">Sdílené tajemství</string> @@ -324,6 +338,9 @@ <string name="conversations_foreground_service">Conversations</string> <string name="pref_keep_foreground_service">Ponechat službu v popředí</string> <string name="pref_keep_foreground_service_summary">Zamezit operačnímu systému v ukončení připojení</string> + <string name="pref_export_logs">Exportovat logy</string> + <string name="pref_export_logs_summary">Zapsat logy na SD kartu</string> + <string name="notification_export_logs_title">Zapisování logů na SD kartu</string> <string name="choose_file">Vybrat soubor</string> <string name="receiving_x_file">Přijímám %1$s (%2$d%% dokončeno)</string> <string name="download_x_file">Stáhnout %s</string> @@ -350,6 +367,17 @@ <string name="reset">Reset</string> <string name="account_image_description">Avatar účtu</string> <string name="copy_otr_clipboard_description">Zkopírovat otisk OTR do schránky</string> + <string name="copy_omemo_clipboard_description">Zkopírovat OMEMO otisk do schránky</string> + <string name="regenerate_omemo_key">Znovu vytvořit OMEMO klíč</string> + <string name="wipe_omemo_pep">Smazat ostatní přístroje z PEP</string> + <string name="clear_other_devices">Smazat přístroje</string> + <string name="clear_other_devices_desc">Opravdu chcete vymazat ostatní přístroje z OMEMO upozornění? Až se příště vaše přístroje připojí, znovu se ohlásí, ale pravděpodobně neobdrží zprávy odeslané v mezičase mezi přihlášeními.</string> + <string name="purge_key">Vymazat klíč</string> + <string name="purge_key_desc_part1">Opravdu si přejete vymazat tento klíč?</string> + <string name="purge_key_desc_part2">Bude neodvolatelně pokládán jako kompromitovaný a již s jeho pomocí nebudete moci nikdy spustit jiné sezení.</string> + <string name="error_no_keys_to_trust_server_error">Pro tento kontakt nejsou dostupné žádné použitelné klíče.\nNahrání klíčů ze serveru nebylo úspěšné. Možná je něco špatně se serverem vašeho kontaktu.</string> + <string name="error_no_keys_to_trust">Pro tento kontakt nejsou dostupné žádné použitelné klíče. Pokud jste smazali jakýkoliv z jeho klíčů bude třeba vygenerovat nové.</string> + <string name="error_trustkeys_title">Chyba</string> <string name="fetching_history_from_server">Načíst historii ze serveru</string> <string name="no_more_history_on_server">Na serveru není žádná další historie</string> <string name="updating">Aktualizuji...</string> @@ -387,8 +415,10 @@ <string name="public_conference">Veřejně přístupná konference</string> <string name="private_conference">Soukromá konference pouze pro členy</string> <string name="conference_options">Nastavení konference</string> - <string name="members_only">Soukromá (pouze členové)</string> + <string name="members_only">Soukromé, pouze pro členy</string> <string name="non_anonymous">Neanonymní</string> + <string name="moderated">Moderováno</string> + <string name="you_are_not_participating">Neúčastníte se</string> <string name="modified_conference_options">Nastavení konference upravena!</string> <string name="could_not_modify_conference_options">Nepodařilo se upravit nastavení konference!</string> <string name="never">Nikdy</string> @@ -427,7 +457,6 @@ <string name="received_location">Přijmout pozici</string> <string name="title_undo_swipe_out_conversation">Conversation zavřena</string> <string name="title_undo_swipe_out_muc">Opustil(a) konferenci</string> - <string name="pref_certificate_options">Nastavení certifikátu</string> <string name="pref_dont_trust_system_cas_title">Nedůvěřovat systémovým CA</string> <string name="pref_dont_trust_system_cas_summary">Všechny certifikáty musí být schváleny ručně</string> <string name="pref_remove_trusted_certificates_title">Odstranit certifikáty</string> @@ -451,6 +480,74 @@ <string name="none">Žádná</string> <string name="recently_used">Naposledy použitá</string> <string name="choose_quick_action">Vybrat rychlou akci</string> - <string name="file_not_found_on_remote_host">Soubor nenalezen na vzdáleném serveru</string> <string name="search_for_contacts_or_groups">Hledat kontakty či skupiny</string> + <string name="send_private_message">Poslat soukromou zprávu</string> + <string name="user_has_left_conference">%s opustil(a) konferenci!</string> + <string name="username">Uživatelské jméno</string> + <string name="username_hint">Uživatelské jméno</string> + <string name="invalid_username">Toto není platné uživatelské jméno</string> + <string name="download_failed_server_not_found">Stahování selhalo: Server nenalezen</string> + <string name="download_failed_file_not_found">Stahování selhalo: Soubor nenalezen</string> + <string name="download_failed_could_not_connect">Stahování selhalo: Nelze se připojit k hostu</string> + <string name="pref_use_white_background">Použít bílé pozadí</string> + <string name="pref_use_white_background_summary">Zobrazovat přijaté zprávy jako černý text na bílém pozadí</string> + <string name="account_status_tor_unavailable">Tor síť není dostupná</string> + <string name="server_info_broken">Rozbité</string> + <string name="pref_presence_settings">Nastavení přítomnosti</string> + <string name="pref_away_when_screen_off">Pryč při vypnuté obrazovce</string> + <string name="pref_away_when_screen_off_summary">Při vypnuté obrazovce označí váš stav jako pryč</string> + <string name="pref_xa_on_silent_mode">Nedostupný při vypnutém zvuku</string> + <string name="pref_xa_on_silent_mode_summary">Při tichém módu označí váš stav jako nedostupný</string> + <string name="action_add_account_with_certificate">Přidat účet s certifikátem</string> + <string name="unable_to_parse_certificate">Nelze načíst certifikát</string> + <string name="authenticate_with_certificate">Nechat prázdné pro ověření s certifikátem</string> + <string name="captcha_ocr">Captcha text</string> + <string name="captcha_required">Captcha vyžadována</string> + <string name="captcha_hint">opište text z obrázku</string> + <string name="certificate_chain_is_not_trusted">Řetězec certifikátů není důvěryhodný</string> + <string name="jid_does_not_match_certificate">Jabber ID neodpovídá certifikátu</string> + <string name="action_renew_certificate">Obnovit certifikát</string> + <string name="error_fetching_omemo_key">Chyba získání OMEMO klíče!</string> + <string name="verified_omemo_key_with_certificate">OMEMO klíč ověřen certifikátem!</string> + <string name="device_does_not_support_certificates">Tento přístroj nepodporuje výběr klientského certifikátu!</string> + <string name="pref_connection_options">Možnosti připojení</string> + <string name="pref_use_tor">Připojit přes Tor</string> + <string name="pref_use_tor_summary">Vedení všech připojení po Tor síti vyžaduje aplikaci Orbot</string> + <string name="account_settings_hostname">Hostname</string> + <string name="account_settings_port">Port</string> + <string name="hostname_or_onion">Server- nebo .onion-adresa</string> + <string name="not_a_valid_port">Toto není platné číslo portu</string> + <string name="not_valid_hostname">Toto není platné hostname</string> + <string name="connected_accounts">%1$d z %2$d účtů připojeno</string> + <plurals name="x_messages"> + <item quantity="one">%d zpráva</item> + <item quantity="few">%d zprávy</item> + <item quantity="other">%d zpráv</item> + </plurals> + <string name="shared_file_with_x">Soubor sdílen s %s</string> + <string name="shared_image_with_x">Obrázek sdílen s %s</string> + <string name="no_storage_permission">Conversations vyžaduje přístup k externímu úložišti</string> + <string name="sync_with_contacts">Synchronizovat s kontakty</string> + <string name="sync_with_contacts_long">Aplikace Conversations by ráda porovnala váš XMPP seznam s vašimi kontakty, aby mohla zobrazit plná jména a avatary.\n\nConversations načte a porovná kontakty pouze lokálně, bez jejich nahrávání na server.\n\nNyní budete dotázáni na udělení práv pro přístup k seznamu kontaktů.</string> + <string name="certificate_information">Informace certifikátu</string> + <string name="certificate_subject">Předmět</string> + <string name="certificate_issuer">Vydavatel</string> + <string name="certificate_cn">Jméno</string> + <string name="certificate_o">Organizace</string> + <string name="certificate_sha1">SHA1</string> + <string name="certicate_info_not_available">(Nedostupné)</string> + <string name="certificate_not_found">Certifikát nenalezen</string> + <string name="notify_on_all_messages">Upozorňovat na všechny zprávy</string> + <string name="notify_only_when_highlighted">Upozorňovat pouze pokud zvýrazněno</string> + <string name="notify_never">Upozornění vypnuta</string> + <string name="notify_paused">Upozornění pozastavena</string> + <string name="pref_picture_compression">Komprimovat obrázky</string> + <string name="pref_picture_compression_summary">Upravené a komprimované obrázky</string> + <string name="always">Vždy</string> + <string name="automatically">Automaticky</string> + <string name="battery_optimizations_enabled">Povolena optimalizace využití baterie</string> + <string name="battery_optimizations_enabled_explained">Tento přístroj provádí agresivní optimalizace využití baterie pro konverzace. Ty mohou způsobit zpoždění upozornění nebo ztrátu zpráv.\nJe doporučeno tyto vypnout.</string> + <string name="battery_optimizations_enabled_dialog">Tento přístroj provádí agresivní optimalizace využití baterie pro konverzace. Ty mohou způsobit zpoždění upozornění nebo ztrátu zpráv.\nnyní budete vyzváni k jejich vypnutí.</string> + <string name="disable">Vypnout</string> + <string name="selection_too_large">Vybraný obsah je příliš dlouhý</string> </resources> diff --git a/src/main/res/values-de/strings.xml b/src/main/res/values-de/strings.xml index 22d5bc32..9ceeaec7 100644 --- a/src/main/res/values-de/strings.xml +++ b/src/main/res/values-de/strings.xml @@ -1,102 +1,105 @@ <?xml version='1.0' encoding='UTF-8'?> <resources> - <string name="action_settings">Einstellungen</string> - <string name="action_add">Neue Unterhaltung</string> - <string name="action_accounts">Konten verwalten</string> - <string name="action_end_conversation">Unterhaltung beenden</string> + <string name="action_settings">Einstellungen</string> + <string name="action_add">Neue Unterhaltung</string> + <string name="action_accounts">Konten verwalten</string> + <string name="action_end_conversation">Unterhaltung beenden</string> <string name="action_contact_details">Kontakt-Details</string> <string name="action_muc_details">Konferenz-Details</string> - <string name="action_secure">Verschlüsselte Unterhaltung</string> - <string name="action_add_account">Konto hinzufügen</string> + <string name="action_secure">Verschlüsselte Unterhaltung</string> + <string name="action_add_account">Konto hinzufügen</string> <string name="action_edit_contact">Namen bearbeiten</string> - <string name="action_add_phone_book">Zum Telefonbuch hinzufügen</string> - <string name="action_delete_contact">Aus Kontaktliste entfernen</string> - <string name="action_block_contact">Kontakt sperren</string> - <string name="action_unblock_contact">Kontakt entsperren</string> - <string name="action_block_domain">Domain sperren</string> - <string name="action_unblock_domain">Domain entsperren</string> - <string name="title_activity_manage_accounts">Konten verwalten</string> - <string name="title_activity_settings">Einstellungen</string> + <string name="action_add_phone_book">Zu Telefonbuch hinzufügen</string> + <string name="action_delete_contact">Aus Kontaktliste entfernen</string> + <string name="action_block_contact">Kontakt sperren</string> + <string name="action_unblock_contact">Kontakt entsperren</string> + <string name="action_block_domain">Domain sperren</string> + <string name="action_unblock_domain">Domain entsperren</string> + <string name="title_activity_manage_accounts">Konten verwalten</string> + <string name="title_activity_settings">Einstellungen</string> <string name="title_activity_conference_details">Konferenz-Details</string> <string name="title_activity_contact_details">Kontakt-Details</string> - <string name="title_activity_sharewith">Mit Unterhaltung teilen</string> - <string name="title_activity_start_conversation">Beginne Unterhaltung</string> - <string name="title_activity_choose_contact">Kontakt auswählen</string> - <string name="title_activity_block_list">Sperrliste</string> - <string name="just_now">gerade</string> - <string name="minute_ago">vor einer Minute</string> - <string name="minutes_ago">vor %d Minuten</string> - <string name="unread_conversations">ungelesene Unterhaltungen</string> + <string name="title_activity_sharewith">Mit Unterhaltung teilen</string> + <string name="title_activity_start_conversation">Unterhaltung beginnen</string> + <string name="title_activity_choose_contact">Kontakt auswählen</string> + <string name="title_activity_block_list">Sperrliste</string> + <string name="just_now">gerade</string> + <string name="minute_ago">vor einer Minute</string> + <string name="minutes_ago">vor %d Minuten</string> + <string name="unread_conversations">ungelesene Unterhaltungen</string> <string name="sending">senden…</string> - <string name="encrypted_message">Entschlüssele Nachricht. Bitte warten…</string> - <string name="nick_in_use">Nickname wird bereits verwendet</string> - <string name="admin">Administrator</string> - <string name="owner">Eigentümer</string> - <string name="moderator">Moderator</string> - <string name="participant">Teilnehmer</string> - <string name="visitor">Besucher</string> - <string name="remove_contact_text">Möchtest du %s von deiner Kontaktliste entfernen? Die Unterhaltung mit diesem Kontakt wird dabei nicht entfernt.</string> - <string name="block_contact_text">Möchtest du %s sperren und keine Nachrichten mehr erhalten?</string> - <string name="unblock_contact_text">Möchtest du %s entsperren und wieder Nachrichten empfangen?</string> - <string name="block_domain_text">Sperre alle Kontakte von %s?</string> - <string name="unblock_domain_text">Entsperre alle Kontakte %s?</string> - <string name="contact_blocked">Kontakt gesperrt</string> + <string name="message_decrypting">Nachricht wird entschlüsselt. Bitte warten …</string> + <string name="pgp_message">OpenPGP-verschlüsselte Nachricht</string> + <string name="nick_in_use">Nickname wird bereits verwendet</string> + <string name="admin">Administrator</string> + <string name="owner">Eigentümer</string> + <string name="moderator">Moderator</string> + <string name="participant">Teilnehmer</string> + <string name="visitor">Besucher</string> + <string name="remove_contact_text">Möchtest du %s von deiner Kontaktliste entfernen? Die Unterhaltung mit diesem Kontakt wird dabei nicht entfernt.</string> + <string name="block_contact_text">Möchtest du %s sperren und keine Nachrichten mehr erhalten?</string> + <string name="unblock_contact_text">Möchtest du %s entsperren und wieder Nachrichten empfangen?</string> + <string name="block_domain_text">Alle Kontakte von %s sperren?</string> + <string name="unblock_domain_text">Alle Kontakte von %s entsperren?</string> + <string name="contact_blocked">Kontakt gesperrt</string> <string name="remove_bookmark_text">Möchtest du %s von deiner Kontaktliste entfernen? Die Unterhaltung mit dieser Konferenz wird dabei nicht entfernt.</string> - <string name="register_account">Neues Konto auf dem Server erstellen</string> - <string name="change_password_on_server">Passwort ändern</string> - <string name="share_with">Teile mit…</string> - <string name="start_conversation">Beginne Unterhaltung</string> - <string name="invite_contact">Kontakt einladen</string> - <string name="contacts">Kontakte</string> - <string name="cancel">Abbrechen</string> - <string name="set">Einstellen</string> - <string name="add">Hinzufügen</string> - <string name="edit">Bearbeiten</string> - <string name="delete">Entfernen</string> - <string name="block">Sperren</string> - <string name="unblock">Entsperren</string> - <string name="save">Speichern</string> - <string name="ok">OK</string> - <string name="crash_report_title">Conversations ist abgestürzt</string> - <string name="crash_report_message">Durch das Einsenden von Fehlerberichten hilfst du bei der stetigen Verbesserung von Conversations.\n<b>Achtung:</b> Dies wird eines deiner XMPP-Konten benutzen, um den Entwickler zu kontaktieren.</string> - <string name="send_now">Jetzt abschicken</string> - <string name="send_never">Nie mehr nachfragen</string> - <string name="problem_connecting_to_account">Es gibt Probleme beim Verbindungsaufbau mit einem Konto</string> - <string name="problem_connecting_to_accounts">Es gibt Probleme beim Verbindungsaufbau mit mehreren Konten</string> - <string name="touch_to_fix">Drücke hier, um das Konto zu verwalten</string> - <string name="attach_file">Datei anfügen</string> - <string name="not_in_roster">Der Kontakt ist nicht in deiner Kontaktliste. Möchtest du ihn hinzufügen?</string> - <string name="add_contact">Zur Kontaktliste hinzufügen</string> - <string name="send_failed">Zustellung nicht erfolgreich</string> - <string name="send_rejected">abgelehnt</string> - <string name="preparing_image">Bereite Bild für die Übertragung vor</string> - <string name="action_clear_history">Verlauf löschen</string> + <string name="register_account">Neues Konto auf Server erstellen</string> + <string name="change_password_on_server">Passwort ändern</string> + <string name="share_with">Teilen mit…</string> + <string name="start_conversation">Unterhaltung beginnen</string> + <string name="invite_contact">Kontakt einladen</string> + <string name="contacts">Kontakte</string> + <string name="cancel">Abbrechen</string> + <string name="set">Einstellen</string> + <string name="add">Hinzufügen</string> + <string name="edit">Bearbeiten</string> + <string name="delete">Entfernen</string> + <string name="block">Sperren</string> + <string name="unblock">Entsperren</string> + <string name="save">Speichern</string> + <string name="ok">OK</string> + <string name="crash_report_title">Conversations ist abgestürzt</string> + <string name="crash_report_message">Durch das Einsenden von Fehlerberichten hilfst du bei der stetigen Verbesserung von Conversations.\n<b>Achtung:</b> Dies wird eines deiner XMPP-Konten benutzen, um den Entwickler zu kontaktieren.</string> + <string name="send_now">Jetzt abschicken</string> + <string name="send_never">Nie mehr nachfragen</string> + <string name="problem_connecting_to_account">Es gibt Probleme beim Verbindungsaufbau mit einem Konto</string> + <string name="problem_connecting_to_accounts">Es gibt Probleme beim Verbindungsaufbau mit mehreren Konten</string> + <string name="touch_to_fix">Hier antippen, um Ihre Konten zu verwalten</string> + <string name="attach_file">Datei anfügen</string> + <string name="not_in_roster">Der Kontakt ist nicht in deiner Kontaktliste. Möchtest du ihn hinzufügen?</string> + <string name="add_contact">Kontakt hinzufügen</string> + <string name="send_failed">Zustellung fehlgeschlagen</string> + <string name="send_rejected">abgelehnt</string> + <string name="preparing_image">Bild wird für Übertragung vorbereitet</string> + <string name="action_clear_history">Verlauf löschen</string> <string name="clear_conversation_history">Verlauf löschen</string> - <string name="clear_histor_msg">Möchtest du alle Nachrichten in dieser Unterhaltung löschen?\n\n<b>Achtung:</b> Dies beeinflusst nicht Nachrichten, die auf anderen Geräten oder Servern gespeichert sind.</string> - <string name="delete_messages">Nachrichten löschen</string> - <string name="also_end_conversation">Diese Unterhaltung danach beenden</string> - <string name="choose_presence">Ressource des Kontakts auswählen</string> - <string name="send_plain_text_message">Normal schreiben…</string> + <string name="clear_histor_msg">Möchtest du alle Nachrichten in dieser Unterhaltung löschen?\n\n<b>Achtung:</b> Dies beeinflusst nicht Nachrichten, die auf anderen Geräten oder Servern gespeichert sind.</string> + <string name="delete_messages">Nachrichten löschen</string> + <string name="also_end_conversation">Diese Unterhaltung danach beenden</string> + <string name="choose_presence">Ressource des Kontakts auswählen</string> + <string name="send_unencrypted_message">Unverschlüsselt schreiben…</string> <string name="send_otr_message">OTR-verschlüsselt schreiben…</string> + <string name="send_omemo_message">OMEMO-verschlüsselt schreiben…</string> + <string name="send_omemo_x509_message">v\\OMEMO-verschlüsselte Nachricht senden</string> <string name="send_pgp_message">OpenPGP-verschlüsselt schreiben…</string> - <string name="your_nick_has_been_changed">Dein Nickname wurde geändert</string> - <string name="download_image">Bild herunterladen</string> - <string name="send_unencrypted">Normal verschicken</string> - <string name="decryption_failed">Entschlüsselung fehlgeschlagen. Vielleicht hast du nicht den richtigen privaten Schlüssel.</string> - <string name="openkeychain_required">OpenKeychain</string> - <string name="openkeychain_required_long">Conversations benutzt eine Drittanwendung namens <b>OpenKeychain</b>, um Nachrichten zu ver- und entschlüsseln und um deine Schlüssel zu verwalten.\n\nOpenKeychain ist GPLv3-lizenziert und kann über F-Droid oder Google Play bezogen werden.\n\n<small>(Bitte starte Conversations danach neu.)</small></string> + <string name="your_nick_has_been_changed">Dein Nickname wurde geändert</string> + <string name="send_unencrypted">Unverschlüsselt senden</string> + <string name="decryption_failed">Entschlüsselung fehlgeschlagen. Vielleicht hast du nicht den richtigen privaten Schlüssel.</string> + <string name="openkeychain_required">OpenKeychain</string> + <string name="openkeychain_required_long">Conversations benutzt eine Drittanwendung namens <b>OpenKeychain</b>, um Nachrichten zu ver- und entschlüsseln und um deine Schlüssel zu verwalten.\n\nOpenKeychain ist GPLv3-lizenziert und kann über F-Droid oder Google Play bezogen werden.\n\n<small>(Bitte starte Conversations danach neu.)</small></string> <string name="restart">Neu starten</string> - <string name="install">Installieren</string> + <string name="install">Installieren</string> + <string name="openkeychain_not_installed">Bitte OpenKeychain installieren</string> <string name="offering">angeboten…</string> <string name="waiting">warten…</string> - <string name="no_pgp_key">Kein OpenPGP-Schlüssel gefunden</string> - <string name="contact_has_no_pgp_key">Conversations ist nicht in der Lage, deine Nachrichten zu verschlüsseln, weil dein Kontakt seinen oder ihren Schlüssel nicht preisgibt.\n\n<small>Bitte sag deinem Kontakt, er oder sie möge OpenPGP einrichten.</small></string> - <string name="no_pgp_keys">Keine OpenPGP-Schlüssel gefunden</string> + <string name="no_pgp_key">Kein OpenPGP-Schlüssel gefunden</string> + <string name="contact_has_no_pgp_key">Conversations ist nicht in der Lage, deine Nachrichten zu verschlüsseln, weil dein Kontakt seinen oder ihren Schlüssel nicht preisgibt.\n\n<small>Bitte sag deinem Kontakt, er oder sie möge OpenPGP einrichten.</small></string> + <string name="no_pgp_keys">Keine OpenPGP-Schlüssel gefunden</string> <string name="contacts_have_no_pgp_keys">Conversations ist nicht in der Lage, deine Nachrichten zu verschlüsseln, weil deine Kontakte ihre Schlüssel nicht preisgeben.\n\n<small>Bitte sage deinen Kontakten, sie mögen OpenPGP einrichten.</small></string> - <string name="encrypted_message_received"><i>Verschlüsselte Nachricht erhalten. Drücke hier, um sie zu entschlüsseln und anzuzeigen.</i></string> - <string name="pref_general">Allgemeines</string> - <string name="pref_xmpp_resource">XMPP-Ressource</string> - <string name="pref_xmpp_resource_summary">Der Name, mit dem sich der Client selbst identifiziert</string> + <string name="encrypted_message_received"><i>Verschlüsselte Nachricht erhalten. Antippen zum Entschlüsseln.</i></string> + <string name="pref_general">Allgemeines</string> + <string name="pref_xmpp_resource">XMPP-Ressource</string> + <string name="pref_xmpp_resource_summary">Der Name, mit dem sich der Client selbst identifiziert</string> <string name="pref_accept_files">Dateiannahme</string> <string name="pref_accept_files_summary">Einstellungen für Dateiannahme und automatischen Download</string> <string name="pref_accept_files_size">Größe</string> @@ -105,173 +108,183 @@ <string name="pref_accept_files_download_summary">Automatisches Herunterladen und Akzeptieren von Dateien nur im WLAN</string> <string name="pref_accept_files_download_link">Bilder-Links</string> <string name="pref_accept_files_download_link_summary">Bilder-Links automatisch herunterladen</string> - <string name="pref_notification_settings">Benachrichtigungen</string> - <string name="pref_notifications">Benachrichtigungen</string> - <string name="pref_notifications_summary">Benachrichtige mich, wenn eine neue Nachricht ankommt</string> - <string name="pref_vibrate">Vibrieren</string> - <string name="pref_vibrate_summary">Vibriere, wenn eine neue Nachricht ankommt</string> - <string name="pref_sound">Klingelton</string> - <string name="pref_sound_summary">Spiele Klingelton, wenn eine neue Nachricht ankommt</string> - <string name="pref_conference_notifications">Konferenz-Benachrichtigungen</string> - <string name="pref_conference_notifications_summary">Benachrichtige mich bei jeder Konferenz-Nachricht und nicht nur, wenn ich angesprochen werde</string> - <string name="pref_notification_grace_period">Gnadenfrist</string> - <string name="pref_notification_grace_period_summary">Deaktiviere Benachrichtigungen für eine kurze Zeit nach Erhalt einer Nachricht, die von einem anderen deiner Clients kommt.</string> - <string name="pref_advanced_options">Erweiterte Optionen</string> - <string name="pref_never_send_crash">Sende niemals Absturzberichte</string> - <string name="pref_never_send_crash_summary">Wenn du Absturzberichte einschickst, hilfst du Conversations stetig zu verbessern</string> + <string name="pref_notification_settings">Benachrichtigungen</string> + <string name="pref_notifications">Benachrichtigungen</string> + <string name="pref_notifications_summary">Benachrichtigen bei Erhalt einer neuen Nachricht</string> + <string name="pref_vibrate">Vibrieren</string> + <string name="pref_vibrate_summary">Vibrieren bei Erhalt einer neuen Nachricht</string> + <string name="pref_sound">Benachrichtigungston</string> + <string name="pref_sound_summary">Benachrichtigungston wiedergeben</string> + <string name="pref_notification_grace_period">Gnadenfrist</string> + <string name="pref_notification_grace_period_summary">Deaktiviere Benachrichtigungen für eine kurze Zeit nach Erhalt einer Nachricht, die von einem anderen deiner Clients kommt.</string> + <string name="pref_advanced_options">Erweiterte Optionen</string> + <string name="pref_never_send_crash">Niemals Absturzberichte senden</string> + <string name="pref_never_send_crash_summary">Wenn du Absturzberichte einschickst, hilfst du Conversations stetig zu verbessern</string> <string name="pref_confirm_messages">Lese- und Empfangsbestätigung senden</string> - <string name="pref_confirm_messages_summary">Informiere deine Kontakte, wenn du eine Nachricht empfangen oder gelesen hast</string> + <string name="pref_confirm_messages_summary">Informiere deine Kontakte, wenn du eine Nachricht empfangen und gelesen hast</string> <string name="pref_confirm_messages_none">Keine Bestätigungen</string> <string name="pref_confirm_messages_received">Nur Empfangsbestätigung</string> <string name="pref_confirm_messages_read_and_received">Lese- und Empfangsbestätigung</string> - <string name="pref_ui_options">Benutzeroberfläche</string> + <string name="pref_ui_options">Benutzeroberfläche</string> <string name="pref_parse_emoticons">Smilies ersetzen</string> <string name="pref_parse_emoticons_summary">Zeige Smilie-Bilder anstelle von Emoticons.</string> - <string name="openpgp_error">Fehler mit OpenKeychain</string> - <string name="error_decrypting_file">Fehler beim Entschlüsseln der Datei</string> - <string name="accept">Annehmen</string> - <string name="error">Ein unbekannter Fehler ist aufgetreten</string> - <string name="pref_grant_presence_updates">Online-Status</string> - <string name="pref_grant_presence_updates_summary">Erlaube neu hinzugefügten Kontakten deinen online-Status zu sehen und frage um Erlaubnis, ihren sehen zu dürfen</string> - <string name="subscriptions">Abonnements</string> - <string name="your_account">Dein Konto</string> - <string name="keys">Schlüssel</string> - <string name="send_presence_updates">Anwesenheitsbenachrichtigungen senden</string> - <string name="receive_presence_updates">Empfange Anwesenheitsbenachrichtigungen</string> - <string name="ask_for_presence_updates">Frage um Erlaubnis, Anwesenheitsbenachrichtigungen sehen zu dürfen</string> + <string name="openpgp_error">Fehler mit OpenKeychain</string> + <string name="error_decrypting_file">Fehler beim Entschlüsseln der Datei</string> + <string name="accept">Annehmen</string> + <string name="error">Ein Fehler ist aufgetreten</string> + <string name="pref_grant_presence_updates">Online-Status</string> + <string name="pref_grant_presence_updates_summary">Erlaube neu hinzugefügten Kontakten meinen Online-Status zu sehen und frage um Erlaubnis, ihren sehen zu dürfen</string> + <string name="subscriptions">Abonnements</string> + <string name="your_account">Dein Konto</string> + <string name="keys">Schlüssel</string> + <string name="send_presence_updates">Online-Status senden</string> + <string name="receive_presence_updates">Online-Status empfangen</string> + <string name="ask_for_presence_updates">Online-Status anfragen</string> <string name="attach_choose_picture">Bild auswählen</string> <string name="attach_take_picture">Bild aufnehmen</string> - <string name="preemptively_grant">Erlaube Statusanfrage vorab</string> - <string name="error_not_an_image_file">Die ausgewählte Datei ist kein Bild</string> - <string name="error_compressing_image">Fehler beim Umwandeln des Bildes</string> - <string name="error_file_not_found">Datei nicht gefunden</string> - <string name="error_io_exception">Allgemeiner Fehler. Vielleicht hast du keinen Speicherplatz mehr?</string> - <string name="error_security_exception_during_image_copy">Die App, mit der du das Bild ausgesucht hast, hat uns keine Rechte eingeräumt, das Bild zu betrachten.\n\n<small>Benutze einen anderen Dateimanager</small></string> - <string name="account_status_unknown">Unbekannt</string> - <string name="account_status_disabled">Vorübergehend abgeschaltet</string> - <string name="account_status_online">Online</string> + <string name="preemptively_grant">Statusanfragen vorab erlauben</string> + <string name="error_not_an_image_file">Die ausgewählte Datei ist kein Bild</string> + <string name="error_compressing_image">Fehler beim Umwandeln des Bildes</string> + <string name="error_file_not_found">Datei nicht gefunden</string> + <string name="error_io_exception">Allgemeiner Fehler. Vielleicht hast du keinen Speicherplatz mehr?</string> + <string name="error_security_exception_during_image_copy">Die App, mit der du das Bild ausgesucht hast, hat uns keine Rechte eingeräumt, das Bild zu betrachten.\n\n<small>Benutze einen anderen Dateimanager</small></string> + <string name="account_status_unknown">Unbekannt</string> + <string name="account_status_disabled">Vorübergehend abgeschaltet</string> + <string name="account_status_online">Online</string> <string name="account_status_connecting">Verbinde…</string> - <string name="account_status_offline">Offline</string> - <string name="account_status_unauthorized">Ungültige Zugangsdaten</string> - <string name="account_status_not_found">Server nicht gefunden</string> - <string name="account_status_no_internet">Keine Internetverbindung</string> - <string name="account_status_regis_fail">Registrierung fehlgeschlagen</string> - <string name="account_status_regis_conflict">Benutzername wird bereits verwendet</string> - <string name="account_status_regis_success">Registrierung abgeschlossen</string> - <string name="account_status_regis_not_sup">Der Server unterstützt keine Registrierung</string> - <string name="account_status_security_error">Sicherheitsfehler</string> - <string name="account_status_incompatible_server">Inkompatibler Server</string> - <string name="encryption_choice_none">Klartext</string> - <string name="encryption_choice_otr">OTR</string> - <string name="encryption_choice_pgp">OpenPGP</string> - <string name="mgmt_account_edit">Konto bearbeiten</string> - <string name="mgmt_account_delete">Löschen</string> - <string name="mgmt_account_disable">Vorübergehend abschalten</string> - <string name="mgmt_account_publish_avatar">Avatar veröffentlichen</string> - <string name="mgmt_account_publish_pgp">Öffentlichen OpenPGP-Schlüssel veröffentlichen</string> - <string name="mgmt_account_enable">Anschalten</string> - <string name="mgmt_account_are_you_sure">Bist du dir sicher?</string> + <string name="account_status_offline">Offline</string> + <string name="account_status_unauthorized">Ungültige Zugangsdaten</string> + <string name="account_status_not_found">Server nicht gefunden</string> + <string name="account_status_no_internet">Keine Internetverbindung</string> + <string name="account_status_regis_fail">Registrierung fehlgeschlagen</string> + <string name="account_status_regis_conflict">Benutzername wird bereits verwendet</string> + <string name="account_status_regis_success">Registrierung abgeschlossen</string> + <string name="account_status_regis_not_sup">Der Server unterstützt keine Registrierung</string> + <string name="account_status_security_error">Sicherheitsfehler</string> + <string name="account_status_incompatible_server">Inkompatibler Server</string> + <string name="encryption_choice_unencrypted">Unverschlüsselt</string> + <string name="encryption_choice_otr">OTR</string> + <string name="encryption_choice_pgp">OpenPGP</string> + <string name="encryption_choice_omemo">OMEMO</string> + <string name="mgmt_account_edit">Konto bearbeiten</string> + <string name="mgmt_account_delete">Löschen</string> + <string name="mgmt_account_disable">Vorübergehend abschalten</string> + <string name="mgmt_account_publish_avatar">Avatar veröffentlichen</string> + <string name="mgmt_account_publish_pgp">Öffentlichen OpenPGP-Schlüssel veröffentlichen</string> + <string name="mgmt_account_enable">Anschalten</string> + <string name="mgmt_account_are_you_sure">Bist du dir sicher?</string> <string name="mgmt_account_delete_confirm_text">Wenn du dein Konto löschst, gehen alle Gesprächsverläufe verloren</string> - <string name="attach_record_voice">Sprache aufzeichnen</string> + <string name="attach_record_voice">Sprache aufzeichnen</string> <string name="account_settings_jabber_id">Jabber-ID</string> <string name="account_settings_password">Passwort</string> - <string name="account_settings_example_jabber_id">benutzer@domain.de</string> - <string name="account_settings_confirm_password">Passwort bestätigen</string> - <string name="password">Passwort</string> - <string name="confirm_password">Passwort bestätigen</string> - <string name="passwords_do_not_match">Passwörter stimmen nicht überein</string> - <string name="invalid_jid">Ungültige Jabber-ID</string> - <string name="error_out_of_memory">Zu wenig Speicher vorhanden. Das Bild ist zu groß</string> - <string name="add_phone_book_text">Möchtest du %s zum Telefonbuch hinzufügen?</string> + <string name="account_settings_example_jabber_id">benutzer@domain.de</string> + <string name="account_settings_confirm_password">Passwort bestätigen</string> + <string name="password">Passwort</string> + <string name="confirm_password">Passwort bestätigen</string> + <string name="passwords_do_not_match">Passwörter stimmen nicht überein</string> + <string name="invalid_jid">Ungültige Jabber-ID</string> + <string name="error_out_of_memory">Zu wenig Speicher vorhanden. Das Bild ist zu groß</string> + <string name="add_phone_book_text">%s zum Telefonbuch hinzufügen</string> <string name="contact_status_online">online</string> - <string name="contact_status_free_to_chat">Bereit</string> - <string name="contact_status_away">Abwesend</string> - <string name="contact_status_extended_away">Abwesend (erweitert)</string> - <string name="contact_status_do_not_disturb">Nicht stören</string> - <string name="contact_status_offline">Offline</string> - <string name="muc_details_conference">Konferenz</string> - <string name="muc_details_other_members">Andere Mitglieder</string> - <string name="server_info_show_more">Server Info</string> - <string name="server_info_mam">XEP-0313: MAM</string> - <string name="server_info_carbon_messages">XEP-0280: Message Carbons</string> + <string name="contact_status_free_to_chat">bereit</string> + <string name="contact_status_away">abwesend</string> + <string name="contact_status_extended_away">länger abwesend</string> + <string name="contact_status_do_not_disturb">nicht stören</string> + <string name="contact_status_offline">offline</string> + <string name="muc_details_conference">Konferenz</string> + <string name="muc_details_other_members">Andere Mitglieder</string> + <string name="server_info_show_more">Server-Info</string> + <string name="server_info_mam">XEP-0313: MAM</string> + <string name="server_info_carbon_messages">XEP-0280: Message Carbons</string> <string name="server_info_csi">XEP-0352: CSI</string> - <string name="server_info_blocking">XEP-0191: Blocking Command</string> - <string name="server_info_roster_version">XEP-0237: Roster Versioning</string> - <string name="server_info_stream_management">XEP-0198: Stream Management</string> - <string name="server_info_pep">XEP-0163: PEP (Avatare)</string> + <string name="server_info_blocking">XEP-0191: Blocking Command</string> + <string name="server_info_roster_version">XEP-0237: Roster Versioning</string> + <string name="server_info_stream_management">XEP-0198: Stream Management</string> + <string name="server_info_pep">XEP-0163: PEP (Avatare/OMEMO)</string> + <string name="server_info_http_upload">XEP-0363: HTTP File Upload</string> <string name="server_info_available">ja</string> <string name="server_info_unavailable">nein</string> - <string name="missing_public_keys">Öffentlicher Schlüssel fehlt</string> - <string name="last_seen_now">online</string> - <string name="last_seen_min">vor einer Minute gesehen</string> - <string name="last_seen_mins">vor %d Minuten gesehen</string> - <string name="last_seen_hour">vor einer Stunde gesehen</string> - <string name="last_seen_hours">vor %d Stunden gesehen</string> - <string name="last_seen_day">vor einem Tag gesehen</string> - <string name="last_seen_days">vor %d Tagen gesehen</string> - <string name="never_seen">noch nie gesehen</string> - <string name="install_openkeychain">Verschlüsselte Nachricht. Bitte installiere OpenKeychain zur Entschlüsselung.</string> - <string name="unknown_otr_fingerprint">Unbekannter OTR-Fingerabdruck</string> - <string name="openpgp_messages_found">Verschlüsselte OpenPGP-Nachricht gefunden</string> - <string name="reception_failed">Empfang ist fehlgeschlagen</string> - <string name="your_fingerprint">Dein Fingerabdruck</string> - <string name="otr_fingerprint">OTR-Fingerabdruck</string> - <string name="verify">Verifizieren</string> - <string name="decrypt">Entschlüsseln</string> - <string name="conferences">Konferenzen</string> - <string name="search">Suche</string> - <string name="create_contact">Kontakt erstellen</string> - <string name="join_conference">Konferenz betreten</string> - <string name="delete_contact">Kontakt löschen</string> + <string name="missing_public_keys">Öffentlicher Schlüssel fehlt</string> + <string name="last_seen_now">online</string> + <string name="last_seen_min">vor einer Minute gesehen</string> + <string name="last_seen_mins">vor %d Minuten gesehen</string> + <string name="last_seen_hour">vor einer Stunde gesehen</string> + <string name="last_seen_hours">vor %d Stunden gesehen</string> + <string name="last_seen_day">vor einem Tag gesehen</string> + <string name="last_seen_days">vor %d Tagen gesehen</string> + <string name="never_seen">noch nie gesehen</string> + <string name="install_openkeychain">Verschlüsselte Nachricht. Bitte installiere OpenKeychain zur Entschlüsselung.</string> + <string name="unknown_otr_fingerprint">Unbekannter OTR-Fingerabdruck</string> + <string name="openpgp_messages_found">Verschlüsselte OpenPGP-Nachricht gefunden</string> + <string name="reception_failed">Empfang fehlgeschlagen</string> + <string name="your_fingerprint">Dein Fingerabdruck</string> + <string name="otr_fingerprint">OTR-Fingerabdruck</string> + <string name="omemo_fingerprint">OMEMO-Fingerabdruck</string> + <string name="omemo_fingerprint_x509">v\\OMEMO-Fingerabdruck</string> + <string name="omemo_fingerprint_selected_message">OMEMO-Fingerabdruck der Nachricht</string> + <string name="omemo_fingerprint_x509_selected_message">v\\OMEMO-Fingerabdruck der Nachricht</string> + <string name="this_device_omemo_fingerprint">Eigener OMEMO-Fingerabdruck</string> + <string name="other_devices">Andere Geräte</string> + <string name="trust_omemo_fingerprints">OMEMO-Fingerabdruck vertrauen</string> + <string name="fetching_keys">Schlüssel werden abgerufen …</string> + <string name="done">Erledigt</string> + <string name="verify">Überprüfen</string> + <string name="decrypt">Entschlüsseln</string> + <string name="conferences">Konferenzen</string> + <string name="search">Suchen</string> + <string name="create_contact">Kontakt erstellen</string> + <string name="enter_contact">Kontakt eingeben</string> + <string name="join_conference">Konferenz beitreten</string> + <string name="delete_contact">Kontakt löschen</string> <string name="view_contact_details">Kontakt-Details anzeigen</string> - <string name="block_contact">Kontakt sperren</string> + <string name="block_contact">Kontakt sperren</string> <string name="unblock_contact">Kontakt entsperren</string> - <string name="create">Erstellen</string> - <string name="contact_already_exists">Der Kontakt existiert bereits</string> - <string name="join">Beitreten</string> + <string name="create">Erstellen</string> + <string name="select">Auswählen</string> + <string name="contact_already_exists">Der Kontakt existiert bereits</string> + <string name="join">Beitreten</string> <string name="conference_address">Konferenz-Adresse</string> <string name="conference_address_example">raum@conference.domain.de</string> <string name="save_as_bookmark">Zur Kontaktliste hinzufügen</string> <string name="delete_bookmark">Von Kontaktliste entfernen</string> <string name="bookmark_already_exists">Die Konferenz befindet sich bereits auf deiner Kontaktliste</string> - <string name="you">Du</string> - <string name="action_edit_subject">Konferenz-Thema anpassen</string> - <string name="conference_not_found">Konferenz nicht gefunden</string> - <string name="leave">Verlassen</string> - <string name="contact_added_you">Der Kontakt hat dich zur Kontaktliste hinzugefügt</string> - <string name="add_back">Auch hinzufügen</string> - <string name="contact_has_read_up_to_this_point">%s hat bis zu diesem Punkt gelesen</string> - <string name="publish">Veröffentlichen</string> - <string name="touch_to_choose_picture">Hier klicken, um einen Avatar auszuwählen</string> - <string name="publish_avatar_explanation">Achtung: Jeder, der deinen Status sehen darf, sieht auch deinen Avatar.</string> + <string name="you">Du</string> + <string name="action_edit_subject">Konferenz-Thema bearbeiten</string> + <string name="conference_not_found">Konferenz nicht gefunden</string> + <string name="leave">Verlassen</string> + <string name="contact_added_you">Der Kontakt hat dich zur Kontaktliste hinzugefügt</string> + <string name="add_back">Auch hinzufügen</string> + <string name="contact_has_read_up_to_this_point">%s hat bis zu diesem Punkt gelesen</string> + <string name="publish">Veröffentlichen</string> + <string name="touch_to_choose_picture">Avatar antippen, um Bild aus Galerie auszuwählen</string> + <string name="publish_avatar_explanation">Achtung: Jeder, der deinen Status sehen darf, sieht auch deinen Avatar.</string> <string name="publishing">Veröffentliche…</string> - <string name="error_publish_avatar_server_reject">Der Server hat die Veröffentlichung des Avatars abgelehnt.</string> - <string name="error_publish_avatar_converting">Bei der Konvertierung des Avatars lief etwas schief.</string> - <string name="error_saving_avatar">Kann Avatar nicht speichern.</string> - <string name="or_long_press_for_default">(Oder klicke lange, um Standard wiederherzustellen)</string> - <string name="error_publish_avatar_no_server_support">Dein Server unterstützt die Veröffentlichung von Avataren nicht.</string> + <string name="error_publish_avatar_server_reject">Der Server hat die Veröffentlichung des Avatars abgelehnt.</string> + <string name="error_publish_avatar_converting">Bei der Konvertierung des Avatars lief etwas schief.</string> + <string name="error_saving_avatar">Avatar kann nicht gespeichert werden</string> + <string name="or_long_press_for_default">(Oder klicke lange, um Standard wiederherzustellen)</string> + <string name="error_publish_avatar_no_server_support">Dein Server unterstützt die Veröffentlichung von Avataren nicht.</string> <string name="private_message">private Nachricht:</string> <string name="private_message_to">privat an %s:</string> - <string name="send_private_message_to">Sende private Nachricht an %s…</string> - <string name="connect">Verbinden</string> - <string name="account_already_exists">Das Konto existiert bereits</string> - <string name="next">Weiter</string> + <string name="send_private_message_to">Private Nachricht an %s senden…</string> + <string name="connect">Verbinden</string> + <string name="account_already_exists">Das Konto existiert bereits</string> + <string name="next">Weiter</string> <string name="server_info_session_established">Sitzung wiederhergestellt</string> <string name="additional_information">Zusätzliche Informationen</string> <string name="skip">Überspringen</string> <string name="disable_notifications">Benachrichtigungen deaktivieren</string> <string name="disable_notifications_for_this_conversation">Benachrichtigungen für diese Unterhaltung deaktivieren</string> - <string name="notifications_disabled">Benachrichtigungen sind deaktiviert</string> <string name="enable">Aktivieren</string> <string name="conference_requires_password">Konferenz ist passwortgeschützt</string> <string name="enter_password">Passwort eingeben</string> - <string name="missing_presence_updates">Fehlender Online-Status vom Kontakt</string> + <string name="missing_presence_updates">Fehlender Online-Status des Kontakts</string> <string name="request_presence_updates">Bitte erst Online-Status vom Kontakt anfragen.\n\n<small>Dies wird verwendet, um festzustellen, welche Client(s) der Kontakt benutzt.</small></string> <string name="request_now">Jetzt anfordern</string> <string name="delete_fingerprint">Fingerabdruck löschen</string> - <string name="sure_delete_fingerprint">Soll dieser Fingerabdruck gelöscht werden?</string> + <string name="sure_delete_fingerprint">Soll dieser Fingerabdruck wirklich gelöscht werden?</string> <string name="ignore">Ignorieren</string> - <string name="without_mutual_presence_updates"><b>Achtung:</b> Ohne gegenseitig den Online-Status zu kennen, kann es zu unerwarteten Problemen kommen.\n\n<small>Bitte die Einstellungen in den Kontakt-Details prüfen.</small></string> + <string name="without_mutual_presence_updates"><b>Achtung:</b> Ohne gegenseitige Kenntnis des Online-Status kann es zu unerwarteten Problemen kommen.\n\n<small>Bitte die Einstellungen in den Kontakt-Details prüfen.</small></string> <string name="pref_encryption_settings">Verschlüsselungs-Einstellungen</string> <string name="pref_force_encryption">Ende-zu-Ende-Verschlüsselung erzwingen</string> <string name="pref_force_encryption_summary">Nachrichten immer verschlüsseln (außer für Konferenzen)</string> @@ -284,23 +297,24 @@ <string name="title_pref_quiet_hours">Ruhige Stunden</string> <string name="title_pref_quiet_hours_start_time">Beginn</string> <string name="title_pref_quiet_hours_end_time">Ende</string> - <string name="title_pref_enable_quiet_hours">Aktiviere ruhige Stunden</string> + <string name="title_pref_enable_quiet_hours">Ruhige Stunden aktivieren</string> <string name="pref_quiet_hours_summary">Benachrichtigungen sind während der ruhigen Stunden stumm.</string> <string name="pref_use_larger_font">Schrift vergrößern</string> - <string name="pref_use_larger_font_summary">Größere Schrift verwenden</string> - <string name="pref_use_send_button_to_indicate_status">Absende-Knopf zeigt Online-Status an</string> - <string name="pref_use_indicate_received">Anfrage für Nachrichtenempfang</string> - <string name="pref_use_indicate_received_summary">Empfangene Nachrichten werden mit einem grünen Häkchen markiert. Bitte beachte, dass dies nicht in allen Fällen funktioniert.</string> - <string name="pref_use_send_button_to_indicate_status_summary">Absende-Knopf einfärben, um den Online-Status des Kontakts zu signalisieren</string> - <string name="pref_expert_options_other">Sonstiges</string> - <string name="pref_conference_name">Konferenz-Name</string> - <string name="pref_conference_name_summary">Konferenz-Thema statt Raum-JID als Namen verwenden</string> - <string name="toast_message_otr_fingerprint">OTR-Fingerabdruck in die Zwischenablage kopiert!</string> + <string name="pref_use_larger_font_summary">Größere Schriften für die gesamte App verwenden</string> + <string name="pref_use_send_button_to_indicate_status">\"Senden\"-Schaltfläche zeigt Online-Status an</string> + <string name="pref_use_indicate_received">Empfangsbestätigungen anfragen</string> + <string name="pref_use_indicate_received_summary">Empfangene Nachrichten werden mit einem grünen Häkchen markiert. Bitte beachte, dass dies nicht in allen Fällen funktioniert.</string> + <string name="pref_use_send_button_to_indicate_status_summary">\"Senden\"-Schaltfläche einfärben, um den Online-Status des Kontakts anzuzeigen</string> + <string name="pref_expert_options_other">Sonstiges</string> + <string name="pref_conference_name">Konferenz-Name</string> + <string name="pref_conference_name_summary">Konferenz-Thema statt Raum-JID als Namen verwenden</string> + <string name="toast_message_otr_fingerprint">OTR-Fingerabdruck in die Zwischenablage kopiert!</string> + <string name="toast_message_omemo_fingerprint">OMEMO-Fingerabdruck in die Zwischenablage kopiert!</string> <string name="conference_banned">Du wurdest von der Konferenz ausgeschlossen</string> <string name="conference_members_only">Die Konferenz ist nur für Mitglieder</string> <string name="conference_kicked">Du wurdest aus der Konferenz geworfen</string> - <string name="using_account">Verwende Konto %s</string> - <string name="checking_x">%s wird auf HTTP-Host geprüft</string> + <string name="using_account">Verwendetes Konto: %s</string> + <string name="checking_x">%s auf HTTP-Host wird überprüft</string> <string name="not_connected_try_again">Nicht verbunden, bitte später versuchen</string> <string name="check_x_filesize">%s-Größe prüfen</string> <string name="message_options">Nachrichtenoptionen</string> @@ -312,81 +326,94 @@ <string name="url_copied_to_clipboard">URL in Zwischenablage kopiert</string> <string name="message_copied_to_clipboard">Nachricht in Zwischenablage kopiert</string> <string name="image_transmission_failed">Bild-Übertragung fehlgeschlagen</string> - <string name="scan_qr_code">Scanne QR-Code</string> - <string name="show_qr_code">Zeige QR-Code</string> - <string name="show_block_list">Zeige Sperrliste</string> + <string name="scan_qr_code">QR-Code scannen</string> + <string name="show_qr_code">QR-Code anzeigen</string> + <string name="show_block_list">Sperrliste anzeigen</string> <string name="account_details">Konto-Details</string> - <string name="verify_otr">Prüfe OTR</string> - <string name="remote_fingerprint">Fingerabdruck der Gegenseite</string> - <string name="scan">Scanne</string> - <string name="or_touch_phones">(oder Touch-Handys)</string> - <string name="smp">Socialist Millionaire Protocol</string> - <string name="shared_secret_hint">Hinweis oder Frage</string> - <string name="shared_secret_secret">Gemeinsamer Schlüssel</string> - <string name="confirm">Bestätige</string> - <string name="in_progress">In Bearbeitung</string> - <string name="respond">Antworten</string> - <string name="failed">Fehlgeschlagen</string> - <string name="secrets_do_not_match">Schlüssel stimmen nicht überein</string> - <string name="try_again">Erneut versuchen</string> - <string name="finish">Fertig</string> - <string name="verified">Überprüft!</string> + <string name="verify_otr">OTR prüfen</string> + <string name="remote_fingerprint">Fingerabdruck der Gegenseite</string> + <string name="scan">Scannen</string> + <string name="smp">Socialist Millionaire Protocol</string> + <string name="shared_secret_hint">Hinweis oder Frage</string> + <string name="shared_secret_secret">Gemeinsamer Schlüssel</string> + <string name="confirm">Bestätigen</string> + <string name="in_progress">In Bearbeitung</string> + <string name="respond">Antworten</string> + <string name="failed">Fehlgeschlagen</string> + <string name="secrets_do_not_match">Schlüssel stimmen nicht überein</string> + <string name="try_again">Erneut versuchen</string> + <string name="finish">Fertig</string> + <string name="verified">Überprüft!</string> <string name="smp_requested">Kontakt fordert eine Überprüfung an</string> - <string name="no_otr_session_found">Keine gültige OTR Sitzung gefunden!</string> - <string name="conversations_foreground_service">Conversations</string> - <string name="pref_keep_foreground_service">Den Dienst im Vordergrund ausführen.</string> + <string name="no_otr_session_found">Keine gültige OTR-Sitzung gefunden!</string> + <string name="conversations_foreground_service">Conversations</string> + <string name="pref_keep_foreground_service">Dienst im Vordergrund ausführen</string> <string name="pref_keep_foreground_service_summary">Verhindert, dass Android Conversations beendet und die Verbindung unterbricht</string> - <string name="choose_file">Datei auswählen</string> - <string name="receiving_x_file">Empfange %1$s (%2$d%% abgeschlossen)</string> - <string name="download_x_file">Lade %s herunter</string> + <string name="pref_export_logs">Chats exportieren</string> + <string name="pref_export_logs_summary">Chats auf SD-Karte schreiben</string> + <string name="notification_export_logs_title">Chats werden auf SD-Karte geschrieben</string> + <string name="choose_file">Datei auswählen</string> + <string name="receiving_x_file">Empfange %1$s (%2$d%% abgeschlossen)</string> + <string name="download_x_file">%s herunterladen</string> <string name="file">Datei</string> - <string name="open_x_file">Öffne %s</string> - <string name="sending_file">Sende (%1$d%% gesendet)</string> - <string name="preparing_file">Bereite Datei für die Übertragung vor</string> + <string name="open_x_file">%s öffnen</string> + <string name="sending_file">Senden (%1$d%% abgeschlossen)</string> + <string name="preparing_file">Datei wird für die Übertragung vorbereitet</string> <string name="x_file_offered_for_download">%s zum Herunterladen angeboten</string> - <string name="cancel_transmission">Datei-Übertragung abbrechen</string> - <string name="file_transmission_failed">Datei-Übertragung fehlgeschlagen</string> - <string name="file_deleted">Datei wurde gelöscht</string> - <string name="no_application_found_to_open_file">Keine Anwendung zum Öffnen der Datei gefunden</string> - <string name="could_not_verify_fingerprint">Kann Fingerabdruck nicht überprüfen</string> - <string name="manually_verify">Manuell überprüfen</string> - <string name="are_you_sure_verify_fingerprint">Bist du sicher, dass du den OTR-Fingerabdruck des Kontakts überprüfen willst?</string> - <string name="pref_show_dynamic_tags">Dynamische Tags anzeigen</string> - <string name="pref_show_dynamic_tags_summary">Zeige schreibgeschützte Tags unterhalb der Kontakte</string> - <string name="enable_notifications">Aktiviere Benachrichtigungen</string> - <string name="conference_with">Beginne Konferenz mit…</string> - <string name="no_conference_server_found">Konferenz-Server kann nicht gefunden werden</string> - <string name="conference_creation_failed">Beginnen der Konferenz fehlgeschlagen!</string> - <string name="conference_created">Konferenz erstellt!</string> - <string name="secret_accepted">Schlüssel akzeptiert!</string> - <string name="reset">Zurücksetzen</string> - <string name="account_image_description">Konto-Avatar</string> - <string name="copy_otr_clipboard_description">OTR-Fingerabdruck in Zwischenablage kopieren</string> + <string name="cancel_transmission">Übertragung abbrechen</string> + <string name="file_transmission_failed">Übertragung fehlgeschlagen</string> + <string name="file_deleted">Datei wurde gelöscht</string> + <string name="no_application_found_to_open_file">Keine Anwendung zum Öffnen der Datei gefunden</string> + <string name="could_not_verify_fingerprint">Fingerabdruck konnte nicht überprüft werden</string> + <string name="manually_verify">Manuell überprüfen</string> + <string name="are_you_sure_verify_fingerprint">Bist du sicher, dass du den OTR-Fingerabdruck des Kontakts überprüfen willst?</string> + <string name="pref_show_dynamic_tags">Dynamische Tags anzeigen</string> + <string name="pref_show_dynamic_tags_summary">Schreibgeschütze Tags unterhalb der Kontakte anzeigen</string> + <string name="enable_notifications">Benachrichtigungen aktivieren</string> + <string name="conference_with">Konferenz erstellen mit…</string> + <string name="no_conference_server_found">Kein Konferenz-Server gefunden</string> + <string name="conference_creation_failed">Erstellen der Konferenz fehlgeschlagen!</string> + <string name="conference_created">Konferenz erstellt!</string> + <string name="secret_accepted">Schlüssel akzeptiert!</string> + <string name="reset">Zurücksetzen</string> + <string name="account_image_description">Konto-Avatar</string> + <string name="copy_otr_clipboard_description">OTR-Fingerabdruck in Zwischenablage kopieren</string> + <string name="copy_omemo_clipboard_description">OMEMO-Fingerabdruck in Zwischenablage kopieren</string> + <string name="regenerate_omemo_key">OMEMO-Schlüssel erneuern</string> + <string name="wipe_omemo_pep">Andere Geräte von PEP entfernen</string> + <string name="clear_other_devices">Geräte entfernen</string> + <string name="clear_other_devices_desc">Alle anderen Geräte aus der OMEMO-Bekanntmachung entfernen? Die Bekanntmachung wird bei der nächsten Verbindung erneuert, möglicherweise werden aber nicht alle Nachrichten empfangen, die in der Zwischenzeit versendet wurden.</string> + <string name="purge_key">Schlüssel löschen</string> + <string name="purge_key_desc_part1">Soll dieser Schlüssel gelöscht werden?</string> + <string name="purge_key_desc_part2">Dieser Vorgang kann nicht rückgängig gemacht werden und es kann nie wieder eine Verbindung mit diesem Schlüssel hergestellt werden.</string> + <string name="error_no_keys_to_trust_server_error">Für diesen Kontakt sind keine benutzbaren Schlüssel verfügbar.\nNeue Schlüssel vom Server herunterzuladen war nicht erfolgreich. Vielleicht stimmt etwas mit dem Server deines Kontakts nicht.</string> + <string name="error_no_keys_to_trust">Es sind keine Schlüssel für diesen Kontakt verfügbar. Falls sie gelöscht wurden, müssen diese neu erstellt werden.</string> + <string name="error_trustkeys_title">Fehler</string> <string name="fetching_history_from_server">Lade Chatverlauf…</string> <string name="no_more_history_on_server">Keine weiteren Nachrichten vorhanden</string> - <string name="updating">Aktualisiere…</string> - <string name="password_changed">Passwort geändert.</string> - <string name="could_not_change_password">Passwort kann nicht geändert werden.</string> - <string name="otr_session_not_started">Sende eine Nachricht, um eine verschlüsselte Unterhaltung zu beginnen</string> - <string name="ask_question">Frage stellen</string> - <string name="smp_explain_question">Falls du mit deinem Kontakt ein gemeinsames Geheimnis hast (z.B. ein Insider-Witz oder was ihr zuletzt gemeinsam zum Mittag gegessen habt), kann dies zur gegenseitigen Überprüfung des Fingerabdrucks genutzt werden.\n\nDu stellst eine Frage oder gibst einen Hinweis und dein Kontakt gibt eine eindeutige Antwort.</string> - <string name="smp_explain_answer">Dein Kontakt möchte deinen Fingerabdruck mit Hilfe eines gemeinsamen Schlüssels überprüfen. Dein Kontakt hat dazu folgende Frage gestellt.</string> - <string name="shared_secret_hint_should_not_be_empty">Deine Frage darf nicht leer sein.</string> - <string name="shared_secret_can_not_be_empty">Dein gemeinsamer Schlüssel darf nicht leer sein</string> - <string name="manual_verification_explanation">Vergleiche den angezeigten Fingerabdruck sorgfältig mit dem deines Kontakts.\nDu kannst dazu einen sicheren Kommunikationsweg (z.B. verschlüsselte E-Mail oder Telefonanruf) zum Austausch nutzen.</string> - <string name="change_password">Passwort ändern</string> - <string name="current_password">Aktuelles Passwort</string> - <string name="new_password">Neues Passwort</string> - <string name="password_should_not_be_empty">Das Passwort darf nicht leer sein</string> + <string name="updating">Aktualisieren…</string> + <string name="password_changed">Passwort geändert.</string> + <string name="could_not_change_password">Passwort kann nicht geändert werden.</string> + <string name="otr_session_not_started">Sende eine Nachricht, um eine verschlüsselte Unterhaltung zu beginnen</string> + <string name="ask_question">Frage stellen</string> + <string name="smp_explain_question">Falls du mit deinem Kontakt ein gemeinsames Geheimnis hast (z.B. ein Insider-Witz oder was ihr zuletzt gemeinsam zum Mittag gegessen habt), kann dies zur gegenseitigen Überprüfung des Fingerabdrucks genutzt werden.\n\nDu stellst eine Frage oder gibst einen Hinweis und dein Kontakt gibt eine eindeutige Antwort.</string> + <string name="smp_explain_answer">Dein Kontakt möchte deinen Fingerabdruck mit Hilfe eines gemeinsamen Schlüssels überprüfen. Dein Kontakt hat dazu folgende Frage gestellt.</string> + <string name="shared_secret_hint_should_not_be_empty">Deine Frage darf nicht leer sein.</string> + <string name="shared_secret_can_not_be_empty">Dein gemeinsamer Schlüssel darf nicht leer sein</string> + <string name="manual_verification_explanation">Vergleiche den angezeigten Fingerabdruck sorgfältig mit dem deines Kontakts.\nDu kannst dazu einen sicheren Kommunikationsweg (z.B. verschlüsselte E-Mail oder Telefonanruf) zum Austausch nutzen.</string> + <string name="change_password">Passwort ändern</string> + <string name="current_password">Aktuelles Passwort</string> + <string name="new_password">Neues Passwort</string> + <string name="password_should_not_be_empty">Das Passwort darf nicht leer sein</string> <string name="password_should_not_contain_only_spaces">Das Passwort darf nicht nur aus Leerzeichen bestehen</string> - <string name="enable_all_accounts">Alle Konten anschalten</string> - <string name="disable_all_accounts">Alle Konten abschalten</string> - <string name="perform_action_with">Aktion durchführen mit</string> + <string name="enable_all_accounts">Alle Konten anschalten</string> + <string name="disable_all_accounts">Alle Konten abschalten</string> + <string name="perform_action_with">Aktion durchführen mit</string> <string name="no_affiliation">Keine Zugehörigkeit</string> <string name="no_role">Keine Rolle</string> <string name="outcast">Ausgeschlossen</string> <string name="member">Mitglied</string> - <string name="advanced_mode">Experten Modus</string> + <string name="advanced_mode">Erweiterter Modus</string> <string name="grant_membership">Mitgliedschaft gewähren</string> <string name="remove_membership">Mitgliedschaft entziehen</string> <string name="grant_admin_privileges">Administratorrechte gewähren</string> @@ -400,8 +427,10 @@ <string name="public_conference">Öffentlich zugängliche Konferenz</string> <string name="private_conference">Private Konferenz nur für Mitglieder</string> <string name="conference_options">Konferenz-Optionen</string> - <string name="members_only">Privat (Nur für Mitglieder)</string> + <string name="members_only">Privat, nur Mitglieder</string> <string name="non_anonymous">De-anonymisiert</string> + <string name="moderated">Moderiert</string> + <string name="you_are_not_participating">Du bist kein Mitglied</string> <string name="modified_conference_options">Konferenz-Optionen wurden modifiziert!</string> <string name="could_not_modify_conference_options">Konferenz-Optionen konnten nicht modifiziert werden</string> <string name="never">Niemals</string> @@ -409,12 +438,12 @@ <string name="one_hour">1 Stunde</string> <string name="two_hours">2 Stunden</string> <string name="eight_hours">8 Stunden</string> - <string name="until_further_notice">Bis auf weiters</string> + <string name="until_further_notice">Bis auf Weiteres</string> <string name="pref_input_options">Eingabe-Optionen</string> <string name="pref_enter_is_send">Eingabe-Taste (Enter) sendet Nachricht</string> - <string name="pref_enter_is_send_summary">Benutze die Eingabe-Taste (Enter) zum Senden einer Nachricht</string> + <string name="pref_enter_is_send_summary">Eingabe-Taste (Enter) zum Versenden einer Nachricht verwenden</string> <string name="pref_display_enter_key">Zeige Eingabe-Taste (Enter)</string> - <string name="pref_display_enter_key_summary">Zeige die Eingabe-Taste (Enter) anstelle der Smiley-Taste</string> + <string name="pref_display_enter_key_summary">Emoji-Taste durch Eingabe-Taste ersetzen</string> <string name="audio">Audio</string> <string name="video">Video</string> <string name="image">Bild</string> @@ -422,12 +451,12 @@ <string name="apk">Android App</string> <string name="vcard">Kontakt</string> <string name="received_x_file">%s empfangen</string> - <string name="disable_foreground_service">Vordergrund-Dienst beenden</string> - <string name="touch_to_open_conversations">Tippen, um Conversations zu öffnen</string> + <string name="disable_foreground_service">Vordergrund-Dienst deaktivieren</string> + <string name="touch_to_open_conversations">Antippen, um Conversations zu öffnen</string> <string name="avatar_has_been_published">Avatar wurde gespeichert</string> - <string name="sending_x_file">Sende %s</string> - <string name="offering_x_file">%s angeboten</string> - <string name="hide_offline">verstecke offline</string> + <string name="sending_x_file">%s wird gesendet</string> + <string name="offering_x_file">%s wird angeboten</string> + <string name="hide_offline">Offline verstecken</string> <string name="disable_account">Konto abschalten</string> <string name="contact_is_typing">%s schreibt…</string> <string name="contact_has_stopped_typing">%s schreibt nicht mehr</string> @@ -440,8 +469,7 @@ <string name="received_location">Standort empfangen</string> <string name="title_undo_swipe_out_conversation">Unterhaltung beendet</string> <string name="title_undo_swipe_out_muc">Konferenz verlassen</string> - <string name="pref_certificate_options">Zertifikats-Optionen</string> - <string name="pref_dont_trust_system_cas_title">Misstraue Zertifizierungsstellen</string> + <string name="pref_dont_trust_system_cas_title">Zertifizierungsstellen misstrauen</string> <string name="pref_dont_trust_system_cas_summary">Alle Zertifikate müssen manuell bestätigt werden</string> <string name="pref_remove_trusted_certificates_title">Zertifikate löschen</string> <string name="pref_remove_trusted_certificates_summary">Als vertrauenswürdig bestätigte Zertifikate löschen</string> @@ -462,7 +490,6 @@ <string name="none">keine</string> <string name="recently_used">zuletzt verwendet</string> <string name="choose_quick_action">wähle Schnell-Taste</string> - <string name="file_not_found_on_remote_host">Datei auf Server nicht gefunden</string> <string name="search_for_contacts_or_groups">Nach Kontakten oder Konferenzen suchen</string> <string name="pref_led_notification_color">LED-Benachrichtigung Farbe</string> <string name="pref_led_notification_color_summary">Setze die Farbe der LED-Benachrichtigung</string> @@ -512,4 +539,72 @@ <string name="pref_file_transfer_category">Dateiübertragung</string> <string name="cplus_not_copied_to_clipboard_empty">Nichts zu kopieren.</string> <string name="cplus_me">Ich</string> + <string name="send_private_message">Private Nachricht senden</string> + <string name="user_has_left_conference">%s hat die Konferenz verlassen!</string> + <string name="username">Benutzername</string> + <string name="username_hint">Benutzername</string> + <string name="invalid_username">Ungültiger Benutzername</string> + <string name="download_failed_server_not_found">Download fehlgeschlagen: Server nicht gefunden</string> + <string name="download_failed_file_not_found">Download fehlgeschlagen: Datei nicht gefunden</string> + <string name="download_failed_could_not_connect">Download fehlgeschlagen: keine Verbindung zum Host</string> + <string name="pref_use_white_background">Weißen Hintergrund benutzen</string> + <string name="pref_use_white_background_summary">Empfangene Nachrichten als schwarzen Text auf weißem Hintergrund anzeigen</string> + <string name="account_status_tor_unavailable">Tor-Netzwerk nicht verfügbar</string> + <string name="server_info_broken">Fehlerhaft</string> + <string name="pref_presence_settings">Status Einstellungen</string> + <string name="pref_away_when_screen_off">Abwesend bei abgeschaltetem Bildschirm</string> + <string name="pref_away_when_screen_off_summary">Setzt deinen Status auf \"abwesend\", solange dein Bildschirm abgeschaltet ist</string> + <string name="pref_xa_on_silent_mode">Nicht verfügbar bei Stummschaltung</string> + <string name="pref_xa_on_silent_mode_summary">Setzt deinen Status auf \"nicht verfügbar\", solange dein Telefon lautlos ist</string> + <string name="action_add_account_with_certificate">Konto mit Zertifikat hinzufügen</string> + <string name="unable_to_parse_certificate">Zertifikat kann nicht gelesen werden</string> + <string name="authenticate_with_certificate">Leer lassen, um mit Zertifikat anzumelden</string> + <string name="captcha_ocr">Captcha Text</string> + <string name="captcha_required">Captcha erforderlich</string> + <string name="captcha_hint">Text aus Captcha eintragen</string> + <string name="certificate_chain_is_not_trusted">Zertifikat wird nicht vertraut</string> + <string name="jid_does_not_match_certificate">Jabber-ID stimmt nicht dem Zertifikat überein</string> + <string name="action_renew_certificate">Zertifikat erneuern</string> + <string name="error_fetching_omemo_key">Kann OMEMO Schlüssel nicht empfangen!</string> + <string name="verified_omemo_key_with_certificate">OMEMO Schlüssel mit Zertifikat bestätigt!</string> + <string name="device_does_not_support_certificates">Dein Gerät unterstützt das Auswählen von Client-Zertifikaten nicht!</string> + <string name="pref_connection_options">Verbindungs-Optionen</string> + <string name="pref_use_tor">Über TOR verbinden</string> + <string name="pref_use_tor_summary">Alle Verbindungen über das Tor-Netzwerk tunneln. Benötigt Orbot</string> + <string name="account_settings_hostname">Hostname</string> + <string name="account_settings_port">Port</string> + <string name="hostname_or_onion">Server- oder .onion-Adresse</string> + <string name="not_a_valid_port">Dies ist keine gültige Port-Nummer</string> + <string name="not_valid_hostname">Dies ist kein gültiger Hostname</string> + <string name="connected_accounts">%1$d von %2$d Konten verbunden</string> + <plurals name="x_messages"> + <item quantity="one">%d Nachricht</item> + <item quantity="other">%d Nachrichten</item> + </plurals> + <string name="shared_file_with_x">Datei mit %s geteilt</string> + <string name="shared_image_with_x">Bild mit %s geteilt</string> + <string name="no_storage_permission">Conversations benötigt Zugriff auf externen Speicher</string> + <string name="sync_with_contacts">Mit Kontakten synchronisieren</string> + <string name="sync_with_contacts_long">Conversations möchte deine XMPP-Kontaktliste mit deinen Kontakten abgleichen, um deren vollständige Namen und Avatare anzuzeigen.\n\nConversations wird deine Kontakte nur lokal lesen und abgleichen und überträgt diese nicht auf den Server.\n\nDu wirst nun gefragt, ob du den Zugriff auf deine Kontakte erlauben möchtest.</string> + <string name="certificate_information">Zertifikatinformationen</string> + <string name="certificate_subject">Betreff</string> + <string name="certificate_issuer">Aussteller</string> + <string name="certificate_cn">Gemeinsamer Name</string> + <string name="certificate_o">Organisation</string> + <string name="certificate_sha1">SHA1</string> + <string name="certicate_info_not_available">(Nicht verfügbar)</string> + <string name="certificate_not_found">Kein Zertifikat gefunden</string> + <string name="notify_on_all_messages">Bei allen Nachrichten benachrichtigen</string> + <string name="notify_only_when_highlighted">Nur benachrichtigen, wenn ich angesprochen werde</string> + <string name="notify_never">Benachrichtigungen deaktiviert</string> + <string name="notify_paused">Benachrichtigungen pausiert</string> + <string name="pref_picture_compression">Bilder komprimieren</string> + <string name="pref_picture_compression_summary">Bildgröße anpassen und komprimieren</string> + <string name="always">Immer</string> + <string name="automatically">Automatisch</string> + <string name="battery_optimizations_enabled">Batterieoptimierung aktiv</string> + <string name="battery_optimizations_enabled_explained">Dein Telefon wendet Batterioptimierungen bei Conversations an, welche verspätete Benachrichtigungen oder Nachrichtenverlust verursachen können.\nEs ist empfehlenswert diese zu deaktivieren.</string> + <string name="battery_optimizations_enabled_dialog">Dein Telefon wendet Batterioptimierungen bei Conversations an, welche verspätete Benachrichtigungen oder Nachrichtenverlust verursachen können. Es ist empfehlenswert dies zu deaktivieren.</string> + <string name="disable">Deaktivieren</string> + <string name="selection_too_large">Der ausgewählte Bereich ist zu groß</string> </resources> diff --git a/src/main/res/values-el/strings.xml b/src/main/res/values-el/strings.xml index 0e2dc010..e55a6b2d 100644 --- a/src/main/res/values-el/strings.xml +++ b/src/main/res/values-el/strings.xml @@ -9,7 +9,6 @@ <string name="action_secure">Ασφαλής συζήτηση</string> <string name="action_add_account">Προσθήκη λογαριασμού</string> <string name="action_edit_contact">Επεξεργασία ονόματος</string> - <string name="action_add_phone_book">Προσθήκη στον τηλεφωνικό κατάλογο</string> <string name="action_delete_contact">Διαγραφή από τη λίστα επαφών</string> <string name="action_block_contact">Αποκλεισμός επαφής</string> <string name="action_unblock_contact">Άρση αποκλεισμού επαφής</string> @@ -28,7 +27,6 @@ <string name="minutes_ago">πριν από %d λεπτά</string> <string name="unread_conversations">μη αναγνωσμένες Συζητήσεις</string> <string name="sending">αποστολή...</string> - <string name="encrypted_message">Αποκρυπτογράφηση μηνύματος. Παρακαλώ περιμένετε...</string> <string name="nick_in_use">Το ψευδώνυμο είναι ήδη σε χρήση</string> <string name="admin">Διαχειριστής</string> <string name="owner">Κάτοχος</string> @@ -74,9 +72,7 @@ <string name="clear_conversation_history">Καθαρισμός ιστορικού Συζήτησης</string> <string name="clear_histor_msg">Θέλετε να σβήσετε όλα τα μηνύματα αυτής της Συζήτησης;\n\n<b>Προειδοποίηση:</b> Αυτό δεν θα επηρεάσει τα μηνύματα που είναι αποθηκευμένα σε άλλες συσκευές ή άλλους διακομιστές.</string> <string name="delete_messages">Διαγραφή μηνυμάτων</string> - <string name="also_end_conversation">Τερματισμός αυτής της συζήτησης αμέσως μετά</string> <string name="choose_presence">Επιλέξτε παρουσία για επικοινωνία</string> - <string name="send_plain_text_message">Αποστολή απλού μηνύματος κειμένου</string> <string name="send_otr_message">Αποστολή κρυπτογραφημένου μηνύματος OTR</string> <string name="send_pgp_message">Αποστολή κρυπτογραφημένου μηνύματος OpenPGP</string> <string name="your_nick_has_been_changed">Το ψευδώνυμό σας έχει αλλάξει</string> @@ -92,12 +88,11 @@ <string name="contact_has_no_pgp_key">Το Conversations αδυνατεί να κρυπτογραφήσει τα μηνύματά σας γιατί η επαφή σας δεν ανακοινώνει το δημόσιο κλειδί της.\n\n<small>Παρακαλώ ζητήστε από την επαφή σας να εγκαταστήσει το OpenPGP.</small></string> <string name="no_pgp_keys">Δεν βρέθηκαν κλειδιά OpenPGP</string> <string name="contacts_have_no_pgp_keys">Το Conversations αδυνατεί να κρυπτογραφήσει τα μηνύματά σας γιατί οι επαφές σας δεν ανακοινώνουν το δημόσιο κλειδί τους.\n\n<small>Παρακαλώ ζητήστε από τις επαφές σας να εγκαταστήσουν το OpenPGP.</small></string> - <string name="encrypted_message_received"><i>Λήψη κρυπτογραφημένου μηνύματος. Επιλέξτε για ανάγνωση και αποκρυπτογράφηση.</i></string> <string name="pref_general">Γενικά</string> <string name="pref_xmpp_resource">πόρος XMPP</string> <string name="pref_xmpp_resource_summary">Το όνομα με το οποίο ταυτοποιείται αυτό το πρόγραμμα-πελάτης</string> <string name="pref_accept_files">Αποδοχή αρχείων</string> - <string name="pref_accept_files_size_summary">Αυτόματη αποδοχή αρχείων μικρότερα από...</string> + <string name="pref_accept_files_summary">Αυτόματη αποδοχή αρχείων μικρότερα από...</string> <string name="pref_notification_settings">Επιλογές ειδοποιήσεων</string> <string name="pref_notifications">Ειδοποιήσεις</string> <string name="pref_notifications_summary">Ειδοποίηση όταν λαμβάνεται ένα νέο μήνυμα</string> @@ -105,8 +100,6 @@ <string name="pref_vibrate_summary">Επίσης δόνηση όταν έρχεται ένα νέο μήνυμα</string> <string name="pref_sound">Ήχος</string> <string name="pref_sound_summary">Αναπαραγωγή ήχου κλήσης με την ειδοποίηση</string> - <string name="pref_conference_notifications">Ειδοποιήσεις συνδιασκέψεων</string> - <string name="pref_conference_notifications_summary">Ειδοποίηση πάντα όταν ένα νέο μήνυμα συνδιάσκεψης λαμβάνεται αντί για μόνο όταν τονίζεται</string> <string name="pref_notification_grace_period">Περίοδος χάριτος ειδοποιήσεων</string> <string name="pref_notification_grace_period_summary">Απενεργοποίηση ειδοποιήσεων για λίγο χρόνο μετά από τη λήψη ακριβούς αντιγράφου</string> <string name="pref_advanced_options">Προχωρημένες επιλογές</string> @@ -149,7 +142,6 @@ <string name="account_status_regis_not_sup">Ο διακομιστής δεν υποστηρίζει εγγραφή</string> <string name="account_status_security_error">Σφάλμα ασφάλειας</string> <string name="account_status_incompatible_server">Μη συμβατός διακομιστής</string> - <string name="encryption_choice_none">Απλό κείμενο</string> <string name="encryption_choice_otr">OTR</string> <string name="encryption_choice_pgp">OpenPGP</string> <string name="mgmt_account_edit">Επεξεργασία λογαριασμού</string> @@ -170,7 +162,6 @@ <string name="passwords_do_not_match">Τα συνθηματικά δεν ταιριάζουν</string> <string name="invalid_jid">Αυτή δεν είναι έγκυρη ταυτότητα Jabber</string> <string name="error_out_of_memory">Πλήρης μνήμη. Η εικόνα είναι πολύ μεγάλη</string> - <string name="add_phone_book_text">Θέλετε να προσθέσετε την επαφή %s στον τηλεφωνικό κατάλογο του τηλεφώνου σας;</string> <string name="contact_status_online">συνδεμένος</string> <string name="contact_status_free_to_chat">ελεύθερος για συνομιλία</string> <string name="contact_status_away">λείπω</string> @@ -186,7 +177,6 @@ <string name="server_info_blocking">XEP-0191: Εντολή αποκλεισμού</string> <string name="server_info_roster_version">XEP-0237: Διατήρηση εκδόσεων λίστας επαφών</string> <string name="server_info_stream_management">XEP-0198: Διαχείριση ροών</string> - <string name="server_info_pep">XEP-0163: Πρωτόκολλο προσωπικών συμβάντων (εικόνες προφίλ)</string> <string name="server_info_available">διαθέσιμος</string> <string name="server_info_unavailable">μη διαθέσιμος</string> <string name="missing_public_keys">Ελλειπείς ανακοινώσεις δημοσίων κλειδιών</string> @@ -249,7 +239,6 @@ <string name="skip">Παράλειψη</string> <string name="disable_notifications">Απενεργοποίηση ειδοποιήσεων</string> <string name="disable_notifications_for_this_conversation">Απενεργοποίηση ειδοποιήσεων για αυτή την συζήτηση</string> - <string name="notifications_disabled">Οι ειδοποιήσεις είναι απενεργοποιημένες</string> <string name="enable">Ενεργοποίηση</string> <string name="conference_requires_password">Η συζήτηση απαιτεί συνθηματικό</string> <string name="enter_password">Εισαγωγή συνθηματικού</string> @@ -304,7 +293,6 @@ <string name="verify_otr">Επαλήθευση OTR</string> <string name="remote_fingerprint">Απομακρυσμένο αποτύπωμα</string> <string name="scan">σάρωση</string> - <string name="or_touch_phones">(ή τηλέφωνα επαφής)</string> <string name="smp">Πρωτόκολλο Socialist Millionaire</string> <string name="shared_secret_hint">Υπαινιγμός ή ερώτηση</string> <string name="shared_secret_secret">Κοινό μυστικό</string> @@ -384,7 +372,6 @@ <string name="public_conference">Συνδιάσκεψη δημόσιας πρόσβασης</string> <string name="private_conference">Ιδιωτική συνδιάσκεψη, μόνο για μέλη</string> <string name="conference_options">Επιλογές συνδιάσκεψης</string> - <string name="members_only">Ιδιωτική (μόνο για μέλη)</string> <string name="non_anonymous">Μη-ανώνυμα</string> <string name="modified_conference_options">Μεταβολή των επιλογών συνδιάσκεψης!</string> <string name="could_not_modify_conference_options">Δεν ήταν δυνατή η μεταβολή των επιλογών συνδιάσκεψης</string> @@ -413,7 +400,6 @@ <string name="offering_x_file">Προσφορά του %s</string> <string name="hide_offline">Απόκρυψη των εκτός σύνδεσης</string> <string name="disable_account">Απενεργοποίηση λογαριασμού</string> - <string name="contact_is_typing">Ο χρήστης %s γράφει...</string> <string name="contact_has_stopped_typing">Ο χρήστης %s σταμάτησε να γράφει</string> <string name="pref_chat_states">Ειδοποιήσεις πληκτρολόγησης</string> <string name="pref_chat_states_summary">Επιτρέψτε στην επαφή σας να γνωρίζει πότε γράφετε ένα νέο μήνυμα</string> @@ -424,7 +410,6 @@ <string name="received_location">Ελήφθη τοποθεσία</string> <string name="title_undo_swipe_out_conversation">Η συζήτηση έκλεισε</string> <string name="title_undo_swipe_out_muc">Έφυγε από την συνδιάσκεψη</string> - <string name="pref_certificate_options">Επιλογές πιστοποιητικών</string> <string name="pref_dont_trust_system_cas_title">Μη έμπιστες αρχές πιστοποίησης συστήματος</string> <string name="pref_dont_trust_system_cas_summary">Όλα τα πιστοποιητικά πρέπει να εγκριθούν χειροκίνητα</string> <string name="pref_remove_trusted_certificates_title">Αφαίρεση πιστοποιητικών</string> diff --git a/src/main/res/values-es/strings.xml b/src/main/res/values-es/strings.xml index 4d8bef38..b327c1eb 100644 --- a/src/main/res/values-es/strings.xml +++ b/src/main/res/values-es/strings.xml @@ -9,7 +9,7 @@ <string name="action_secure">Conversación segura</string> <string name="action_add_account">Añadir cuenta</string> <string name="action_edit_contact">Editar contacto</string> - <string name="action_add_phone_book">Añadir a contactos del teléfono</string> + <string name="action_add_phone_book">Añadir a contactos</string> <string name="action_delete_contact">Eliminar contacto de la lista</string> <string name="action_block_contact">Bloquear contacto</string> <string name="action_unblock_contact">Desbloquear contacto</string> @@ -28,7 +28,8 @@ <string name="minutes_ago">hace %d min</string> <string name="unread_conversations">conversaciones por leer</string> <string name="sending">enviando…</string> - <string name="encrypted_message">Descifrando mensaje. Espera por favor…</string> + <string name="message_decrypting">Descifrando mensaje. Por favor, espera...</string> + <string name="pgp_message">Mensaje cifrado con OpenPGP</string> <string name="nick_in_use">El apodo ya está en uso</string> <string name="admin">Administrador</string> <string name="owner">Propietario</string> @@ -76,8 +77,10 @@ <string name="delete_messages">Borrar mensajes</string> <string name="also_end_conversation">Además, terminar esta conversación</string> <string name="choose_presence">Selecciona recurso del contacto</string> - <string name="send_plain_text_message">Enviar mensaje de texto</string> + <string name="send_unencrypted_message">Enviar mensaje sin cifrar</string> <string name="send_otr_message">Enviar mensaje cifrado con OTR</string> + <string name="send_omemo_message">Enviar mensaje cifrado con OMEMO </string> + <string name="send_omemo_x509_message">Enviar mensaje cifrado v\\OMEMO</string> <string name="send_pgp_message">Enviar mensaje cifrado con OpenPGP</string> <string name="your_nick_has_been_changed">Tu apodo se ha modificado</string> <string name="send_unencrypted">Enviar sin cifrar</string> @@ -86,18 +89,19 @@ <string name="openkeychain_required_long">Conversations utiliza una aplicación de terceros llamada <b>OpenKeychain</b> para cifrar y descifrar mensajes y gestionar tus claves públicas.\n\nOpenKeychain está publicado bajo licencia GPLv3 y disponible on F-Droid y Google Play.\n\n<small>(Por favor, reinicie Conversations después.)</small></string> <string name="restart">Reiniciar</string> <string name="install">Instalar</string> + <string name="openkeychain_not_installed">Por favor, instala OpenKeyChain</string> <string name="offering">ofreciendo…</string> <string name="waiting">esperando…</string> <string name="no_pgp_key">Clave OpenPGP no encontrada</string> <string name="contact_has_no_pgp_key">Conversations no ha podido cifrar tus mensajes porque tu contacto no está anunciando su clave publica.\n\n<small>Por favor, pide a tu contacto que configure OpenPGP.</small></string> <string name="no_pgp_keys">Claves OpenPGP no encontradas</string> <string name="contacts_have_no_pgp_keys">Conversations no ha podido cifrar tus mensajes porque tus contactos no están anunciando su clave publica.\n\n<small>Por favor, pide a tus contactos que configuren OpenPGP.</small></string> - <string name="encrypted_message_received"><i>Mensaje cifrado recibido. Pulsa para ver.</i></string> + <string name="encrypted_message_received"><i>Mensaje cifrado recibido. Pulsa para descifrar.</i></string> <string name="pref_general">General</string> <string name="pref_xmpp_resource">Recurso</string> <string name="pref_xmpp_resource_summary">El nombre que identifica el cliente que estás utilizando</string> <string name="pref_accept_files">Aceptar archivos</string> - <string name="pref_accept_files_size_summary">De forma automática aceptar archivos menores que…</string> + <string name="pref_accept_files_summary">De forma automática aceptar archivos menores que…</string> <string name="pref_notification_settings">Ajustes de notificación</string> <string name="pref_notifications">Notificaciones</string> <string name="pref_notifications_summary">Notifica cuando llega un nuevo mensaje</string> @@ -105,14 +109,12 @@ <string name="pref_vibrate_summary">Vibra cuando llega un nuevo mensaje</string> <string name="pref_sound">Sonido</string> <string name="pref_sound_summary">Reproduce tono con la notificación</string> - <string name="pref_conference_notifications">Notif. conversación grupo</string> - <string name="pref_conference_notifications_summary">Siempre notifica cuando llega un mensaje a una conversación en grupo y no solo cuando alguien menciona tu nombre en un mensaje</string> <string name="pref_notification_grace_period">Notificaciones Carbons</string> <string name="pref_notification_grace_period_summary">Deshabilita las notificaciones durante un corto periodo de tiempo después de recibir la copia del mensaje carbon</string> <string name="pref_advanced_options">Opciones avanzadas</string> <string name="pref_never_send_crash">Nunca informar de errores</string> <string name="pref_never_send_crash_summary">Si envías registros de error ayudas al desarrollo de Conversations</string> - <string name="pref_confirm_messages">Confirmar Mensajes</string> + <string name="pref_confirm_messages">Confirmar mensajes</string> <string name="pref_confirm_messages_summary">Permitir a tus contactos saber cuando recibes y lees un mensaje</string> <string name="pref_ui_options">Opciones de interfaz</string> <string name="openpgp_error">OpenKeychain reportó un error</string> @@ -149,9 +151,10 @@ <string name="account_status_regis_not_sup">El servidor no soporta registros</string> <string name="account_status_security_error">Error de seguridad</string> <string name="account_status_incompatible_server">Servidor incompatible</string> - <string name="encryption_choice_none">Texto plano</string> + <string name="encryption_choice_unencrypted">Sin cifrado</string> <string name="encryption_choice_otr">OTR</string> <string name="encryption_choice_pgp">OpenPGP</string> + <string name="encryption_choice_omemo">OMEMO</string> <string name="mgmt_account_edit">Editar cuenta</string> <string name="mgmt_account_delete">Eliminar cuenta</string> <string name="mgmt_account_disable">Deshabilitar temporalmente</string> @@ -170,7 +173,7 @@ <string name="passwords_do_not_match">Las contraseñas no coinciden</string> <string name="invalid_jid">El identificador no es un identificador Jabber válido</string> <string name="error_out_of_memory">Sin memoria. La imagen es demasiado grande</string> - <string name="add_phone_book_text">¿Quieres añadir a %s a tus contactos del teléfono?</string> + <string name="add_phone_book_text">¿Quieres añadir a %s a tus contactos?</string> <string name="contact_status_online">Disponible</string> <string name="contact_status_free_to_chat">Hablador</string> <string name="contact_status_away">Ausente</string> @@ -186,7 +189,8 @@ <string name="server_info_blocking">XEP-0191: Blocking Command</string> <string name="server_info_roster_version">XEP-0237: Roster Versioning</string> <string name="server_info_stream_management">XEP-0198: Stream Management</string> - <string name="server_info_pep">XEP-0163: PEP (Avatars)</string> + <string name="server_info_pep">XEP-0163: PEP (Avatars / OMEMO)</string> + <string name="server_info_http_upload">XEP-0363: HTTP File Upload</string> <string name="server_info_available">Sí</string> <string name="server_info_unavailable">No</string> <string name="missing_public_keys">Se han perdido las claves de anuncio públicas</string> @@ -204,17 +208,28 @@ <string name="reception_failed">Error al recibir</string> <string name="your_fingerprint">Tu huella digital</string> <string name="otr_fingerprint">Huella digital OTR</string> + <string name="omemo_fingerprint">Huella digital OMEMO</string> + <string name="omemo_fingerprint_x509">Huella digital v\\OMEMO</string> + <string name="omemo_fingerprint_selected_message">Huella digital OMEMO del mensaje</string> + <string name="omemo_fingerprint_x509_selected_message">huella digital v\\OMEMO del mensaje</string> + <string name="this_device_omemo_fingerprint">Tu huella digital OMEMO</string> + <string name="other_devices">Otros dispositivos</string> + <string name="trust_omemo_fingerprints">Huellas digitales OMEMO de confianza</string> + <string name="fetching_keys">Buscando claves...</string> + <string name="done">Hecho</string> <string name="verify">Verificar</string> <string name="decrypt">Descifrar</string> <string name="conferences">Conversación Grupo</string> <string name="search">Buscar</string> <string name="create_contact">Crear Contacto</string> + <string name="enter_contact">Introducir contacto</string> <string name="join_conference">Unirse a Conversación en grupo</string> <string name="delete_contact">Eliminar Contacto</string> <string name="view_contact_details">Ver detalles del contacto</string> <string name="block_contact">Bloquear contacto</string> <string name="unblock_contact">Desbloquear contacto</string> <string name="create">Crear</string> + <string name="select">Seleccionar</string> <string name="contact_already_exists">El contacto ya existe</string> <string name="join">Unirse</string> <string name="conference_address">Dirección</string> @@ -249,7 +264,6 @@ <string name="skip">Omitir</string> <string name="disable_notifications">Deshabilitar notificaciones</string> <string name="disable_notifications_for_this_conversation">Deshabilitar notificaciones para esta conversación</string> - <string name="notifications_disabled">Las notificaciones están deshabilitadas</string> <string name="enable">Habilitar</string> <string name="conference_requires_password">Esta conversación requiere contraseña</string> <string name="enter_password">Introduce la contraseña</string> @@ -284,6 +298,7 @@ <string name="pref_conference_name">Nombre conversación grupo</string> <string name="pref_conference_name_summary">Usar el asunto de la conversación en lugar del identificador jabber como nombre en las conversaciones en grupo</string> <string name="toast_message_otr_fingerprint">¡Huella digital OTR copiada al portapapeles!</string> + <string name="toast_message_omemo_fingerprint">¡Huella digital OMEMO copiada al portapapeles!</string> <string name="conference_banned">Tu entrada a esta conversación ha sido prohibida</string> <string name="conference_members_only">Esta conversación es solo para miembros</string> <string name="conference_kicked">Has sido expulsado de esta conversación</string> @@ -307,7 +322,6 @@ <string name="verify_otr">Verificar OTR</string> <string name="remote_fingerprint">Huella digital remota</string> <string name="scan">escanear</string> - <string name="or_touch_phones">(o une los teléfonos)</string> <string name="smp">Protocolo del Socialista Millonario</string> <string name="shared_secret_hint">Sugerencia o pregunta</string> <string name="shared_secret_secret">Secreto compartido</string> @@ -324,6 +338,9 @@ <string name="conversations_foreground_service">Conversations</string> <string name="pref_keep_foreground_service">Servicio en primer plano</string> <string name="pref_keep_foreground_service_summary">Mantener el servicio en primer plano previene que el sistema cierre la conexión</string> + <string name="pref_export_logs">Exportar logs</string> + <string name="pref_export_logs_summary">Escribir logs en la tarjeta SD</string> + <string name="notification_export_logs_title">Escribiendo logs en la tarjeta SD</string> <string name="choose_file">Seleccionar archivo</string> <string name="receiving_x_file">Recibiendo %1$s (%2$d%% completado)</string> <string name="download_x_file">Descargar %s</string> @@ -350,6 +367,17 @@ <string name="reset">Reinicializar</string> <string name="account_image_description">Imagen de perfil</string> <string name="copy_otr_clipboard_description">Copiar huella digital OTR al portapapeles</string> + <string name="copy_omemo_clipboard_description">Copiar huella digital OMEMO al portapapeles</string> + <string name="regenerate_omemo_key">Regenerar clave OMEMO</string> + <string name="wipe_omemo_pep">Limpiar otros dispositivos de PEP</string> + <string name="clear_other_devices">Limpiar dispositivos</string> + <string name="clear_other_devices_desc">¿Estás seguro de que quieres limpiar todos los otros dispositivos del anuncio OMEMO? La próxima vez que tus dispositivos conecten, tendrán que volver a anunciarse, pero estos podrían no recibir los mensajes enviados durante el proceso.</string> + <string name="purge_key">Eliminar clave</string> + <string name="purge_key_desc_part1">¿Estás seguro de que quieres eliminar esta clave?</string> + <string name="purge_key_desc_part2">Esto será irreversible y nunca podrás iniciar sesión con esta clave de nuevo.</string> + <string name="error_no_keys_to_trust_server_error">No hay claves usables disponibles para este contacto. La búsqueda de nuevas claves al servidor ha fallado. Puede que exista un problema con el servidor de tus contactos.</string> + <string name="error_no_keys_to_trust">No hay claves usables disponibles para este contacto. Si has eliminado alguna de sus claves, tus contactos necesitarán generar nuevas claves.</string> + <string name="error_trustkeys_title">Error</string> <string name="fetching_history_from_server">Buscando historial en el servidor</string> <string name="no_more_history_on_server">No hay más historial en el servidor</string> <string name="updating">Actualizando…</string> @@ -387,8 +415,10 @@ <string name="public_conference">Conversación de acceso público</string> <string name="private_conference">Conversación privada solo para miembros</string> <string name="conference_options">Opciones de conversación</string> - <string name="members_only">Privada (solo miembros)</string> + <string name="members_only">Privada, solo miembros</string> <string name="non_anonymous">No anónima</string> + <string name="moderated">Moderada</string> + <string name="you_are_not_participating">No estás participando</string> <string name="modified_conference_options">¡Modificadas las opciones de la conversación!</string> <string name="could_not_modify_conference_options">No se pueden modificar las opciones de la conversación</string> <string name="never">Nunca</string> @@ -427,13 +457,12 @@ <string name="received_location">Ubicación recibida</string> <string name="title_undo_swipe_out_conversation">Conversación cerrada</string> <string name="title_undo_swipe_out_muc">Has salido de la conversación</string> - <string name="pref_certificate_options">Opciones de Certificados</string> <string name="pref_dont_trust_system_cas_title">No confiar en los CAs del sistema</string> <string name="pref_dont_trust_system_cas_summary">Todos los certificados deben ser aprobados manualmente</string> - <string name="pref_remove_trusted_certificates_title">Eliminar Certificados</string> + <string name="pref_remove_trusted_certificates_title">Eliminar certificados</string> <string name="pref_remove_trusted_certificates_summary">Eliminar manualmente certificados aceptados</string> <string name="toast_no_trusted_certs">No aceptar certificados manualmente</string> - <string name="dialog_manage_certs_title">Eliminar Certificados</string> + <string name="dialog_manage_certs_title">Eliminar certificados</string> <string name="dialog_manage_certs_positivebutton">Eliminar seleccionados</string> <string name="dialog_manage_certs_negativebutton">Cancelar</string> <plurals name="toast_delete_certificates"> @@ -445,10 +474,77 @@ <item quantity="other">Seleccionados %d contactos</item> </plurals> <string name="pref_quick_action_summary">Cambiar el botón de enviar por botón de acción rápida</string> - <string name="pref_quick_action">Acción Rápida</string> + <string name="pref_quick_action">Acción rápida</string> <string name="none">Ninguna</string> <string name="recently_used">Usada más recientemente</string> <string name="choose_quick_action">Elegir acción rápida</string> - <string name="file_not_found_on_remote_host">Archivo no encontrado en servidor remoto</string> <string name="search_for_contacts_or_groups">Buscar contactos o grupos</string> + <string name="send_private_message">Enviar mensaje privado</string> + <string name="user_has_left_conference">¡%s ha dejado la conversación!</string> + <string name="username">Usuario</string> + <string name="username_hint">Usuario</string> + <string name="invalid_username">Esto no es un usuario válido</string> + <string name="download_failed_server_not_found">Error al descargar: Servidor no encontrado</string> + <string name="download_failed_file_not_found">Error al descargar: Archivo no encontrado</string> + <string name="download_failed_could_not_connect">Error al descargar: No se ha podido conectar con el servidor</string> + <string name="pref_use_white_background">Usar fondo blanco</string> + <string name="pref_use_white_background_summary">Mostrar mensajes recibidos en texto negro con fondo blanco</string> + <string name="account_status_tor_unavailable">Red Tor no disponible.</string> + <string name="server_info_broken">Error</string> + <string name="pref_presence_settings">Opciones de presencia</string> + <string name="pref_away_when_screen_off">Ausente con pantalla apagada</string> + <string name="pref_away_when_screen_off_summary">Cambia tu estado a ausente cuando la pantalla está apagada</string> + <string name="pref_xa_on_silent_mode">No disponible en modo silencio</string> + <string name="pref_xa_on_silent_mode_summary">Cambiar el recurso a no disponible cuando el dispositivo esté en modo silencio</string> + <string name="action_add_account_with_certificate">Añadir cuenta con certificado</string> + <string name="unable_to_parse_certificate">No se ha podido leer el certificado</string> + <string name="authenticate_with_certificate">Dejar vacío para autenticar certificado w/ </string> + <string name="captcha_ocr">Texto captcha</string> + <string name="captcha_required">Captcha requerido</string> + <string name="captcha_hint">Introduce el texto de la imagen</string> + <string name="certificate_chain_is_not_trusted">La cadena de certificados no es de confianza</string> + <string name="jid_does_not_match_certificate">El identificador Jabber no coincide con el del certificado</string> + <string name="action_renew_certificate">Renovar certificado</string> + <string name="error_fetching_omemo_key">¡Error buscando clave OMEMO!</string> + <string name="verified_omemo_key_with_certificate">¡Clave OMEMO con certificado verificada!</string> + <string name="device_does_not_support_certificates">¡Tu dispositivo no soporta la elección de certificados de cliente!</string> + <string name="pref_connection_options">Opciones de conexión</string> + <string name="pref_use_tor">Conectar via Tor</string> + <string name="pref_use_tor_summary">Todas las conexiones se realizan a través de la red TOR. Requiere Orbot</string> + <string name="account_settings_hostname">Hostname</string> + <string name="account_settings_port">Puerto</string> + <string name="hostname_or_onion">Server- or .onion-Address</string> + <string name="not_a_valid_port">Éste no es un número de puerto válido</string> + <string name="not_valid_hostname">Éste no es un hostame válido</string> + <string name="connected_accounts">%1$d de %2$d cuentas conectadas</string> + <plurals name="x_messages"> + <item quantity="one">%d mensaje</item> + <item quantity="other">%d mensajes</item> + </plurals> + <string name="shared_file_with_x">Archivo compartido con %s</string> + <string name="shared_image_with_x">Imagen compartida con %s</string> + <string name="no_storage_permission">Conversations necesita acceder al almacenamiento externo</string> + <string name="sync_with_contacts">Sincronizar contactos</string> + <string name="sync_with_contacts_long">Conversations quiere cruzar tu lista de contactos de XMPP con tus contactos del móvil para mostrar sus nombres completos y sus fotos de perfil.\n\nConversations solo leerá tus contactos y los cruzará localmente sin subirlos a tu servidor.\n\nEl sistema te preguntará ahora para conceder los permisos de acceso a tus contactos del móvil.</string> + <string name="certificate_information">Información de certificado</string> + <string name="certificate_subject">Asunto</string> + <string name="certificate_issuer">Editor</string> + <string name="certificate_cn">Nombre</string> + <string name="certificate_o">Organización</string> + <string name="certificate_sha1">SHA1</string> + <string name="certicate_info_not_available">(No disponible)</string> + <string name="certificate_not_found">Certificado no encontrado</string> + <string name="notify_on_all_messages">Notificar para todos los mensajes</string> + <string name="notify_only_when_highlighted">Notificar solo cuando se recibe un mensjae resaltado</string> + <string name="notify_never">Notificaciones deshabilitadas</string> + <string name="notify_paused">Notificaciones pausadas</string> + <string name="pref_picture_compression">Comprimir imagenes</string> + <string name="pref_picture_compression_summary">Redimiensionar y comprimir imágenes</string> + <string name="always">Siempre</string> + <string name="automatically">Automáticamente</string> + <string name="battery_optimizations_enabled">Optimizaciones de uso de batería habilitadas</string> + <string name="battery_optimizations_enabled_explained">Tu dispositivo está realizando optimizaciones de uso de batería en Conversations que pueden hacer que los mensajes se retrasen o incluso hacer que se pierdan.\nEs recomendable deshabilitarlas.</string> + <string name="battery_optimizations_enabled_dialog">Tu dispositivo está realizando optimizaciones de uso de batería en Conversations que pueden hacer que los mensajes se retrasen o incluso hacer que se pierdan.\n\nEl sistema te preguntará ahora para deshabilitarlas.</string> + <string name="disable">Deshabilitar</string> + <string name="selection_too_large">El área seleccionada es demasiado grande</string> </resources> diff --git a/src/main/res/values-eu/strings.xml b/src/main/res/values-eu/strings.xml index ffc276cc..d51b6f83 100644 --- a/src/main/res/values-eu/strings.xml +++ b/src/main/res/values-eu/strings.xml @@ -9,7 +9,7 @@ <string name="action_secure">Elkarrizketa segurua</string> <string name="action_add_account">Kontua gehitu</string> <string name="action_edit_contact">Izena editatu</string> - <string name="action_add_phone_book">Telefono kontaktuetara gehitu</string> + <string name="action_add_phone_book">Helbideen liburura gehitu</string> <string name="action_delete_contact">Zerrendatik ezabatu</string> <string name="action_block_contact">Kontaktua blokeatu</string> <string name="action_unblock_contact">Kontaktua desblokeatu</string> @@ -28,7 +28,8 @@ <string name="minutes_ago">%d min lehenago</string> <string name="unread_conversations">irakurri gabeko elkarrizketak</string> <string name="sending">bidaltzen…</string> - <string name="encrypted_message">Mezua desenkriptatzen. Mesedez itxaron…</string> + <string name="message_decrypting">Mezua desenkriptatzen. Mesedez itxaron…</string> + <string name="pgp_message">OpenPGPz enkriptatutako mezua</string> <string name="nick_in_use">Ezizena erabilita dagoeneko</string> <string name="admin">Administratzailea</string> <string name="owner">Jabea</string> @@ -76,8 +77,10 @@ <string name="delete_messages">Mezuak ezabatu</string> <string name="also_end_conversation">Elkarrizketa hau jarraian amaitu</string> <string name="choose_presence">Hautatu agerpena kontaktuarentzat</string> - <string name="send_plain_text_message">Testu mezua bidali</string> + <string name="send_unencrypted_message">Enkriptatu gabeko mezua bidali</string> <string name="send_otr_message">OTRz enkriptatutako mezua bidali</string> + <string name="send_omemo_message">OMEMOz enkriptatutako mezua bidali</string> + <string name="send_omemo_x509_message">v\\OMEMOz enkriptatutako mezua bidali</string> <string name="send_pgp_message">OpenPGPz enkriptatutako mezua bidali</string> <string name="your_nick_has_been_changed">Zure ezizena aldatu da</string> <string name="send_unencrypted">Enkriptatu gabe bidali</string> @@ -86,18 +89,19 @@ <string name="openkeychain_required_long">Conversationsek <b>OpenKeychain</b> izeneko hirugarren app bat erabiltzen du mezuak enkriptatu eta desenkriptatzeko eta zure gako publikoak kudeatzeko.\n\nOpenKeychain GPLv3 lizentziapean dago eta F-Droid eta Google Playn eskura daiteke.\n\n<small>(Mesedez ondoren Conversations berrabiarazi)</small></string> <string name="restart">Berrabiarazi</string> <string name="install">Instalatu</string> + <string name="openkeychain_not_installed">Mesedez instalatu ezazu OpenKeychain</string> <string name="offering">eskeintzen…</string> <string name="waiting">itxaroten…</string> <string name="no_pgp_key">Ez da OpenPGP gakorik aurkitu</string> <string name="contact_has_no_pgp_key">Conversations ez da zure mezuak enkriptatzeko gai zure kontaktua bere gako publikoa jakinarazten ez dagoelako.\n\n<small>Mesedez eskatu ezaiozu zure kontaktuari openPGP konfigura dezan.</small></string> <string name="no_pgp_keys">Ez da OpenPGP gakorik aurkitu</string> <string name="contacts_have_no_pgp_keys">Conversations ez da zure mezuak enkriptatzeko gai zure kontaktuak haien gako publikoa jakinarazten ez daudelako.\n\n<small>Mesedez eskatu ezaiezu zure kontakuei OpenPGP konfigura dezaten.</small></string> - <string name="encrypted_message_received"><i>Enkriptatutako mezua jaso da. Ukitu ikusi eta desenkriptatzeko.</i></string> + <string name="encrypted_message_received"><i>Enkriptatutako mezua jaso da. Ukitu desenkriptatzeko.</i></string> <string name="pref_general">Orokorrak</string> <string name="pref_xmpp_resource">XMPP baliabidea</string> <string name="pref_xmpp_resource_summary">Bezero honek bere burua aurkezteko erabiltzen duen izena</string> <string name="pref_accept_files">Fitxategiak onartu</string> - <string name="pref_accept_files_size_summary">Hurrengo tamaina baino fitxategi txikiagoak automatikoki onartu…</string> + <string name="pref_accept_files_summary">Hurrengo tamaina baino fitxategi txikiagoak automatikoki onartu…</string> <string name="pref_notification_settings">Jakinarazpenen ezarpenak</string> <string name="pref_notifications">Jakinarazpenak</string> <string name="pref_notifications_summary">Mezu berri bat heltzerakoan jakinarazi</string> @@ -105,8 +109,6 @@ <string name="pref_vibrate_summary">Dardaratu ere mezu berri bat heltzerakoan</string> <string name="pref_sound">Soinua</string> <string name="pref_sound_summary">Dei-tonua jo jakinarazpenarekin</string> - <string name="pref_conference_notifications">Konferentzien jakinarazpenak</string> - <string name="pref_conference_notifications_summary">Beti jakinarazi konferentzia mezu berri bat heltzerakoan eta ez soilik nabarmentzerakoan</string> <string name="pref_notification_grace_period">Jakinarazpenen grazia epea</string> <string name="pref_notification_grace_period_summary">Jakinarazpenak denbora labur baterako ezgaitu ikatz-kopia bat jaso ondoren</string> <string name="pref_advanced_options">Aukera aurreratuak</string> @@ -149,9 +151,10 @@ <string name="account_status_regis_not_sup">Zerbitzariak ez du erregistratzea onartzen</string> <string name="account_status_security_error">Segurtasun akatsa</string> <string name="account_status_incompatible_server">Zerbitzari ez bateragarria</string> - <string name="encryption_choice_none">Testu laua</string> + <string name="encryption_choice_unencrypted">Enkriptatu gabe</string> <string name="encryption_choice_otr">OTR</string> <string name="encryption_choice_pgp">OpenPGP</string> + <string name="encryption_choice_omemo">OMEMO</string> <string name="mgmt_account_edit">Kontua editatu</string> <string name="mgmt_account_delete">Kontua ezabatu</string> <string name="mgmt_account_disable">Aldi baterako ezgaitu</string> @@ -170,7 +173,7 @@ <string name="passwords_do_not_match">Pasahitzak ez dute bat egiten</string> <string name="invalid_jid">Hau ez da Jabber ID baliodun bat</string> <string name="error_out_of_memory">Memoriarik gabe. Irudia handiegia da</string> - <string name="add_phone_book_text">%s zure telefono kontaktu zerrendara gehitu nahi al duzu?</string> + <string name="add_phone_book_text">%s zure helbideen liburura gehitu nahi duzu?</string> <string name="contact_status_online">konektatuta</string> <string name="contact_status_free_to_chat">hitzegiteko aske</string> <string name="contact_status_away">kanpoan</string> @@ -186,7 +189,8 @@ <string name="server_info_blocking">XEP-0191: Blocking Command</string> <string name="server_info_roster_version">XEP-0237: Roster Versioning</string> <string name="server_info_stream_management">XEP-0198: Stream Management</string> - <string name="server_info_pep">XEP-0163: PEP (Profileko argazkiak)</string> + <string name="server_info_pep">XEP-0163: PEP (Avatars / OMEMO)</string> + <string name="server_info_http_upload">XEP-0363: HTTP File Upload</string> <string name="server_info_available">eskuragarri</string> <string name="server_info_unavailable">ez eskuragarri</string> <string name="missing_public_keys">Gako publikoen iragarpenak faltan</string> @@ -204,17 +208,28 @@ <string name="reception_failed">Jasotzeak huts egin du</string> <string name="your_fingerprint">Zure hatz-marka</string> <string name="otr_fingerprint">OTR hatz-marka</string> + <string name="omemo_fingerprint">OMEMO hatz-marka</string> + <string name="omemo_fingerprint_x509">v\\OMEMO hatz-marka</string> + <string name="omemo_fingerprint_selected_message">Mezuaren OMEMO hatz-marka</string> + <string name="omemo_fingerprint_x509_selected_message">Mezuaren v\\OMEMO hatz-marka</string> + <string name="this_device_omemo_fingerprint">Norberaren OMEMO hatz-marka</string> + <string name="other_devices">Beste gailuak</string> + <string name="trust_omemo_fingerprints">OMEMO hatz-marketaz fidatu</string> + <string name="fetching_keys">Gakoak eskuratzen...</string> + <string name="done">Eginda</string> <string name="verify">Egiaztatu</string> <string name="decrypt">Desenkriptatu</string> <string name="conferences">Konferentziak</string> <string name="search">Bilatu</string> <string name="create_contact">Kontaktua sortu</string> + <string name="enter_contact">Kontaktua sartu</string> <string name="join_conference">Konferentziara batu</string> <string name="delete_contact">Kontaktua ezabatu</string> <string name="view_contact_details">Kontaktuaren xehetasunak ikusi</string> <string name="block_contact">Kontaktua blokeatu</string> <string name="unblock_contact">Kontaktua desblokeatu</string> <string name="create">Sortu</string> + <string name="select">Hautatu</string> <string name="contact_already_exists">Kontaktua existitzen da dagoeneko</string> <string name="join">Batu</string> <string name="conference_address">Konferentziaren helbidea</string> @@ -249,7 +264,6 @@ <string name="skip">Orain ez</string> <string name="disable_notifications">Jakinarazpenak ezgaitu</string> <string name="disable_notifications_for_this_conversation">Elkarrizketa honetarako jakinarazpenak ezgaitu</string> - <string name="notifications_disabled">Jakinarazpenak ezgaituta daude</string> <string name="enable">Gaitu</string> <string name="conference_requires_password">Konferentziak pasahitza behar du</string> <string name="enter_password">Sartu pasahitza</string> @@ -284,6 +298,7 @@ <string name="pref_conference_name">Konferentziaren izena</string> <string name="pref_conference_name_summary">Erabili gelaren gaia konferentziak identifikatzeko eta ez JIDa</string> <string name="toast_message_otr_fingerprint">OTR hatz-marka arbelara kopiatu da</string> + <string name="toast_message_omemo_fingerprint">OMEMO hatz-marka arbelara kopiatu da</string> <string name="conference_banned">Konferentzia honetara sartzea debekatuta duzu</string> <string name="conference_members_only">Konferentzia hau kideentzat da soilik</string> <string name="conference_kicked">Konferentzia honetatik kanporatua izan zara</string> @@ -307,7 +322,6 @@ <string name="verify_otr">OTR egiaztatu</string> <string name="remote_fingerprint">Urruneko hatz-marka</string> <string name="scan">eskaneatu</string> - <string name="or_touch_phones">(edo telefonoak ikutu)</string> <string name="smp">Socialist Millionaire protokoloa</string> <string name="shared_secret_hint">Iradokizuna edo galdera</string> <string name="shared_secret_secret">Partekatutako sekretua</string> @@ -324,6 +338,9 @@ <string name="conversations_foreground_service">Conversations</string> <string name="pref_keep_foreground_service">Zerbitzua atzeko planoan mantendu</string> <string name="pref_keep_foreground_service_summary">Sistema eragileak zure konexioa hiltzea galarazten du</string> + <string name="pref_export_logs">Erregistroak esportatu</string> + <string name="pref_export_logs_summary">Erregistroak SD txartelean gorde</string> + <string name="notification_export_logs_title">Erregistroak SD txartelean gordetzen</string> <string name="choose_file">Fitxategia aukeratu</string> <string name="receiving_x_file">%1$s jasotzen (%2$d%% osatua)</string> <string name="download_x_file">%s deskargatu</string> @@ -350,6 +367,17 @@ <string name="reset">Berrezarri</string> <string name="account_image_description">Kontuaren profileko argazkia</string> <string name="copy_otr_clipboard_description">OTR hatz-marka arbelera kopiatu</string> + <string name="copy_omemo_clipboard_description">OMEMO hatz-marka arbelara kopiatu</string> + <string name="regenerate_omemo_key">OMEMO gakoa birsortu</string> + <string name="wipe_omemo_pep">Beste gailuak PEPetik garbitu</string> + <string name="clear_other_devices">Gailuak garbitu</string> + <string name="clear_other_devices_desc">Ziur al zaude OMEMO iragarpenetik beste gailu guztiak garbitu nahi dituzulaz? Zure gailuak konektatzen diren hurrengoan, beraiek berriragarriko dira, baina agian ez dute mezurik jasoko bitartean.</string> + <string name="purge_key">Gakoa purgatu</string> + <string name="purge_key_desc_part1">Ziur al zaude gako hau purgatu nahi duzulaz?</string> + <string name="purge_key_desc_part2">Modu itzulezinean arriskutsutzat hartuko da, eta ezingo duzu gakoarekin berriro saio berri bat sortu.</string> + <string name="error_no_keys_to_trust_server_error">Ez dago gako erabilgarririk kontaktu honetarako.\nEzin izan da zerbitzaritik gako berririk eskuratu. Agian akatsen bat dago zure kontaktuen zerbitzariarekin.</string> + <string name="error_no_keys_to_trust">Kontaktu honetarako gako erabilgarririk ez dago. Bere gakoak purgatu badituzu, berri batzuk sortu behar dituzte.</string> + <string name="error_trustkeys_title">Akatsa</string> <string name="fetching_history_from_server">Mezuak zerbitzaritik eskuratzen</string> <string name="no_more_history_on_server">Mezu gehiagorik ez zerbitzarian</string> <string name="updating">Eguneratzen...</string> @@ -387,8 +415,10 @@ <string name="public_conference">Publikoki edonor sar daiteken konferentzia</string> <string name="private_conference">Konferentzia pribatua, kideentzat soilik</string> <string name="conference_options">Konferentziaren aukerak</string> - <string name="members_only">Pribatua (kideak soilik)</string> + <string name="members_only">Pribatua, kideentzat soilik</string> <string name="non_anonymous">Ez anonimoa</string> + <string name="moderated">Moderatua</string> + <string name="you_are_not_participating">Ez zara parte hartzen ari</string> <string name="modified_conference_options">Konferentziaren aukerak aldatu dira</string> <string name="could_not_modify_conference_options">Konferentziaren aukerak ezin izan dira aldatu</string> <string name="never">Inoiz</string> @@ -427,7 +457,6 @@ <string name="received_location">Kokapena jaso da</string> <string name="title_undo_swipe_out_conversation">Elkarrizketa itxi egin da</string> <string name="title_undo_swipe_out_muc">Konferentzia utzi egin da</string> - <string name="pref_certificate_options">Ziurtagirien aukerak</string> <string name="pref_dont_trust_system_cas_title">Sistemaren CAtaz ez fidatu</string> <string name="pref_dont_trust_system_cas_summary">Ziurtagiri guztiak eskuz onartu behar dira</string> <string name="pref_remove_trusted_certificates_title">Ziurtagiriak kendu</string> @@ -449,6 +478,72 @@ <string name="none">Bat ere ez</string> <string name="recently_used">Azkenengo aldiz erabilitakoa</string> <string name="choose_quick_action">Ekintza azkarra aukeratu</string> - <string name="file_not_found_on_remote_host">Fitxategia ez da aurkitu urruneko zerbitzarian</string> <string name="search_for_contacts_or_groups">Kontaktuak edo taldeak bilatu</string> + <string name="send_private_message">Mezu pribatua bidali</string> + <string name="user_has_left_conference">%s(e)k konferentzia utzi egin du</string> + <string name="username">Erabiltzaile izena</string> + <string name="username_hint">Erabiltzaile izena</string> + <string name="invalid_username">Hau ez da erabiltzaile izen baliodun bat</string> + <string name="download_failed_server_not_found">Deskargak huts egin du: zerbitzaria ez da aurkitu</string> + <string name="download_failed_file_not_found">Deskargak huts egin du: fitxategia ez da aurkitu</string> + <string name="download_failed_could_not_connect">Deskargak huts egin du: ezin izan da ostalarira konektatu</string> + <string name="pref_use_white_background">Atzeko-planoan kolore zuria erabili</string> + <string name="pref_use_white_background_summary">Jasotako mezuak testu beltza atzeko-plano zuri baten gainean bezala erakutsi</string> + <string name="account_status_tor_unavailable">Tor sarea ez dago eskuragarri</string> + <string name="server_info_broken">Hondatuta</string> + <string name="pref_presence_settings">Presentzia ezarpenak</string> + <string name="pref_away_when_screen_off">Urrun pantaila itzalita dagoenean</string> + <string name="pref_away_when_screen_off_summary">Zure baliabidea urrun bezala markatzen du pantaila itzalita dagoenean</string> + <string name="pref_xa_on_silent_mode">Ez eskuragarri modu isilean</string> + <string name="pref_xa_on_silent_mode_summary">Zure baliabidea ez eskuragarri bezala markatzen du gailua modu isilean dagoenean</string> + <string name="action_add_account_with_certificate">Kontua ziurtagiriarekin gehitu</string> + <string name="unable_to_parse_certificate">Ezin izan da ziurtagiria aztertu</string> + <string name="authenticate_with_certificate">Utzi hutsik ziurtagiririk gabe autentifikatzeko</string> + <string name="captcha_ocr">Captcharen testua</string> + <string name="captcha_required">Captcha beharrezkoa da</string> + <string name="captcha_hint">Sartu irudiaren testua</string> + <string name="certificate_chain_is_not_trusted">Ziurtagiriaren katea ez da fidagarria</string> + <string name="jid_does_not_match_certificate">Jabber IDa ez du ziurtagiriarekin bat egiten</string> + <string name="action_renew_certificate">Ziurtagiria berriztu</string> + <string name="error_fetching_omemo_key">Akatsa OMEMO gakoa eskuratzerakoan!</string> + <string name="verified_omemo_key_with_certificate">OMEMO gakoa ziurtagiriarekin egiaztatuta!</string> + <string name="device_does_not_support_certificates">Zure gailuak ez du bezero ziurtagiriak aukeratzea onartzen!</string> + <string name="pref_connection_options">Konexioaren aukerak</string> + <string name="pref_use_tor">Tor bidez konektatu</string> + <string name="pref_use_tor_summary">Konexio guztiak Tor sarean zehar igaro. Orbot behar du</string> + <string name="account_settings_hostname">Ostalariaren izena</string> + <string name="account_settings_port">Ataka</string> + <string name="hostname_or_onion">Zerbitzari- edo .onion-helbidea</string> + <string name="not_a_valid_port">Hau ez da ataka zenbaki balioduna</string> + <string name="not_valid_hostname">Hau ez da ostalari izen balioduna</string> + <string name="connected_accounts">%2$dtik %1$d kontu konektatuta</string> + <plurals name="x_messages"> + <item quantity="one">mezu %d</item> + <item quantity="other">%d mezu</item> + </plurals> + <string name="shared_file_with_x">Fitxategia %s(r)ekin partekatu da</string> + <string name="shared_image_with_x">Irudia %s(r)ekin partekatu da</string> + <string name="no_storage_permission">Conversationsek kanpoko biltegirako sarbidea behar du</string> + <string name="sync_with_contacts">Kontaktuekin sinkronizatu</string> + <string name="sync_with_contacts_long">Conversationsek zure XMPP zerrenda eta zure kontaktuak uztartu nahi ditu haien izenak eta argazkiak erakusteko.\n\nConversationsek zure kontaktuak modu lokalean soilik irakurri eta uztartuko ditu, zure zerbitzarira kargatu gabe.\n\nJarraian baimenak eskatuko zaizkizu zure kontaktuetara sartu ahal izateko.</string> + <string name="certificate_information">Ziurtagiriaren informazioa</string> + <string name="certificate_subject">Subjektua</string> + <string name="certificate_issuer">Igorlea</string> + <string name="certificate_cn">Izen arrunta</string> + <string name="certificate_o">Erakundea</string> + <string name="certificate_sha1">SHA1</string> + <string name="certicate_info_not_available">(Ez eskuragarri)</string> + <string name="certificate_not_found">Ez da ziurtagiririk aurkitu</string> + <string name="notify_on_all_messages">Mezu guztiak jakinarazi</string> + <string name="notify_only_when_highlighted">Jakinarazi nabarmentzerakoan soilik</string> + <string name="notify_never">Jakinarazpenak ezgaituta</string> + <string name="notify_paused">Jakinarazpenak gelditu dira</string> + <string name="pref_picture_compression">Irudiak konprimitu</string> + <string name="pref_picture_compression_summary">Irudiak konprimitu eta neurria aldatu</string> + <string name="always">Beti</string> + <string name="automatically">Automatikoki</string> + <string name="battery_optimizations_enabled">Bateriaren optimizazioak gaituta</string> + <string name="battery_optimizations_enabled_explained">Zure gailua jakinarazpen atzeratuak edota mezuen galera ekar lezaketen bateriaren optimizazio handiak egiten ari da Conversationsen.\nHoriek ezgaitzea gomendatzen da.</string> + <string name="battery_optimizations_enabled_dialog">Zure gailua jakinarazpen atzeratuak edota mezuen galera ekar lezaketen bateriaren optimizazio handiak egiten ari da Conversationsen.\nJarraian hauek ezgaitzea eskatuko zaizu.</string> + <string name="disable">Ezgaitu</string> </resources> diff --git a/libs/openpgp-api-lib/res/values-et/strings.xml b/src/main/res/values-fa-rIR/strings.xml index c757504a..c757504a 100644 --- a/libs/openpgp-api-lib/res/values-et/strings.xml +++ b/src/main/res/values-fa-rIR/strings.xml diff --git a/src/main/res/values-fr/strings.xml b/src/main/res/values-fr/strings.xml index 0a42b56a..0332de89 100644 --- a/src/main/res/values-fr/strings.xml +++ b/src/main/res/values-fr/strings.xml @@ -9,7 +9,7 @@ <string name="action_secure">Conversation sécurisée</string> <string name="action_add_account">Ajouter un compte</string> <string name="action_edit_contact">Modifier le nom</string> - <string name="action_add_phone_book">Ajouter aux contacts</string> + <string name="action_add_phone_book">Ajouter au carnet d\'adresses</string> <string name="action_delete_contact">Retirer des contacts</string> <string name="action_block_contact">Bloquer le contact</string> <string name="action_unblock_contact">Débloquer le contact</string> @@ -20,28 +20,29 @@ <string name="title_activity_conference_details">Détails de la conférence</string> <string name="title_activity_contact_details">Détails du contact</string> <string name="title_activity_sharewith">Partager avec Conversation</string> - <string name="title_activity_start_conversation">Lancement de Conversation</string> + <string name="title_activity_start_conversation">Démarrer une conversation</string> <string name="title_activity_choose_contact">Choix du contact</string> <string name="title_activity_block_list">Bloquer la liste</string> <string name="just_now">À l\'instant</string> <string name="minute_ago">Il y a 1 minute</string> <string name="minutes_ago">Il y a %d minutes</string> <string name="unread_conversations">Conversations non lues</string> - <string name="sending">envoi…</string> - <string name="encrypted_message">Déchiffrement du message. Patientez…</string> + <string name="sending">Envoi…</string> + <string name="message_decrypting">Déchiffrement du message. Veuillez patienter...</string> + <string name="pgp_message">Message chiffré avec OpenPGP</string> <string name="nick_in_use">Cet identifiant est déjà utilisé.</string> <string name="admin">Administrateur</string> <string name="owner">Propriétaire</string> <string name="moderator">Modérateur</string> <string name="participant">Participant</string> <string name="visitor">Visiteur</string> - <string name="remove_contact_text">Voulez-vous supprimer %s de votre liste? Les conversations associées à ce compte ne seront pas supprimées.</string> - <string name="block_contact_text">Voulez-vous bloquer %s de vous envoyer des messages?</string> - <string name="unblock_contact_text">Voulez-vous débloquer %s et leur permettre de vous envoyer des messages?</string> - <string name="block_domain_text">Bloquer tous les contacts de %s?</string> - <string name="unblock_domain_text">Débloquer tous les contacts de %s?</string> + <string name="remove_contact_text">Voulez-vous supprimer %s de votre liste ? Les conversations associées à ce contact ne seront pas supprimées.</string> + <string name="block_contact_text">Voulez-vous bloquer %s pour l\'empêcher de vous envoyer des messages ?</string> + <string name="unblock_contact_text">Voulez-vous débloquer %s et lui permettre de vous envoyer des messages?</string> + <string name="block_domain_text">Bloquer tous les contacts de %s ?</string> + <string name="unblock_domain_text">Débloquer tous les contacts de %s ?</string> <string name="contact_blocked">Contact bloqué</string> - <string name="remove_bookmark_text">Voulez-vous retirer %s des favoris? La conversation associée avec ce favoris ne sera pas supprimé.</string> + <string name="remove_bookmark_text">Voulez-vous retirer %s des favoris? La conversation associée à ce favori ne sera pas supprimée.</string> <string name="register_account">Créer un nouveau compte sur le serveur</string> <string name="change_password_on_server">Changer de mot de passe sur le serveur</string> <string name="share_with">Partager avec…</string> @@ -57,120 +58,122 @@ <string name="unblock">Débloquer</string> <string name="save">Enregistrer</string> <string name="ok">OK</string> - <string name="crash_report_title">Conversations s\'est arreté</string> - <string name="crash_report_message">En envoyant des logs vous aidez au développement de Conversations.\n\n<b>Attention:</b> Votre compte XMPP sera utilisé pour envoyer les logs aux développeurs.</string> + <string name="crash_report_title">Conversations a planté</string> + <string name="crash_report_message">En envoyant des logs, vous aidez le développement de Conversations.\n\n<b>Attention :</b> Votre compte XMPP sera utilisé pour envoyer les logs aux développeurs.</string> <string name="send_now">Envoyer</string> <string name="send_never">Ne plus me demander</string> <string name="problem_connecting_to_account">Impossible de se connecter au compte.</string> <string name="problem_connecting_to_accounts">Impossible de se connecter aux comptes.</string> <string name="touch_to_fix">Appuyez pour gérer vos comptes.</string> - <string name="attach_file">Lier un fichier</string> - <string name="not_in_roster">Le contact n\'est pas dans votre carnet d\'adresses. Voulez-vous l\'y ajouter?</string> + <string name="attach_file">Joindre un fichier</string> + <string name="not_in_roster">Le contact n\'est pas dans votre carnet d\'adresses. Voulez-vous l\'y ajouter ?</string> <string name="add_contact">Ajouter un contact</string> - <string name="send_failed">Echec de l\'envoi.</string> + <string name="send_failed">Échec de l\'envoi.</string> <string name="send_rejected">Rejeté</string> - <string name="preparing_image">Préparation de la transmission de l\'image. Patientez…</string> + <string name="preparing_image">Préparation de l\'image pour envoi...</string> <string name="action_clear_history">Vider l\'historique</string> <string name="clear_conversation_history">Vider l\'historique de la conversation</string> - <string name="clear_histor_msg">Voulez-vous supprimer tous les messages de cette conversation?\n\n<b>Attention:</b> Les messages seront supprimés uniquement sur ce périphérique.</string> + <string name="clear_histor_msg">Voulez-vous supprimer tous les messages de cette conversation ?\n\n<b>Attention :</b> Les messages seront supprimés uniquement sur cet appareil.</string> <string name="delete_messages">Supprimer les messages</string> - <string name="also_end_conversation">Terminer plus tard cette conversation</string> + <string name="also_end_conversation">Fermer cette conversation ensuite</string> <string name="choose_presence">Choisir le status de présence</string> - <string name="send_plain_text_message">Envoyer un message</string> - <string name="send_otr_message">Envoyer un message sécurisé par OTR</string> - <string name="send_pgp_message">Envoyer un message sécurisé par OpenPGP</string> + <string name="send_unencrypted_message">Envoyer un message non chiffré</string> + <string name="send_otr_message">Envoyer un message chiffré avec OTR</string> + <string name="send_omemo_message">Envoyé un message chiffré avec OMEMO</string> + <string name="send_omemo_x509_message">Envoyé un message chiffré avec \\OMEMO</string> + <string name="send_pgp_message">Envoyer un message chiffré avec OpenPGP</string> <string name="your_nick_has_been_changed">Votre identifiant a été changé</string> <string name="send_unencrypted">Envoyer en clair</string> - <string name="decryption_failed">Echec du déchiffrement. Merci de vérifier la clef privée utilisée.</string> + <string name="decryption_failed">Echec du déchiffrement. Avez-vous la bonne clef privée ?</string> <string name="openkeychain_required">OpenKeychain</string> - <string name="openkeychain_required_long">Conversations requiert une application tierce nommée <b>OpenKeychain</b> pour chiffrer et déchiffrer les messages.\n\nOpenKeychain est sous licence GPLv3 et est disponible sur F-Droid et Google Play.\n\n<small>(Merci de redémarrer Conversations apres l\'installation du logiciel)</small></string> + <string name="openkeychain_required_long">Conversations requiert une application tierce nommée <b>OpenKeychain</b> pour chiffrer et déchiffrer les messages.\n\nOpenKeychain est sous licence GPLv3 et est disponible sur F-Droid et Google Play.\n\n<small>(Veuillez redémarrer Conversations apres l\'installation de l\'app)</small></string> <string name="restart">Redémarrer</string> <string name="install">Installer</string> + <string name="openkeychain_not_installed">Veuillez installer OpenKeychain</string> <string name="offering">Proposition…</string> <string name="waiting">Patientez…</string> <string name="no_pgp_key">Aucune clef OpenPGP trouvée.</string> - <string name="contact_has_no_pgp_key">Conversations ne peut chiffrer vos messages car votre correspondant n\'a pas communiqué sa clef publique.\n\n<small>Merci de demander à votre correspondant de configurer OpenPGP.</small></string> - <string name="no_pgp_keys">Aucune clef OpenPGP n\'est disponible.</string> - <string name="contacts_have_no_pgp_keys">Conversations ne peut pas chiffrer votre message car vous ne connaissez pas la clef publique de vos contacts.\n\n<small>Merci de les faire configurer leur OpenPGP.</small></string> - <string name="encrypted_message_received"><i>Message chiffré reçu. Appuyez pour le déchiffrer.</i></string> + <string name="contact_has_no_pgp_key">Conversations ne peut pas chiffrer vos messages car votre correspondant n\'a pas communiqué sa clef publique.\n\n<small>Demandez-lui de configurer OpenPGP.</small></string> + <string name="no_pgp_keys">Aucune clef OpenPGP n\'a été trouvée.</string> + <string name="contacts_have_no_pgp_keys">Conversations ne peut pas chiffrer votre message car vos contacts ne communiquent pas leur clef publique.\n\n<small>Demandez-leur de configurer OpenPGP.</small></string> + <string name="encrypted_message_received"><i>Message chiffré reçu. Appuyez pour déchiffrer.</i></string> <string name="pref_general">Général</string> <string name="pref_xmpp_resource">Ressource XMPP</string> - <string name="pref_xmpp_resource_summary">Nom permettant d\'identifier ce client XMPP</string> + <string name="pref_xmpp_resource_summary">Nom utilisé par ce client pour s\'identifier</string> <string name="pref_accept_files">Accepter les fichiers</string> - <string name="pref_accept_files_size_summary">Accepter automatiquement les fichiers plus petits que…</string> - <string name="pref_notification_settings">Paramètres de notification</string> + <string name="pref_accept_files_summary">Accepter automatiquement les fichiers plus petits que…</string> + <string name="pref_notification_settings">Options de notification</string> <string name="pref_notifications">Notifications</string> - <string name="pref_notifications_summary">Notifier l\'arrivée d\'un message</string> + <string name="pref_notifications_summary">Notifier de l\'arrivée d\'un message.</string> <string name="pref_vibrate">Vibration</string> - <string name="pref_vibrate_summary">Vibrer lors de l\'arrivée d\'un message</string> + <string name="pref_vibrate_summary">Vibrer lors de l\'arrivée d\'un message.</string> <string name="pref_sound">Sonore</string> - <string name="pref_sound_summary">Jouer une sonnerie lors de l\'arrivée d\'un message</string> - <string name="pref_conference_notifications">Notifications lors des conférences</string> - <string name="pref_conference_notifications_summary">Toujours notifier l\'arrivée d\'un message provenant d\'une conférence.</string> + <string name="pref_sound_summary">Jouer une sonnerie pour notifier.</string> <string name="pref_notification_grace_period">Période sans notification</string> <string name="pref_notification_grace_period_summary">Désactiver momentanément les notifications après l\'arrivée d\'une copie carbone.</string> <string name="pref_advanced_options">Options avancées</string> - <string name="pref_never_send_crash">Ne jamais envoyer de rapports d\'erreurs</string> - <string name="pref_never_send_crash_summary">En envoyant des logs vous aidez au développement de Conversations.</string> + <string name="pref_never_send_crash">Ne pas envoyer de rapports d\'erreurs</string> + <string name="pref_never_send_crash_summary">En envoyant des logs vous aidez le développement de Conversations.</string> <string name="pref_confirm_messages">Confirmation de lecture</string> - <string name="pref_confirm_messages_summary">Informer l\'expéditeur d\'un message de sa bonne réception.</string> - <string name="pref_ui_options">Options d\'affichage</string> - <string name="openpgp_error">Une erreur s\'est produite via OpenKeychain</string> + <string name="pref_confirm_messages_summary">Informer le contact lorsque vous avez reçu et lu un message.</string> + <string name="pref_ui_options">Options d\'interface</string> + <string name="openpgp_error">OpenKeychain a signalé une erreur</string> <string name="error_decrypting_file">Erreur d\'E/S lors du déchiffrement du fichier</string> <string name="accept">Accepter</string> <string name="error">Une erreur s\'est produite</string> - <string name="pref_grant_presence_updates">Accepter les mises à jour de présence</string> - <string name="pref_grant_presence_updates_summary">Demander et accepter par avance les mises à jour de présence des contacts créés.</string> + <string name="pref_grant_presence_updates">Autoriser les màj de présence</string> + <string name="pref_grant_presence_updates_summary">Autoriser et demander par avance les mises à jour de présence des contacts ajoutés.</string> <string name="subscriptions">Publications</string> <string name="your_account">Votre compte</string> <string name="keys">Clefs</string> - <string name="send_presence_updates">Envoyer les mises à jour de présence</string> - <string name="receive_presence_updates">Recevoir les mises à jour de présence</string> - <string name="ask_for_presence_updates">Demander les mises à jour de présence</string> + <string name="send_presence_updates">Envoyer mes màj de présence</string> + <string name="receive_presence_updates">Recevoir ses màj de présence</string> + <string name="ask_for_presence_updates">Demander les màj de présence</string> <string name="attach_choose_picture">Choisir une image</string> <string name="attach_take_picture">Prendre une photo</string> <string name="preemptively_grant">Accepter par avance les demandes de publication.</string> <string name="error_not_an_image_file">Le fichier choisi n\'est pas une image</string> <string name="error_compressing_image">Une erreur s\'est produite en convertissant l\'image</string> - <string name="error_file_not_found">Fichier non trouvé</string> - <string name="error_io_exception">Erreur générale d\'E/S. Avez-vous encore de l\'espace libre?</string> - <string name="error_security_exception_during_image_copy">L\'application utilisée empêche la lecture de l\'image.\n\n<small>Choisissez l\'image depuis une autre application.</small></string> + <string name="error_file_not_found">Impossible de trouver le fichier</string> + <string name="error_io_exception">Erreur générale d\'E/S. Avez-vous encore de l\'espace libre ?</string> + <string name="error_security_exception_during_image_copy">L\'application utilisée ne nous donne pas la permission de lire l\'image.\n\n<small>Utilisez une autre application pour choisir une image.</small></string> <string name="account_status_unknown">Inconnu</string> <string name="account_status_disabled">Désactivé temporairement</string> <string name="account_status_online">En ligne</string> <string name="account_status_connecting">Connexion\u2026</string> <string name="account_status_offline">Hors-ligne</string> <string name="account_status_unauthorized">Non autorisé</string> - <string name="account_status_not_found">Serveur non trouvé</string> + <string name="account_status_not_found">Impossible de trouver le serveur</string> <string name="account_status_no_internet">Aucune connectivité</string> - <string name="account_status_regis_fail">Enregistrement échoué</string> + <string name="account_status_regis_fail">Échec de l\'enregistrement</string> <string name="account_status_regis_conflict">Identifiant déjà utilisé</string> <string name="account_status_regis_success">Enregistrement réussi</string> <string name="account_status_regis_not_sup">Le serveur ne permet pas l\'enregistrement</string> <string name="account_status_security_error">Erreur de sécurité</string> <string name="account_status_incompatible_server">Serveur incompatible</string> - <string name="encryption_choice_none">Texte clair</string> + <string name="encryption_choice_unencrypted">Non chiffré</string> <string name="encryption_choice_otr">OTR</string> <string name="encryption_choice_pgp">OpenPGP</string> + <string name="encryption_choice_omemo">OMEMO</string> <string name="mgmt_account_edit">Modifier le compte</string> <string name="mgmt_account_delete">Supprimer</string> <string name="mgmt_account_disable">Désactiver temporairement</string> <string name="mgmt_account_publish_avatar">Publier un avatar</string> <string name="mgmt_account_publish_pgp">Publier la clef publique OpenPGP</string> <string name="mgmt_account_enable">Activer</string> - <string name="mgmt_account_are_you_sure">Êtes-vous sûr?</string> - <string name="mgmt_account_delete_confirm_text">En supprimant votre compte, votre historique de conversations sera perdu!</string> + <string name="mgmt_account_are_you_sure">Êtes-vous sûr ?</string> + <string name="mgmt_account_delete_confirm_text">Si vous supprimez votre compte, votre historique de conversations sera perdu !</string> <string name="attach_record_voice">Enregistrer un son</string> <string name="account_settings_jabber_id">Identifiant</string> <string name="account_settings_password">Mot de passe</string> - <string name="account_settings_example_jabber_id">utilisateur@exemple.com</string> + <string name="account_settings_example_jabber_id">nom@exemple.com</string> <string name="account_settings_confirm_password">Confirmer le mot de passe</string> <string name="password">Mot de passe</string> <string name="confirm_password">Confirmer le mot de passe</string> - <string name="passwords_do_not_match">Les deux mots de passes ne correspondent pas.</string> - <string name="invalid_jid">Ce n\'est pas un identifiant valide.</string> + <string name="passwords_do_not_match">Les deux mots de passe ne correspondent pas.</string> + <string name="invalid_jid">Cet identifiant n\'est pas valide.</string> <string name="error_out_of_memory">Plus de mémoire disponible. L\'image est trop volumineuse.</string> - <string name="add_phone_book_text">Voulez-vous ajouter %s aux contacts du téléphone?</string> + <string name="add_phone_book_text">Voulez-vous ajouter %s à votre carnet d\'adresses ?</string> <string name="contact_status_online">En ligne</string> <string name="contact_status_free_to_chat">Disponible</string> <string name="contact_status_away">Absent</string> @@ -180,16 +183,17 @@ <string name="muc_details_conference">Conférence</string> <string name="muc_details_other_members">Autres membres</string> <string name="server_info_show_more">Infos sur le serveur</string> - <string name="server_info_mam">XEP-0313: MAM</string> - <string name="server_info_carbon_messages">Copies carbone</string> - <string name="server_info_csi">XEP-0352: Client State Indication</string> - <string name="server_info_blocking">XEP-0191: Blocking Command</string> - <string name="server_info_roster_version">XEP-0237: Roster Versioning</string> - <string name="server_info_stream_management">Gestion des flux</string> - <string name="server_info_pep">XEP-0163: PEP (Avatars)</string> - <string name="server_info_available">disponible</string> - <string name="server_info_unavailable">indisponible</string> - <string name="missing_public_keys">Aucune annonce de clef publique</string> + <string name="server_info_mam">XEP-0313 : MAM</string> + <string name="server_info_carbon_messages">XEP-0280 : Copies carbone</string> + <string name="server_info_csi">XEP-0352 : Indication status client</string> + <string name="server_info_blocking">XEP-0191 : Commande de bloquage</string> + <string name="server_info_roster_version">XEP-0237 : Versionnement contacts</string> + <string name="server_info_stream_management">XEP-0198 : Gestion des flux</string> + <string name="server_info_pep">XEP-0163 : PEP (Avatars / OMEMO)</string> + <string name="server_info_http_upload">XEP-0363 : Envoi de fichiers via HTTP</string> + <string name="server_info_available">supporté</string> + <string name="server_info_unavailable">non supporté</string> + <string name="missing_public_keys">Annonce de clef publique manquante</string> <string name="last_seen_now">en ligne à l\'instant</string> <string name="last_seen_min">en ligne il y a 1 minute</string> <string name="last_seen_mins">en ligne il y a %d minutes</string> @@ -198,170 +202,198 @@ <string name="last_seen_day">en ligne hier</string> <string name="last_seen_days">en ligne il y a %d jours</string> <string name="never_seen">jamais vu en ligne</string> - <string name="install_openkeychain">Message chiffré. Merci d\'installer OpenKeychain pour lire le contenu du message.</string> + <string name="install_openkeychain">Message chiffré. Veuillez installer OpenKeychain pour le déchiffrer.</string> <string name="unknown_otr_fingerprint">Empreinte OTR inconnue.</string> - <string name="openpgp_messages_found">Messages chiffrés par OpenPGP détectés.</string> - <string name="reception_failed">Echec lors de la réception</string> + <string name="openpgp_messages_found">Messages chiffrés avec OpenPGP détectés.</string> + <string name="reception_failed">Échec lors de la réception</string> <string name="your_fingerprint">Votre empreinte</string> <string name="otr_fingerprint">Empreinte OTR</string> + <string name="omemo_fingerprint">Empreinte OMEMO</string> + <string name="omemo_fingerprint_x509">v\\Empreinte OMEMO</string> + <string name="omemo_fingerprint_selected_message">Empreinte OMEMO du message</string> + <string name="omemo_fingerprint_x509_selected_message">v\\Empreinte OMEMO du message</string> + <string name="this_device_omemo_fingerprint">Votre empreinte OMEMO</string> + <string name="other_devices">Autres appareils</string> + <string name="trust_omemo_fingerprints">Faire confiance aux empreintes OMEMO</string> + <string name="fetching_keys">Récupération des clefs...</string> + <string name="done">Terminé</string> <string name="verify">Vérifier</string> <string name="decrypt">Déchiffrer</string> <string name="conferences">Conférences</string> <string name="search">Rechercher</string> <string name="create_contact">Ajouter un contact</string> + <string name="enter_contact">Ajouter contact</string> <string name="join_conference">Rejoindre la conférence</string> <string name="delete_contact">Supprimer le contact</string> <string name="view_contact_details">Afficher les détails du contact</string> <string name="block_contact">Bloquer le contact</string> <string name="unblock_contact">Débloquer le contact</string> <string name="create">Ajouter</string> + <string name="select">Sélectionner</string> <string name="contact_already_exists">Le contact existe déjà.</string> <string name="join">Rejoindre</string> <string name="conference_address">Adresse de la conférence</string> <string name="conference_address_example">salle@conference.exemple.com</string> - <string name="save_as_bookmark">Enregistrer en favoris</string> - <string name="delete_bookmark">Supprimer le favoris</string> - <string name="bookmark_already_exists">Ce favoris existe déjà.</string> + <string name="save_as_bookmark">Enregistrer comme favori</string> + <string name="delete_bookmark">Supprimer le favori</string> + <string name="bookmark_already_exists">Le favori existe déjà</string> <string name="you">Vous</string> <string name="action_edit_subject">Modifier le sujet de la conférence</string> - <string name="conference_not_found">Conférence non trouvée</string> + <string name="conference_not_found">Impossible de trouver la conférence</string> <string name="leave">Partir</string> <string name="contact_added_you">Votre correspondant vous a ajouté dans sa liste de contacts</string> - <string name="add_back">Ajouter également</string> - <string name="contact_has_read_up_to_this_point">%s a lu les messages précédents.</string> + <string name="add_back">Ré-ajouter</string> + <string name="contact_has_read_up_to_this_point">%s a tout lu jusqu\'à ici</string> <string name="publish">Publier</string> <string name="touch_to_choose_picture">Toucher l\'avatar pour choisir une image depuis la galerie.</string> - <string name="publish_avatar_explanation">Nota Bene: Les personnes ayant activé les mises jour de présence verront cette image.</string> + <string name="publish_avatar_explanation">Note : toutes les personnes ayant souscrit à vos mises jour de présence verront cette image.</string> <string name="publishing">Mise à jour…</string> - <string name="error_publish_avatar_server_reject">Le serveur a rejeté votre envoi d\'image</string> + <string name="error_publish_avatar_server_reject">Le serveur a rejeté votre publication</string> <string name="error_publish_avatar_converting">Une erreur s\'est produite pendant la conversion de votre image.</string> - <string name="error_saving_avatar">Impossible de stocker l\'image sur le disque</string> - <string name="or_long_press_for_default">(Un appui long réinitialise le paramètre par defaut)</string> - <string name="error_publish_avatar_no_server_support">Votre serveur n\'autorise pas l\'envoi d\'avatars</string> + <string name="error_saving_avatar">Impossible de stocker l\'avatar sur le disque</string> + <string name="or_long_press_for_default">(Un appui long réinitialise le paramètre)</string> + <string name="error_publish_avatar_no_server_support">Votre serveur n\'autorise pas la publication d\'avatars</string> <string name="private_message">chuchoté</string> <string name="private_message_to">pour %s</string> <string name="send_private_message_to">Envoyer un message privé à %s</string> <string name="connect">Se connecter</string> <string name="account_already_exists">Ce compte existe déjà</string> - <string name="next">suivant</string> + <string name="next">Suivant</string> <string name="server_info_session_established">Session établie</string> <string name="additional_information">Informations supplémentaires</string> <string name="skip">Passer</string> <string name="disable_notifications">Désactiver les notifications</string> <string name="disable_notifications_for_this_conversation">Désactiver les notifications pour cette conversation</string> - <string name="notifications_disabled">Notifications are Désactivées</string> <string name="enable">Activer</string> - <string name="conference_requires_password">La conférence necessite un mot de passe</string> + <string name="conference_requires_password">La conférence nécessite un mot de passe</string> <string name="enter_password">Entrer le mot de passe</string> - <string name="missing_presence_updates">Mise à jour de présence non connue</string> - <string name="request_presence_updates">Merci de demander à votre contact de fournir les mises à jour de présence.\n\n<small>Cela permettra de savoir quel matériel utilise votre contact.</small></string> + <string name="missing_presence_updates">Mises à jour de présence manquantes pour ce contact</string> + <string name="request_presence_updates">Veuillez demander à votre contact de partager ses mises à jour de présence.\n\n<small>Elles seront utilisées pour déterminer son client.</small></string> <string name="request_now">Demander maintenant</string> <string name="delete_fingerprint">Supprimer l\'empreinte</string> - <string name="sure_delete_fingerprint">Etes-vous sûr de vouloir supprimer l\'empreinte?</string> + <string name="sure_delete_fingerprint">Etes-vous sûr de vouloir supprimer l\'empreinte ?</string> <string name="ignore">Ignorer</string> - <string name="without_mutual_presence_updates"><b>Attention:</b> Ceci peut poser problème si l\'un des deux correspondants n\'a pas activé les mises à jour de présence.\n\n<small>Go to contact details to verify your presence subscriptions.</small></string> - <string name="pref_encryption_settings">Paramètres de chiffrement</string> + <string name="without_mutual_presence_updates"><b>Attention :</b> peut poser problème si l\'un des deux correspondants n\'a pas activé les mises à jour de présence.\n\n<small>Vérifiez dans les détails du contact que vous y avez bien souscrit.</small></string> + <string name="pref_encryption_settings">Options de chiffrement</string> <string name="pref_force_encryption">Forcer le chiffrement de bout en bout</string> <string name="pref_force_encryption_summary">Toujours envoyer des messages chiffrés (sauf pour les conférences)</string> - <string name="pref_dont_save_encrypted">Ne pas sauvegarder les messages chiffrés</string> - <string name="pref_dont_save_encrypted_summary">Attention: Celà peut mener à une perte de messages</string> - <string name="pref_expert_options">Options avancées</string> - <string name="pref_expert_options_summary">A utiliser avec précautions</string> - <string name="title_activity_about">Sur Conversations</string> - <string name="pref_about_conversations_summary">Informations sur le build et les licenses</string> + <string name="pref_dont_save_encrypted">Messages chiffrés non sauvegardés</string> + <string name="pref_dont_save_encrypted_summary">Attention : peut provoquer la perte de messages.</string> + <string name="pref_expert_options">Paramètres expert</string> + <string name="pref_expert_options_summary">À utiliser avec précaution.</string> + <string name="title_activity_about">À propos</string> + <string name="pref_about_conversations_summary">Informations sur la version et les licenses</string> <string name="title_pref_quiet_hours">Heures tranquilles</string> <string name="title_pref_quiet_hours_start_time">Heure de début</string> <string name="title_pref_quiet_hours_end_time">Heure de fin</string> <string name="title_pref_enable_quiet_hours">Activer les heures tranquilles</string> - <string name="pref_quiet_hours_summary">Les notifications seront rendu muets pendant les heures tranquilles</string> + <string name="pref_quiet_hours_summary">Les notifications seront muettes pendant les heures tranquilles.</string> <string name="pref_use_larger_font">Augmenter la taille du texte</string> - <string name="pref_use_larger_font_summary">Augmenter la taille du texte partout dans l\'application</string> - <string name="pref_use_send_button_to_indicate_status">Le bouton Envoyer permet d\'indiquer le statut</string> - <string name="pref_use_indicate_received">Accusé de reception</string> - <string name="pref_use_indicate_received_summary">Les messages recus seront marqués d\'une coche verte si disponible</string> - <string name="pref_use_send_button_to_indicate_status_summary">Adapter la couleur du bouton Envoyer pour indiquer le statut</string> + <string name="pref_use_larger_font_summary">Augmenter la taille du texte partout dans l\'application.</string> + <string name="pref_use_send_button_to_indicate_status">Statut sur le bouton Envoyer</string> + <string name="pref_use_indicate_received">Accusés de réception</string> + <string name="pref_use_indicate_received_summary">Les messages reçus seront marqués d\'une coche verte (si supporté).</string> + <string name="pref_use_send_button_to_indicate_status_summary">Le bouton Envoyer change de couleur pour indiquer le statut du contact.</string> <string name="pref_expert_options_other">Autres</string> <string name="pref_conference_name">Nom de la conférence </string> - <string name="pref_conference_name_summary">Identifier la conférence par son nom plutot que par son JID</string> - <string name="toast_message_otr_fingerprint">Empreinte OTR copiée dans le presse-papier!</string> - <string name="conference_banned">Vous êtes interdit de cette conférence</string> + <string name="pref_conference_name_summary">Identifier les conférences par leur sujet plutôt que leur JID.</string> + <string name="toast_message_otr_fingerprint">Empreinte OTR copiée dans le presse-papier !</string> + <string name="toast_message_omemo_fingerprint">Empreinte OMEMO copiée dans le presse-papier !</string> + <string name="conference_banned">Vous êtes banni de cette conférence</string> <string name="conference_members_only">Cette conférence est réservée aux membres</string> <string name="conference_kicked">Vous avez été éjecté de cette conférence</string> - <string name="using_account">utiliser le compte %s</string> - <string name="not_connected_try_again">Vous n\'êtes pas connecté. Merci de retenter plus tard.</string> + <string name="using_account">avec le compte %s</string> + <string name="checking_x">Vérification de %s sur l\'hôte HTTP</string> + <string name="not_connected_try_again">Vous n\'êtes pas connecté. Essayez plus tard.</string> + <string name="check_x_filesize">Vérification de la taille de %s</string> <string name="message_options">Options du message</string> <string name="copy_text">Copier le texte</string> <string name="copy_original_url">Copier l\'URL</string> <string name="send_again">Envoyer de nouveau</string> + <string name="file_url">URL du fichier</string> <string name="message_text">Message texte</string> <string name="url_copied_to_clipboard">URL copiée dans le presse-papier</string> <string name="message_copied_to_clipboard">Message copié dans le presse-papier</string> - <string name="image_transmission_failed">Echec lors de l\'envoi de l\'image</string> + <string name="image_transmission_failed">Échec lors de l\'envoi de l\'image</string> <string name="scan_qr_code">Scanner un QR code</string> - <string name="show_qr_code">Afficher le QR code</string> + <string name="show_qr_code">Afficher le code QR</string> <string name="show_block_list">Afficher la liste des contacts bloqués</string> <string name="account_details">Détails du compte</string> <string name="verify_otr">Vérifier l\'OTR</string> <string name="remote_fingerprint">Supprimer l\'empreinte</string> <string name="scan">Scanner</string> - <string name="or_touch_phones">(ou les touches)</string> <string name="smp">Socialist Millionaire Protocol</string> <string name="shared_secret_hint">Indice ou question</string> <string name="shared_secret_secret">Secret partagé</string> <string name="confirm">Confirmer</string> <string name="in_progress">En cours</string> <string name="respond">Répondre</string> - <string name="failed">Echoué</string> + <string name="failed">Échec</string> <string name="secrets_do_not_match">Les secrets ne correspondent pas</string> <string name="try_again">Réessayer</string> <string name="finish">Terminé</string> - <string name="verified">Vérifié!</string> + <string name="verified">Vérifié !</string> <string name="smp_requested">Le contact requiert la vérification du SMP</string> - <string name="no_otr_session_found">Aucune session valide d\'OTR n\'a été trouvée!</string> + <string name="no_otr_session_found">Aucune session OTR valide n\'a été trouvée !</string> <string name="conversations_foreground_service">Conversations</string> - <string name="pref_keep_foreground_service">Garder le service au premier-plan</string> - <string name="pref_keep_foreground_service_summary">Évite que le système ferme votre connexion</string> - <string name="choose_file">Choix d\'un fichier</string> + <string name="pref_keep_foreground_service">Garder le service au 1er plan</string> + <string name="pref_keep_foreground_service_summary">Évite que le système ne ferme votre connexion.</string> + <string name="pref_export_logs">Exporter les historiques</string> + <string name="pref_export_logs_summary">Sauvegarder les historiques sur la carte SD</string> + <string name="notification_export_logs_title">Sauvegarde des historiques sur la carte SD...</string> + <string name="choose_file">Choix du fichier</string> <string name="receiving_x_file">Réception %1$s (%2$d%% complété)</string> - <string name="download_x_file">Télecharger %s</string> + <string name="download_x_file">Télécharger %s</string> <string name="file">fichier</string> <string name="open_x_file">Ouvrir %s</string> <string name="sending_file">envoi (%1$d%% complété)</string> <string name="preparing_file">Préparation du fichier pour l\'envoi</string> - <string name="x_file_offered_for_download">%s disponible pour téléchargement</string> + <string name="x_file_offered_for_download">%s proposé à télécharger</string> <string name="cancel_transmission">Annuler l\'envoi</string> - <string name="file_transmission_failed">Envoi du fichier échoué</string> + <string name="file_transmission_failed">Échec de l\'envoi du fichier</string> <string name="file_deleted">Le fichier a été supprimé</string> <string name="no_application_found_to_open_file">Aucune application disponible pour ouvrir le fichier</string> <string name="could_not_verify_fingerprint">Impossible de vérifier l\'empreinte</string> <string name="manually_verify">Vérifier manuellement</string> - <string name="are_you_sure_verify_fingerprint">Etes-vous sûr de vouloir vérifier l\'empreinte OTR de vos contacts?</string> + <string name="are_you_sure_verify_fingerprint">Êtes-vous sûr de vouloir vérifier l\'empreinte OTR de vos contacts ?</string> <string name="pref_show_dynamic_tags">Afficher les tags dynamiques</string> - <string name="pref_show_dynamic_tags_summary">Afficher les tags en lecture-seule sous les contacts</string> + <string name="pref_show_dynamic_tags_summary">Afficher des tags en lecture seule en dessous des contacts.</string> <string name="enable_notifications">Activer les notifications</string> <string name="conference_with">Créer une conférence avec…</string> <string name="no_conference_server_found">Aucun serveur de conférence disponible</string> - <string name="conference_creation_failed">Echec de la création de la conférence!</string> - <string name="conference_created">Conférence créée!</string> - <string name="secret_accepted">Secret commun accepté!</string> + <string name="conference_creation_failed">Échec de la création de la conférence !</string> + <string name="conference_created">Conférence créée !</string> + <string name="secret_accepted">Secret accepté !</string> <string name="reset">Réinitialiser</string> - <string name="account_image_description">Image du compte</string> + <string name="account_image_description">Avatar du compte</string> <string name="copy_otr_clipboard_description">Copier l\'empreinte OTR dans le presse-papier</string> - <string name="fetching_history_from_server">Récupérer l\'historique depuis le serveur</string> - <string name="no_more_history_on_server">Fin de l\'historique sur le serveur</string> + <string name="copy_omemo_clipboard_description">Copier l\'empreinte OMEMO dans le presse-papier</string> + <string name="regenerate_omemo_key">Régénérer l\'empreinte OMEMO</string> + <string name="wipe_omemo_pep">Effacer les autres appareils de PEP</string> + <string name="clear_other_devices">Supprimer les appareils</string> + <string name="clear_other_devices_desc">Êtes-vous sûr de vouloir supprimer les autres appareils de l\'annonce OMEMO ? Ils s\'annonceront de nouveau à leur prochaine connexion, mais ils peuvent ne pas recevoir les messages envoyés entre temps.</string> + <string name="purge_key">Supprimer la clef</string> + <string name="purge_key_desc_part1">Êtes-vous sûr de vouloir supprimer cette clef ?</string> + <string name="purge_key_desc_part2">Elle sera considérée compromise de manière irréversible, et vous ne pourrez plus générer de session avec.</string> + <string name="error_no_keys_to_trust_server_error">Il n\'y a aucune clef utilisable disponible pour ce contact.\nLa récupération de nouvelles clefs sur le serveur a échoué. Peut-être y a-t-il un problème avec votre serveur de contacts.</string> + <string name="error_no_keys_to_trust">il n\'y a pas de clef disponible pour ce contact. Si vous avez purgé toutes ses clefs, il doit en générer de nouvelles.</string> + <string name="error_trustkeys_title">Erreur</string> + <string name="fetching_history_from_server">Récupération de l\'historique sur le serveur</string> + <string name="no_more_history_on_server">Plus d\'historique sur le serveur</string> <string name="updating">Mise à jour…</string> - <string name="password_changed">Mot de passe modifié!</string> + <string name="password_changed">Mot de passe modifié !</string> <string name="could_not_change_password">Impossible de changer le mot de passe</string> <string name="otr_session_not_started">Envoyez un message pour commencer la conversation chiffrée</string> <string name="ask_question">Poser une question</string> - <string name="smp_explain_answer">Votre contact voudrait vous identifier de manière sûre grâce à un secret commun. Il vous envoie le message ou la question suivante.</string> - <string name="shared_secret_hint_should_not_be_empty">Votre indice ne devrait pas être vide</string> - <string name="shared_secret_can_not_be_empty">Votre secret ne peut être vide</string> - <string name="manual_verification_explanation">Comparez avec soin l\'empreinte de votre contact avec celle indiquée ci-dessous.\nVous pouvez utiliser n\'importe quel moyen de communication sécurisée pour cela, tel que le télephone ou l\'envoi d\'un e-mail chiffré.</string> + <string name="smp_explain_question">Si vous et votre contact avez en commun un secret que personne d\'autre ne connait (comme une blague ou ce que vous avez mangé lors de votre dernière rencontre), vous pouvez utiliser ce secret pour vérifier vos empreintes respectives.\n\nVous donnez un indice ou posez votre question à votre contact, qui répondra en faisant attention à la casse.</string> + <string name="smp_explain_answer">Votre contact voudrait confirmer votre identité grâce à un secret partagé. Il vous a envoyé le message/indice suivant concernant ce secret.</string> + <string name="shared_secret_hint_should_not_be_empty">Votre indice ne doit pas être vide</string> + <string name="shared_secret_can_not_be_empty">Votre secret partagé ne doit pas être vide</string> + <string name="manual_verification_explanation">Comparez avec soin l\'empreinte ci-dessous avec celle de votre contact.\nPour ce faire, vous pouvez utiliser n\'importe quel moyen de communication auquel vous avez confiance : appel téléphonique, e-mail encrypté...</string> <string name="change_password">Changer de mot de passe</string> <string name="current_password">Mot de passe actuel</string> <string name="new_password">Nouveau mot de passe</string> - <string name="password_should_not_be_empty">Le mot de passe ne peut être vide</string> + <string name="password_should_not_be_empty">Le mot de passe ne doit pas être vide</string> <string name="enable_all_accounts">Activer tous les comptes</string> <string name="disable_all_accounts">Désactiver tous les comptes</string> <string name="perform_action_with">Faire une action avec</string> @@ -369,23 +401,25 @@ <string name="no_role">Aucun rôle</string> <string name="outcast">Banni</string> <string name="member">Membre</string> - <string name="advanced_mode">Mode avancé</string> + <string name="advanced_mode">Mode expert</string> <string name="grant_membership">Accorder le statut de membre</string> <string name="remove_membership">Révoquer le statut de membre</string> <string name="grant_admin_privileges">Accorder des privilèges d\'administrateur</string> <string name="remove_admin_privileges">Révoquer des privilèges d\'administrateur</string> <string name="remove_from_room">Supprimer de la conférence</string> <string name="could_not_change_affiliation">Impossible de changer l\'affiliation de %s</string> - <string name="ban_from_conference">Interdire de la conférence</string> - <string name="removing_from_public_conference">Vous essayez de supprimer %s d\'une conférence publique. La seule façon de le faire consiste à l\'interdire définitivement.</string> - <string name="ban_now">Interdire maintenant</string> + <string name="ban_from_conference">Bannir de la conférence</string> + <string name="removing_from_public_conference">Vous essayez d\'éjecter %s d\'une conférence publique. La seule façon de le faire consiste à bannir cet utilisateur définitivement.</string> + <string name="ban_now">Bannir maintenant</string> <string name="could_not_change_role">Impossible de changer le rôle de %s</string> <string name="public_conference">Conférence accessible au public</string> <string name="private_conference">Conférence privée, réservée aux membres</string> <string name="conference_options">Options de la conférence</string> - <string name="members_only">Privée (reservée aux membres)</string> + <string name="members_only">Privé, membres uniquement</string> <string name="non_anonymous">Non anonyme</string> - <string name="modified_conference_options">Options de la conférence modifiée!</string> + <string name="moderated">Modéré</string> + <string name="you_are_not_participating">Vous ne participez pas</string> + <string name="modified_conference_options">Options de la conférence modifiée !</string> <string name="could_not_modify_conference_options">Impossible de modifier les options de la conférence</string> <string name="never">Jamais</string> <string name="thirty_minutes">30 minutes</string> @@ -394,35 +428,123 @@ <string name="eight_hours">8 heures</string> <string name="until_further_notice">Jusqu\'à nouvel ordre</string> <string name="pref_input_options">Options de saisie</string> - <string name="pref_enter_is_send">Entrée permet d\'envoyer</string> - <string name="pref_enter_is_send_summary">Utiliser la touche Entrée pour envoyer un message</string> + <string name="pref_enter_is_send">Touche Entrée pour envoyer</string> + <string name="pref_enter_is_send_summary">Utiliser la touche Entrée pour envoyer un message.</string> <string name="pref_display_enter_key">Afficher la touche Entrée</string> - <string name="pref_display_enter_key_summary">Remplacer le bouton Émoticônes par un bouton Entrée</string> + <string name="pref_display_enter_key_summary">Remplacer le bouton des Émoticônes par un bouton Entrée.</string> <string name="audio">audio</string> - <string name="video">video</string> + <string name="video">vidéo</string> <string name="image">image</string> <string name="pdf_document">document PDF</string> <string name="apk">Application Android</string> <string name="vcard">Contact</string> - <string name="received_x_file">%s reçu</string> - <string name="disable_foreground_service">Cesser de garder le service au premier plan</string> + <string name="received_x_file">%s reçu(e)</string> + <string name="disable_foreground_service">Ne plus garder le service au 1er plan</string> <string name="touch_to_open_conversations">Cliquez pour ouvrir Conversations</string> - <string name="avatar_has_been_published">L\'avatar a été envoyé !</string> - <string name="sending_x_file">Envoi de %s</string> - <string name="offering_x_file">Proposition pour %s</string> - <string name="hide_offline">Cacher hors-ligne</string> + <string name="avatar_has_been_published">L\'avatar a été publié !</string> + <string name="sending_x_file">%s en cours d\'envoi</string> + <string name="offering_x_file">En train de proposer un(e) %s</string> + <string name="hide_offline">Se cacher hors-ligne</string> <string name="disable_account">Désactiver le compte</string> - <string name="contact_is_typing">%s écrit un message...</string> + <string name="contact_is_typing">%s est en train d\'écrire</string> <string name="contact_has_stopped_typing">%s a arrêté d\'écrire</string> <string name="pref_chat_states">Notifications d\'écriture</string> - <string name="pref_chat_states_summary">Permettre à votre contact de savoir que vous écrivez un message</string> + <string name="pref_chat_states_summary">Informer votre contact lorsque vous êtes en train d\'écrire un message.</string> <string name="send_location">Envoyer la position</string> <string name="show_location">Afficher la position</string> <string name="no_application_found_to_display_location">Aucune application trouvée pour afficher la position</string> <string name="location">Position</string> <string name="received_location">Position reçue</string> + <string name="title_undo_swipe_out_conversation">Conversation fermée</string> + <string name="title_undo_swipe_out_muc">Conférence quittée</string> + <string name="pref_dont_trust_system_cas_title">Ne pas utiliser les CAs système</string> + <string name="pref_dont_trust_system_cas_summary">Tous les certificats doivent être approuvés manuellement.</string> + <string name="pref_remove_trusted_certificates_title">Retirer les certificats</string> + <string name="pref_remove_trusted_certificates_summary">Supprimer les certificats approuvés manuellement.</string> + <string name="toast_no_trusted_certs">Aucun certificat approuvé manuellement</string> + <string name="dialog_manage_certs_title">Retirer les certificats</string> + <string name="dialog_manage_certs_positivebutton">Supprimer la sélection</string> + <string name="dialog_manage_certs_negativebutton">Annuler</string> + <plurals name="toast_delete_certificates"> + <item quantity="one">%d certificat supprimé</item> + <item quantity="other">%d certificats supprimés</item> + </plurals> <plurals name="select_contact"> <item quantity="one">%d contact séléctionné</item> <item quantity="other">%d contacts séléctionnés</item> </plurals> + <string name="pref_quick_action_summary">Remplacer le bouton Envoyer par une action rapide.</string> + <string name="pref_quick_action">Action Rapide</string> + <string name="none">Aucune</string> + <string name="recently_used">Dernière utilisée</string> + <string name="choose_quick_action">Sélectionner l\'action rapide</string> + <string name="search_for_contacts_or_groups">Rechercher des contacts ou des groupes</string> + <string name="send_private_message">Envoyer un message privé</string> + <string name="user_has_left_conference">%s a quitté la conférence !</string> + <string name="username">Identifiant</string> + <string name="username_hint">Identifiant</string> + <string name="invalid_username">Ce n\'est pas un identifiant valide</string> + <string name="download_failed_server_not_found">Échec du téléchargement : impossible de trouver le serveur</string> + <string name="download_failed_file_not_found">Échec du téléchargement : impossible de trouver le fichier</string> + <string name="download_failed_could_not_connect">Échec du téléchargement : impossible de se connecter à l\'hôte</string> + <string name="pref_use_white_background">Utiliser un fond blanc</string> + <string name="pref_use_white_background_summary">Afficher les messages reçus en texte noir sur fond blanc.</string> + <string name="account_status_tor_unavailable">Réseau Tor inaccessible</string> + <string name="server_info_broken">Détraqué</string> + <string name="pref_presence_settings">Options de présence</string> + <string name="pref_away_when_screen_off">Absent quand l\'écran est éteint</string> + <string name="pref_away_when_screen_off_summary">Marquer cette ressource comme absente quand l\'écran est éteint.</string> + <string name="pref_xa_on_silent_mode">Indisponible en mode silencieux</string> + <string name="pref_xa_on_silent_mode_summary">Marque cette ressource comme indisponible quand l\'appareil est en mode silencieux</string> + <string name="action_add_account_with_certificate">Ajouter un compte avec un certificat</string> + <string name="unable_to_parse_certificate">Impossible d\'analyser le certificat</string> + <string name="authenticate_with_certificate">Laisser vide pour s\'identifier avec un certificat</string> + <string name="captcha_ocr">Texte du captcha</string> + <string name="captcha_required">Captcha obligatoire</string> + <string name="captcha_hint">Saisissez le texte dans l\'image</string> + <string name="certificate_chain_is_not_trusted">La chaîne de certificats n\'est pas digne de confiance</string> + <string name="jid_does_not_match_certificate">L\'identifiant ne correspond pas au certificat</string> + <string name="action_renew_certificate">Renouveler le certificat</string> + <string name="error_fetching_omemo_key">Erreur lors de la récupération de la clef OMEMO !</string> + <string name="verified_omemo_key_with_certificate">Clef OMEMO vérifiée avec un certificat !</string> + <string name="device_does_not_support_certificates">Votre appareil ne supporte pas la sélection de certificats client !</string> + <string name="pref_connection_options">Options de connexion</string> + <string name="pref_use_tor">Connection via Tor</string> + <string name="pref_use_tor_summary">Rediriger toutes les connexions via le réseau Tor. Nécessite Orbot.</string> + <string name="account_settings_hostname">Nom d\'hôte</string> + <string name="account_settings_port">Port</string> + <string name="hostname_or_onion">Adresse du serveur ou .onion</string> + <string name="not_a_valid_port">Ce numéro de port n\'est pas valide</string> + <string name="not_valid_hostname">Ce nom d\'hôte n\'est pas valide</string> + <string name="connected_accounts">%1$d compte(s) sur %2$d connecté(s)</string> + <plurals name="x_messages"> + <item quantity="one">%d message</item> + <item quantity="other">%d messages</item> + </plurals> + <string name="shared_file_with_x">Fichier partagé avec %s</string> + <string name="shared_image_with_x">Image partagée avec %s</string> + <string name="no_storage_permission">Conversations a besoin d\'accéder au stockage externe</string> + <string name="sync_with_contacts">Synchroniser avec contacts</string> + <string name="sync_with_contacts_long">Conversations souhaite associer vos contacts XMPP avec les contacts de votre appareil, pour utiliser leur nom complet et leur avatar.\n\nConversations va uniquement lire vos contacts et les associer localement, sans les uploader sur le serveur XMPP.\n\nVotre appareil va maintenant vous demander la permission d\'accéder à vos contacts.</string> + <string name="certificate_information">Informations du certificat</string> + <string name="certificate_subject">Sujet</string> + <string name="certificate_issuer">Émetteur</string> + <string name="certificate_cn">Nom commun</string> + <string name="certificate_o">Organisation</string> + <string name="certificate_sha1">SHA-1</string> + <string name="certicate_info_not_available">(Non disponible)</string> + <string name="certificate_not_found">Aucun certificat trouvé</string> + <string name="notify_on_all_messages">Notifier pour tous les messages</string> + <string name="notify_only_when_highlighted">Notifier uniquement quand surligné</string> + <string name="notify_never">Notifications désactivées</string> + <string name="notify_paused">Notifications en pause</string> + <string name="pref_picture_compression">Compresser images</string> + <string name="pref_picture_compression_summary">Redimensionner et compresser les images</string> + <string name="always">Toujours</string> + <string name="automatically">Automatiquement</string> + <string name="battery_optimizations_enabled">Optimisations de batterie activées</string> + <string name="battery_optimizations_enabled_explained">Votre appareil applique sur Conversations des optimisations de batterie très strictes qui pourraient provoquer des retards dans les notifications, voire des pertes de messages.\nNous vous recommandons de les désactiver.</string> + <string name="battery_optimizations_enabled_dialog">Votre appareil applique sur Conversations des optimisations de batterie très strictes qui pourraient provoquer des retards dans les notifications, voire des pertes de messages.\nVous allez maintenant avoir la possibilité de les désactiver.</string> + <string name="disable">Désactiver</string> + <string name="selection_too_large">La zone sélectionnée est trop grande</string> </resources> diff --git a/src/main/res/values-gl/strings.xml b/src/main/res/values-gl/strings.xml index ff6c5eeb..656c20d5 100644 --- a/src/main/res/values-gl/strings.xml +++ b/src/main/res/values-gl/strings.xml @@ -14,7 +14,6 @@ <string name="minutes_ago">min</string> <string name="unread_conversations">conversas sen ler</string> <string name="sending">enviando…</string> - <string name="encrypted_message">Descifrando mensaxe. Agarda uns intres…</string> <string name="nick_in_use">O apodo xa está en uso</string> <string name="moderator">Moderador</string> <string name="participant">Participante</string> @@ -42,9 +41,7 @@ <string name="clear_conversation_history">Limpar historial de conversa</string> <string name="clear_histor_msg">¿Queres borrar todas as mensaxes desta conversa?\n\n<b>Ollo:</b> Isto non afectará ás mensaxes gardadas noutros dispositivos ou servidores.</string> <string name="delete_messages">Borrar mensaxes</string> - <string name="also_end_conversation">Terminar esta conversa máis tarde</string> <string name="choose_presence">Selecciona recurso del contacto</string> - <string name="send_plain_text_message">Enviar mensaxe de texto</string> <string name="send_otr_message">Enviar mensaxe cifrado con OTR</string> <string name="send_pgp_message">Enviar mensaxe cifrado con OpenPGP</string> <string name="your_nick_has_been_changed">Modificouse o teu apodo</string> @@ -57,7 +54,6 @@ <string name="offering">ofrecendo…</string> <string name="no_pgp_key">Clave OpenPGP non atopada</string> <string name="contact_has_no_pgp_key">Conversations non foi quen de cifrar as túas mensaxes porque o teu contactos non está anunciando a súa clave pública.\n\n<small>Por favor, pídelle ao teu contacto que configure OpenPGP.</small></string> - <string name="encrypted_message_received"><i>Mensaxe cifrado recibido. Pulsa para ver.</i></string> <string name="pref_xmpp_resource">Recurso</string> <string name="pref_xmpp_resource_summary">O nome que identifica o cliente que estás a empregar</string> <string name="pref_accept_files">Aceptar arquivos</string> @@ -69,8 +65,6 @@ <string name="pref_vibrate_summary">Treme cando chega unha novo mensaxe</string> <string name="pref_sound">Son</string> <string name="pref_sound_summary">Reproduce un ton ca notificación</string> - <string name="pref_conference_notifications">Notificacións de conferencia</string> - <string name="pref_conference_notifications_summary">Siempre notifica cuando chega unha mensaxe de conferencia e non solo cuando chega unha mensaxe destacada</string> <string name="pref_notification_grace_period">Notificacións Carbons</string> <string name="pref_notification_grace_period_summary">Deshabilita as notificacións durante un corto periodo de tiempo despois de recibir a copia da mensaxe carbón</string> <string name="pref_advanced_options">Opcións avanzadas</string> @@ -109,7 +103,6 @@ <string name="account_status_regis_conflict">O identificador xa está en uso</string> <string name="account_status_regis_success">Rexistro completado</string> <string name="account_status_regis_not_sup">O servidor non soporta rexistros</string> - <string name="encryption_choice_none">Texto plano</string> <string name="encryption_choice_otr">OTR</string> <string name="encryption_choice_pgp">OpenPGP</string> <string name="mgmt_account_edit">Editar conta</string> diff --git a/src/main/res/values-id/strings.xml b/src/main/res/values-id/strings.xml index 6f2d967f..745e15ad 100644 --- a/src/main/res/values-id/strings.xml +++ b/src/main/res/values-id/strings.xml @@ -9,7 +9,6 @@ <string name="action_secure">Amankan Percakapan</string> <string name="action_add_account">Tambah Akun</string> <string name="action_edit_contact">Ubah Nama</string> - <string name="action_add_phone_book">Tambah ke buku telepon</string> <string name="action_delete_contact">Hapus dari roster</string> <string name="action_block_contact">Blokir kontak</string> <string name="action_unblock_contact">Batal blokir kontak</string> @@ -28,7 +27,6 @@ <string name="minutes_ago">%d min lalu</string> <string name="unread_conversations">Percakapan belum dibaca</string> <string name="sending">mengirim...</string> - <string name="encrypted_message">Menerjemahkan pesan. Tunggu sebentar...</string> <string name="nick_in_use">Nick ini sudah digunakan</string> <string name="admin">Administrator</string> <string name="owner">Pemilik</string> @@ -74,9 +72,7 @@ <string name="clear_conversation_history">Hapus Riwayat Percakapan</string> <string name="clear_histor_msg">Apakah Anda ingin menghapus semua pesan dalam Percakapan ini\n\n<b>Peringatan:</b>ini tidak akan mempengaruhi pesan yang disimpan pada perangkat atau server lain.</string> <string name="delete_messages">Hapus pesan</string> - <string name="also_end_conversation">Akhiri percakapan setelahnya</string> <string name="choose_presence">Pilih kehadiran untuk kontak</string> - <string name="send_plain_text_message">Kirim pesan teks biasa</string> <string name="send_otr_message">Kirim pesan terenskripsi OTR</string> <string name="send_pgp_message">Kirim pesan terenskripsi OpenPGP</string> <string name="your_nick_has_been_changed">Nick kamu telah dirubah</string> @@ -92,7 +88,6 @@ <string name="contact_has_no_pgp_key">Conversations tidak dapat mengenkripsi pesan Anda karena kontak tidak mengumumkan kunci publiknya.\n\n<small>Silakan meminta kontak Anda untuk menyetel OpenPGP</small></string> <string name="no_pgp_keys">Tidak ada kunci OpenPGP ditemukan</string> <string name="contacts_have_no_pgp_keys">Percakapan tidak dapat mengenkripsi pesan Anda karena kontak tidak mengumumkan kunci publik mereka.\n\n<small>Silakan meminta kontak Anda untuk setup OpenPGP.</small></string> - <string name="encrypted_message_received"><i>Pesan terenkripsi diterima. Sentuh untuk membongkar dan melihatnya.</i></string> <string name="pref_general">Umum</string> <string name="pref_xmpp_resource">XMPP resource</string> <string name="pref_xmpp_resource_summary">Identifikasi nama klien ini dengan</string> @@ -105,8 +100,6 @@ <string name="pref_vibrate_summary">Juga aktifkan getaran bila pesan baru tiba</string> <string name="pref_sound">Suara</string> <string name="pref_sound_summary">mainkan suara saat menerima notifikasi</string> - <string name="pref_conference_notifications">Notifikasi Conference</string> - <string name="pref_conference_notifications_summary">Selalu memberitahukan bila pesan conference baru diterima daripada hanya dicetak tebal</string> <string name="pref_notification_grace_period">Tenggang waktu pemberitahuan</string> <string name="pref_notification_grace_period_summary">Nonaktifkan pemberitahuan untuk waktu yang singkat setelah salinan diterima</string> <string name="pref_advanced_options">Opsi Lanjutan</string> @@ -149,7 +142,6 @@ <string name="account_status_regis_not_sup">Server tidak mendukung pendaftaran akun.</string> <string name="account_status_security_error">Kesalahan keamanan</string> <string name="account_status_incompatible_server">Server tidak cocok</string> - <string name="encryption_choice_none">Teks biasa</string> <string name="encryption_choice_otr">OTR</string> <string name="encryption_choice_pgp">OpenPGP</string> <string name="mgmt_account_edit">Ubah akun</string> @@ -170,7 +162,6 @@ <string name="passwords_do_not_match">Password tidak sama</string> <string name="invalid_jid">Jabber ID tidak valid</string> <string name="error_out_of_memory">Memori habis. Gambar terlalu besar</string> - <string name="add_phone_book_text">Apakah anda ingin menambahkan %s ke daftar kontak anda?</string> <string name="contact_status_online">online</string> <string name="contact_status_free_to_chat">bebas untuk chatting</string> <string name="contact_status_away">pergi</string> @@ -186,7 +177,6 @@ <string name="server_info_blocking">XEP-0191: Blocking Command</string> <string name="server_info_roster_version">XEP-0237: Roster Versioning</string> <string name="server_info_stream_management">XEP-0198: Stream Management</string> - <string name="server_info_pep">XEP-0163: PEP (Avatars)</string> <string name="server_info_available">tersedia</string> <string name="server_info_unavailable">tidak tersedia</string> <string name="missing_public_keys">Pemberitahuan kunci publik tidak ditemukan</string> @@ -204,6 +194,8 @@ <string name="reception_failed">Penerimaan gagal</string> <string name="your_fingerprint">Fingerprint Anda</string> <string name="otr_fingerprint">OTR fingerprint</string> + <string name="other_devices">Perangkat lainnya</string> + <string name="done">Selesai</string> <string name="verify">Verifikasi</string> <string name="decrypt">Deskripsi</string> <string name="conferences">Conferences</string> @@ -249,7 +241,6 @@ <string name="skip">Lewati</string> <string name="disable_notifications">Nonaktifkan notifikasi</string> <string name="disable_notifications_for_this_conversation">Nonaktifkan notifikasi untuk percakapan ini</string> - <string name="notifications_disabled">Notifikasi telah dimatikan</string> <string name="enable">Aktifkan</string> <string name="conference_requires_password">Conference membutuhkan password</string> <string name="enter_password">Masukan password</string> @@ -289,10 +280,12 @@ <string name="conference_kicked">Anda telah ditendang dari conference ini</string> <string name="using_account">menggunakan akun %s</string> <string name="not_connected_try_again">Anda tidak terhubung. Coba lagi nanti</string> + <string name="check_x_filesize">Cek %s ukuran</string> <string name="message_options">Opsi pesan</string> <string name="copy_text">Salin teks</string> <string name="copy_original_url">Salin URL asli</string> <string name="send_again">Kirim lagi</string> + <string name="file_url">URL Berkas</string> <string name="message_text">Pesan teks</string> <string name="url_copied_to_clipboard">URL disalin ke clipboard</string> <string name="message_copied_to_clipboard">Pesan disalin ke clipboard</string> @@ -304,7 +297,6 @@ <string name="verify_otr">Verifikasi OTR</string> <string name="remote_fingerprint">Remote Sidik jari</string> <string name="scan">pindai</string> - <string name="or_touch_phones">(atau menyentuh telepon)</string> <string name="smp">Socialist Millionaire Protocol</string> <string name="shared_secret_hint">Petunjuk atau Pertanyaan</string> <string name="shared_secret_secret">Rahasia bersama</string> @@ -347,6 +339,7 @@ <string name="reset">Ulang</string> <string name="account_image_description">Avatar akun</string> <string name="copy_otr_clipboard_description">Salin OTR fingerprint ke clipboard</string> + <string name="clear_other_devices">Bersihkan perangkat</string> <string name="fetching_history_from_server">Mengambil data dari server</string> <string name="no_more_history_on_server">Tidak ada data lagi di server</string> <string name="updating">Merubah...</string> @@ -384,7 +377,6 @@ <string name="public_conference">Conference umum</string> <string name="private_conference">Rahasia, hanya member conference</string> <string name="conference_options">Opsi conference</string> - <string name="members_only">Rahasia (Hanya member)</string> <string name="non_anonymous">Non Anonymous</string> <string name="modified_conference_options">Opsi conference dimodifikasi!</string> <string name="could_not_modify_conference_options">Tidak dapat merubah pengaturan conference</string> @@ -413,7 +405,6 @@ <string name="offering_x_file">Menawarkan %s</string> <string name="hide_offline">Sembunyikan Offline</string> <string name="disable_account">Nonaktifkan Akun</string> - <string name="contact_is_typing">%s sedang mengetik...</string> <string name="contact_has_stopped_typing">%s telah berhenti mengetik</string> <string name="pref_chat_states">Notifikasi ketik pesan</string> <string name="pref_chat_states_summary">Biarkan kontak Anda tahu ketika Anda sedang menulis pesan baru</string> @@ -424,7 +415,6 @@ <string name="received_location">Lokasi yang diterima</string> <string name="title_undo_swipe_out_conversation">Percakapan tertutup</string> <string name="title_undo_swipe_out_muc">Tinggalkan conference</string> - <string name="pref_certificate_options">Opsi Sertifikat</string> <string name="pref_dont_trust_system_cas_title">Jangan percaya sistem CA</string> <string name="pref_dont_trust_system_cas_summary">Semua sertifikat harus disetujui secara manual</string> <string name="pref_remove_trusted_certificates_title">Hapus sertifikat</string> @@ -444,4 +434,13 @@ <string name="none">Tak satupun</string> <string name="recently_used">Maling sering digunakan</string> <string name="choose_quick_action">Pilih aksi cepat</string> + <string name="search_for_contacts_or_groups">Cari grup atau daftar kontak</string> + <string name="send_private_message">Kirim pesan pribadi</string> + <string name="user_has_left_conference">%s meninggalkan conference!</string> + <string name="username">Username</string> + <string name="username_hint">Username</string> + <string name="invalid_username">Username ini tidak valid</string> + <string name="download_failed_server_not_found">Unduhan gagal: Server tidak ditemukan</string> + <string name="download_failed_file_not_found">Unduh gagal: Berkas tidak ditemukan</string> + <string name="download_failed_could_not_connect">Unduhan gagal: Tidak dapat terhubung ke host</string> </resources> diff --git a/src/main/res/values-it/strings.xml b/src/main/res/values-it/strings.xml index 8397b043..26db33ae 100644 --- a/src/main/res/values-it/strings.xml +++ b/src/main/res/values-it/strings.xml @@ -28,7 +28,8 @@ <string name="minutes_ago">%d min fa</string> <string name="unread_conversations">Conversazioni non lette</string> <string name="sending">invio…</string> - <string name="encrypted_message">Decifrazione del messaggio. Attendere prego…</string> + <string name="message_decrypting">Decifrazione messaggio. Attendere prego...</string> + <string name="pgp_message">Messaggio cifrato con OpenPGP</string> <string name="nick_in_use">Nome utente già in uso</string> <string name="admin">Amministratore</string> <string name="owner">Proprietario</string> @@ -60,7 +61,7 @@ <string name="crash_report_title">Errore di Conversations</string> <string name="crash_report_message">Se scegli di inviare una segnalazione dell’errore aiuterai lo sviluppo di Conversations\n<b>Attenzione:</b> Questo utilizzerà il tuo account XMPP per inviare la segnalazione agli sviluppatori.</string> <string name="send_now">Invia adesso</string> - <string name="send_never">Non chiedere mai più</string> + <string name="send_never">Non chiedere più</string> <string name="problem_connecting_to_account">Impossibile collegarsi tramite questo utente</string> <string name="problem_connecting_to_accounts">Impossibile collegarsi tramite più utenti</string> <string name="touch_to_fix">Tocca qui per gestire i tuoi utenti</string> @@ -74,10 +75,12 @@ <string name="clear_conversation_history">Pulisci la cronologia della Conversazione</string> <string name="clear_histor_msg">Vuoi cancellare tutti i messaggi di questa Conversazione?\n\n<b>Attenzione:</b> Questo non influenzerà i messaggi presenti su altri dispositivi o server.</string> <string name="delete_messages">Elimina messaggi</string> - <string name="also_end_conversation">Termina questa conversazione in seguito</string> + <string name="also_end_conversation">Termina questa conversazione successivamente</string> <string name="choose_presence">Choose presence to contact</string> - <string name="send_plain_text_message">Messaggio non cifrato</string> + <string name="send_unencrypted_message">Invia messaggio non cifrato</string> <string name="send_otr_message">Messaggio OTR</string> + <string name="send_omemo_message">Invia messaggio cifrato OMEMO</string> + <string name="send_omemo_x509_message">Invia messaggio cifrato v\\OMEMO</string> <string name="send_pgp_message">Messaggio OpenPGP</string> <string name="your_nick_has_been_changed">Il tuo nome utente è stato cambiato</string> <string name="send_unencrypted">Invia non cifrato</string> @@ -86,6 +89,7 @@ <string name="openkeychain_required_long">Conversations usa una app di terze parti chiamata <b>OpenKeychain</b> per cifrare e decifrare i messaggi per gestire le tue chiavi pubbliche.\n\nOpenKeychain è rilasciato secondo i termini della GPLv3 ed è disponibile sia su F-Droid, che su Google Play.\n\n<small>(Riavvia Conversations in seguito.)</small></string> <string name="restart">Riavvia</string> <string name="install">Installa</string> + <string name="openkeychain_not_installed">Per favore installa OpenKeychain</string> <string name="offering">offrendo…</string> <string name="waiting">in attesa…</string> <string name="no_pgp_key">Nessuna chiave OpenPGP trovata</string> @@ -97,7 +101,7 @@ <string name="pref_xmpp_resource">Risorsa XMPP</string> <string name="pref_xmpp_resource_summary">Il nome con il quale questo client si identifica</string> <string name="pref_accept_files">Accetta i file</string> - <string name="pref_accept_files_size_summary">Accetta automaticamente i file più piccoli di…</string> + <string name="pref_accept_files_summary">Accetta automaticamente i file più piccoli di…</string> <string name="pref_notification_settings">Impostazioni di Notifica</string> <string name="pref_notifications">Notifiche</string> <string name="pref_notifications_summary">Notifica quando arriva un nuovo messaggio</string> @@ -105,8 +109,6 @@ <string name="pref_vibrate_summary">Vibra anche quando arriva un nuovo messaggio</string> <string name="pref_sound">Suono</string> <string name="pref_sound_summary">Riproduci una suoneria con la notifica</string> - <string name="pref_conference_notifications">Notifiche Conferenze</string> - <string name="pref_conference_notifications_summary">Notifica sempre quando arriva un nuovo messaggio da una conferenza, invece che solo quando in primo piano</string> <string name="pref_notification_grace_period">Periodo tra notifiche</string> <string name="pref_notification_grace_period_summary">Disabilita le notifiche per un breve lasso di tempo dopo che un messaggio è stato ricevuto</string> <string name="pref_advanced_options">Opzioni Avanzate</string> @@ -149,9 +151,10 @@ <string name="account_status_regis_not_sup">Il Server non supporta la registrazione</string> <string name="account_status_security_error">Errore di sicurezza</string> <string name="account_status_incompatible_server">Server non compatibile</string> - <string name="encryption_choice_none">Testo semplice</string> + <string name="encryption_choice_unencrypted">Non cifrato</string> <string name="encryption_choice_otr">OTR</string> <string name="encryption_choice_pgp">OpenPGP</string> + <string name="encryption_choice_omemo">OMEMO</string> <string name="mgmt_account_edit">Modifica utente</string> <string name="mgmt_account_delete">Elimina utente</string> <string name="mgmt_account_disable">Disabilita temporaneamente</string> @@ -170,7 +173,7 @@ <string name="passwords_do_not_match">Le Password non corrispondono</string> <string name="invalid_jid">Questo non è un ID Jabber valido</string> <string name="error_out_of_memory">Memoria esaurita. L’immagine è tropppo grande</string> - <string name="add_phone_book_text">Vuoi aggiungere %s alla rubrica del telefono?</string> + <string name="add_phone_book_text">Vuoi aggiungere %s alla tua rubrica?</string> <string name="contact_status_online">online</string> <string name="contact_status_free_to_chat">vuole chattare</string> <string name="contact_status_away">assente</string> @@ -186,7 +189,8 @@ <string name="server_info_blocking">XEP-0191: Blocking Command</string> <string name="server_info_roster_version">XEP-0237: Roster Versioning</string> <string name="server_info_stream_management">XEP-0198: Stream Management</string> - <string name="server_info_pep">XEP-0163: PEP (Avatars)</string> + <string name="server_info_pep">XEP-0163: PEP (Avatars / OMEMO)</string> + <string name="server_info_http_upload">XEP-0363: HTTP File Upload</string> <string name="server_info_available">disponibile</string> <string name="server_info_unavailable">non disponibile</string> <string name="missing_public_keys">Annuncio chiave pubblica non effettuato</string> @@ -204,17 +208,28 @@ <string name="reception_failed">Ricezione fallita</string> <string name="your_fingerprint">La tua impronta</string> <string name="otr_fingerprint">Impronta OTR</string> + <string name="omemo_fingerprint">Fingerprint OMEMO</string> + <string name="omemo_fingerprint_x509">v\\OMEMO fingerprint</string> + <string name="omemo_fingerprint_selected_message">Fingerprint OMEMO del messaggio</string> + <string name="omemo_fingerprint_x509_selected_message">Fingerprint v\\OMEMO del messaggio</string> + <string name="this_device_omemo_fingerprint">Propria fingerprint OMEMO</string> + <string name="other_devices">Altri dispositivi</string> + <string name="trust_omemo_fingerprints">Fidati delle Fingerprint OMEMO</string> + <string name="fetching_keys">Ricezione chiavi...</string> + <string name="done">Fatto</string> <string name="verify">Verifica</string> <string name="decrypt">Decripta</string> <string name="conferences">Conferenze</string> <string name="search">Cerca</string> <string name="create_contact">Crea Contatto</string> + <string name="enter_contact">Inserisci contatto</string> <string name="join_conference">Entra in Conferenza</string> <string name="delete_contact">Elimina Contatto</string> <string name="view_contact_details">Mostra dettagli contatto</string> <string name="block_contact">Blocca contatto</string> <string name="unblock_contact">Sblocca contatto</string> <string name="create">Crea</string> + <string name="select">Seleziona</string> <string name="contact_already_exists">Il contatto esiste già</string> <string name="join">Entra</string> <string name="conference_address">Indirizzo conferenza</string> @@ -249,7 +264,6 @@ <string name="skip">Salta</string> <string name="disable_notifications">Disabilita le notifiche</string> <string name="disable_notifications_for_this_conversation">Disabilita le notifiche per questa conversazione</string> - <string name="notifications_disabled">Le notifiche sono disabilitate</string> <string name="enable">Abilita</string> <string name="conference_requires_password">La conferenza richiede una password</string> <string name="enter_password">Inserisci la password</string> @@ -284,15 +298,19 @@ <string name="pref_conference_name">Nome della conferenza</string> <string name="pref_conference_name_summary">Usa il soggetto della stanza al posto del JID per identificare le conferenze</string> <string name="toast_message_otr_fingerprint">Impronta OTR copiata!</string> + <string name="toast_message_omemo_fingerprint">Fingerprint OMEMO copiata negli appunti|</string> <string name="conference_banned">Sei stato bandito da questa conferenza</string> <string name="conference_members_only">Questa conferenza è solo per membri</string> <string name="conference_kicked">Sei stato buttato fuori dalla conferenza</string> <string name="using_account">usando l’utente %s</string> + <string name="checking_x">Controllo %s su host HTTP</string> <string name="not_connected_try_again">Non sei connesso. Riprova più tardi</string> + <string name="check_x_filesize">Controllo dimensione %s</string> <string name="message_options">Opzioni del messaggio</string> <string name="copy_text">Copia testo</string> <string name="copy_original_url">Copia URL originale</string> <string name="send_again">Invia di nuovo</string> + <string name="file_url">URL del file</string> <string name="message_text">Messaggio di testo</string> <string name="url_copied_to_clipboard">URL copiato</string> <string name="message_copied_to_clipboard">Messaggio copiato</string> @@ -304,7 +322,6 @@ <string name="verify_otr">Verifica OTR</string> <string name="remote_fingerprint">Impronta remota</string> <string name="scan">scan</string> - <string name="or_touch_phones">(o fai toccare i dispositivi)</string> <string name="smp">Socialist Millionaire Protocol</string> <string name="shared_secret_hint">Suggerimento o Domanda</string> <string name="shared_secret_secret">Segreto condiviso</string> @@ -321,6 +338,9 @@ <string name="conversations_foreground_service">Conversations</string> <string name="pref_keep_foreground_service">Mantieni il servizio in primo piano</string> <string name="pref_keep_foreground_service_summary">Evita che il sistema operativo chiuda la connessione</string> + <string name="pref_export_logs">Esporta Log</string> + <string name="pref_export_logs_summary">Scrivi i log su card SD</string> + <string name="notification_export_logs_title">Scrittura log su card SD</string> <string name="choose_file">Scegli file</string> <string name="receiving_x_file">Ricezione di %1$s file (%2$d%% completato)</string> <string name="download_x_file">Scarica %s</string> @@ -347,6 +367,17 @@ <string name="reset">Reset</string> <string name="account_image_description">Avatar utente</string> <string name="copy_otr_clipboard_description">Copia impronta OTR</string> + <string name="copy_omemo_clipboard_description">Copia fingerprint OMEMO negli appunti</string> + <string name="regenerate_omemo_key">Rigenera chiave OMEMO</string> + <string name="wipe_omemo_pep">Cancella altri dispositivi da PEP</string> + <string name="clear_other_devices">Pulisci dispositivi</string> + <string name="clear_other_devices_desc">Sei sicuro/a di voler rimuovere tutti gli altri dispositivi dall\'annuncio OMEMO? La prossima volta che si connetteranno si riannunceranno, ma potrebbero non ricevere i messaggi inviati nel frattempo.</string> + <string name="purge_key">Elimina chiave</string> + <string name="purge_key_desc_part1">Sei sicuro/a di voler cancellare questa chiave?</string> + <string name="purge_key_desc_part2">Verrà considerata irreversibilmente compromessa, e non potrai più creare una sessione con essa.</string> + <string name="error_no_keys_to_trust_server_error">Non ci sono chiavi utilizzabili per questo contatto.\nLa raccolta di nuove chiavi dal server è fallita. Forse qualcosa non va con il tuo server dei contatti.</string> + <string name="error_no_keys_to_trust">Non ci sono chiavi usabili per questo contatto. Se hai cancellato qualsiasi loro chiave, devono generarne di nuove.</string> + <string name="error_trustkeys_title">Errore</string> <string name="fetching_history_from_server">Caricamento della cronologia dal server</string> <string name="no_more_history_on_server">Fine cronologia sul server</string> <string name="updating">Caricamento…</string> @@ -384,8 +415,10 @@ <string name="public_conference">Conferenza pubblicamente accessibile</string> <string name="private_conference">Conferenza privata</string> <string name="conference_options">Opzioni conferenza</string> - <string name="members_only">Privata (solo membri)</string> + <string name="members_only">Privato, solo membri</string> <string name="non_anonymous">Non anonimo</string> + <string name="moderated">Moderata</string> + <string name="you_are_not_participating">Non stai partecipando</string> <string name="modified_conference_options">Modificate le opzioni della conferenza!</string> <string name="could_not_modify_conference_options">Impossibile modificare opzioni conferenza</string> <string name="never">Mai</string> @@ -395,7 +428,7 @@ <string name="eight_hours">8 ore</string> <string name="until_further_notice">Fino a nuovo avviso</string> <string name="pref_input_options">Opzioni di ingresso</string> - <string name="pref_enter_is_send">Invio invia</string> + <string name="pref_enter_is_send">Invio spedisce</string> <string name="pref_enter_is_send_summary">Il tasto invio spedisce il messaggio</string> <string name="pref_display_enter_key">Mostra il tasto invio</string> <string name="pref_display_enter_key_summary">Cambia il tasto delle faccine nel tastodi invio</string> @@ -406,6 +439,7 @@ <string name="apk">Applicazione Android</string> <string name="vcard">Contatto</string> <string name="received_x_file">Ricevuto %s</string> + <string name="disable_foreground_service">Disabilita i servizi in background</string> <string name="touch_to_open_conversations">Tocca per avviare Conversations</string> <string name="avatar_has_been_published">Il tuo avatar è stato pubblicato!</string> <string name="sending_x_file">Invio %s</string> @@ -414,6 +448,7 @@ <string name="disable_account">Disabilita l\'account</string> <string name="contact_is_typing">%s sta digitando...</string> <string name="contact_has_stopped_typing">%s ha smesso di digitare</string> + <string name="pref_chat_states">Notifiche di composizione</string> <string name="pref_chat_states_summary">Permetti al tuo contatto di vedere quando stai digitando</string> <string name="send_location">Invia la posizione</string> <string name="show_location">Mostra la posizione</string> @@ -422,7 +457,6 @@ <string name="received_location">Posizione ricevuta</string> <string name="title_undo_swipe_out_conversation">Conversazione interrotta</string> <string name="title_undo_swipe_out_muc">Conferenza terminata</string> - <string name="pref_certificate_options">Opzioni per i certificati</string> <string name="pref_dont_trust_system_cas_title">Non ti fidare delle CA di sistema</string> <string name="pref_dont_trust_system_cas_summary">Tutti i certificati devono essere accettati manualmente</string> <string name="pref_remove_trusted_certificates_title">Elimina i certificati</string> @@ -439,4 +473,78 @@ <item quantity="one">Seleziona il %d contatto</item> <item quantity="other">Selezionati %d contatti</item> </plurals> + <string name="pref_quick_action_summary">Sostituisci il tasto invio con un\'azione rapida</string> + <string name="pref_quick_action">Azione Rapida</string> + <string name="none">Nessuno</string> + <string name="recently_used">Usati recentemente</string> + <string name="choose_quick_action">Scegli azione rapida</string> + <string name="search_for_contacts_or_groups">Cerca contatti o gruppi</string> + <string name="send_private_message">Invia messaggio privato</string> + <string name="user_has_left_conference">%s ha abbandonato la conferenza!</string> + <string name="username">Utente</string> + <string name="username_hint">Utente</string> + <string name="invalid_username">Questo non è un nome utente valido</string> + <string name="download_failed_server_not_found">Download fallito: Server non trovato</string> + <string name="download_failed_file_not_found">Download fallito: File non trovato</string> + <string name="download_failed_could_not_connect">Download fallito: Impossibile connettersi all\'host</string> + <string name="pref_use_white_background">Usa sfondo bianco</string> + <string name="pref_use_white_background_summary">Mostra i messaggi ricevuti con testo nero su sfondo bianco</string> + <string name="account_status_tor_unavailable">Rete Tor non disponibile</string> + <string name="server_info_broken">Rotto</string> + <string name="pref_presence_settings">Impostazioni presenza</string> + <string name="pref_away_when_screen_off">\"Non disponibile\" a schermo spento</string> + <string name="pref_away_when_screen_off_summary">Imposta la tua risorsa come non disponibile quando lo schermo è spento</string> + <string name="pref_xa_on_silent_mode">Non disponibile in modalità silenzioso</string> + <string name="pref_xa_on_silent_mode_summary">Imposta la tua risorsa come non disponibile quando il dispositivo è in modalità silenziosa</string> + <string name="action_add_account_with_certificate">Aggiungi account con certificato</string> + <string name="unable_to_parse_certificate">Impossibile analizzare il certificato</string> + <string name="authenticate_with_certificate">Lasciare vuoto per autenticarsi con certificato</string> + <string name="captcha_ocr">Testo captcha</string> + <string name="captcha_required">Captcha richiest</string> + <string name="captcha_hint">inserire il testo dall\'immagine</string> + <string name="certificate_chain_is_not_trusted">Catena di certificati non affidabile</string> + <string name="jid_does_not_match_certificate">L\'ID Jabber non corrisponde al certificato</string> + <string name="action_renew_certificate">Rinnova certificato</string> + <string name="error_fetching_omemo_key">Errore ricezione chiave OMEMO!</string> + <string name="verified_omemo_key_with_certificate">Chiave OMEMO verificata con certificato!</string> + <string name="device_does_not_support_certificates">Il tuo dispositivo non supporta la selezione di certificati utente!</string> + <string name="pref_connection_options">Opzioni di connessione</string> + <string name="pref_use_tor">Connettiti via Tor</string> + <string name="pref_use_tor_summary">Indirizza tutte le connessioni attraverso la rete Tor. Richiede Orbot</string> + <string name="account_settings_hostname">Nome host</string> + <string name="account_settings_port">Porta</string> + <string name="hostname_or_onion">Indirizzo server o .onion</string> + <string name="not_a_valid_port">Questo non è un numero di porta valido</string> + <string name="not_valid_hostname">Questo non è un nome host valido</string> + <string name="connected_accounts">%1$d su %2$d account connessi</string> + <plurals name="x_messages"> + <item quantity="one">%d messaggio</item> + <item quantity="other">%d messaggi</item> + </plurals> + <string name="shared_file_with_x">Condividi file con %s</string> + <string name="shared_image_with_x">Immagine condivisa con %s</string> + <string name="no_storage_permission">Conversations necessita di accesso alla memoria esterna</string> + <string name="sync_with_contacts">Sincronizza con i contatti</string> + <string name="sync_with_contacts_long">Conversations vuole confrontare il tuo roster XMPP con i tuoi contatti per mostrare i loro nomi ed avatar.\n\nConversations leggerà solamente i contatti e li confronterà localmente senza caricarli sul server.\n\nTi verrà ora chiesto il permesso di accedere ai tuoi contatti.</string> + <string name="certificate_information">Informazioni del certificato</string> + <string name="certificate_subject">Soggetto</string> + <string name="certificate_issuer">Emittente</string> + <string name="certificate_cn">Nome comune</string> + <string name="certificate_o">Organizzazione</string> + <string name="certificate_sha1">SHA1</string> + <string name="certicate_info_not_available">(Non disponibile)</string> + <string name="certificate_not_found">Nessun certificato trovato</string> + <string name="notify_on_all_messages">Notifica per tutti i messaggi</string> + <string name="notify_only_when_highlighted">Notifica solo quando evidenziato</string> + <string name="notify_never">Notifiche disabilitate</string> + <string name="notify_paused">Notifiche in pausa</string> + <string name="pref_picture_compression">Comprimi immagini</string> + <string name="pref_picture_compression_summary">Ridimensiona e comprimi le immagini</string> + <string name="always">Sempre</string> + <string name="automatically">Automaticamente</string> + <string name="battery_optimizations_enabled">Ottimizzazioni batteria abilitate</string> + <string name="battery_optimizations_enabled_explained">Il tuo dispositivo sta facendo delle ingenti ottimizzazioni della batteria per Conversations che potrebbero portare ritardi alle notifiche o anche perdita di messaggi.\nSi consiglia di disabilitarle.</string> + <string name="battery_optimizations_enabled_dialog">Il tuo dispositivo sta facendo delle ingenti ottimizzazioni della batteria per Conversations che potrebbero portare ritardi alle notifiche o anche perdita di messaggi.\n\nTi verrà ora chiesto di disabilitarle.</string> + <string name="disable">Disabilita</string> + <string name="selection_too_large">L\'area selezionata è troppo grande</string> </resources> diff --git a/src/main/res/values-iw/strings.xml b/src/main/res/values-iw/strings.xml index cc12c7d0..9348a88e 100644 --- a/src/main/res/values-iw/strings.xml +++ b/src/main/res/values-iw/strings.xml @@ -1,16 +1,19 @@ <?xml version='1.0' encoding='UTF-8'?> <resources> <string name="action_settings">הגדרות</string> - <string name="action_add">דיון חדש</string> + <string name="action_add">שיחה חדשה</string> <string name="action_accounts">נהל חשבונות</string> - <string name="action_end_conversation">סיים את דיון זה</string> + <string name="action_end_conversation">סיים שיחה זו</string> <string name="action_contact_details">פרטי איש קשר</string> <string name="action_muc_details">פרטי ועידה</string> <string name="action_secure">דיון מאובטח</string> <string name="action_add_account">הוסף חשבון</string> <string name="action_edit_contact">ערוך שם</string> - <string name="action_add_phone_book">הוסף אל פנקס טלפונים</string> - <string name="action_delete_contact">מחק מתוך רשימה</string> + <string name="action_delete_contact">מחק מרשימת אנשי הקשר</string> + <string name="action_block_contact">חסום איש קשר</string> + <string name="action_unblock_contact">בטל חסימת איש קשר</string> + <string name="action_block_domain">חסום דומיין</string> + <string name="action_unblock_domain">בטל חסימת דומיין</string> <string name="title_activity_manage_accounts">נהל חשבונות</string> <string name="title_activity_settings">הגדרות</string> <string name="title_activity_conference_details">פרטי ועידה</string> @@ -18,89 +21,101 @@ <string name="title_activity_sharewith">שתף בעזרת Conversations</string> <string name="title_activity_start_conversation">התחל דיון</string> <string name="title_activity_choose_contact">בחר איש קשר</string> - <string name="just_now">רק כעת</string> - <string name="minute_ago">לפני דקה 1</string> + <string name="title_activity_block_list">רשימת חסימה</string> + <string name="just_now">ממש עכשיו</string> + <string name="minute_ago">לפני דקה</string> <string name="minutes_ago">לפני %d דקות</string> - <string name="unread_conversations">דיונים שלא נקראו</string> - <string name="sending">כעת שולח…</string> - <string name="encrypted_message">כעת מפענח הודעה. אנא המתן…</string> - <string name="nick_in_use">שם כינוי כבר מצוי בשימוש</string> + <string name="unread_conversations">שיחות שלא נקראו</string> + <string name="sending">שולח...</string> + <string name="message_decrypting">כעת מפענח צופן הודעה. אנא המתן…</string> + <string name="pgp_message">הודעה מוצפנת OpenPGP</string> + <string name="nick_in_use">שם כינוי כבר בשימוש</string> <string name="admin">מנהל</string> <string name="owner">בעלים</string> - <string name="moderator">אחראי</string> + <string name="moderator">אחראי (Moderator)</string> <string name="participant">משתתף</string> <string name="visitor">מבקר</string> - <string name="remove_contact_text">האם ברצונך להסיר את %s מתןך הרשימה שלך? הדיונים אשר משוייכים עם חשבון זה לא יוסרו.</string> - <string name="remove_bookmark_text">האם ברצונך להסיר את %s בתוור סימנייה? הדיונים אשר משוייכים עם סימנייה זו לא יוסרו.</string> - <string name="register_account">רשום חשבון חדש על שרת</string> - <string name="share_with">שתף בעזרת</string> - <string name="start_conversation">התחל דיון</string> + <string name="remove_contact_text">האם ברצונך להסיר את %s מתוך רשימת אנשי הקשר? השיחה המשוייכת עם איש קשר זה לא תוסר.</string> + <string name="block_contact_text">האם ברצונך לחסום קבלת הודעות מאת %s?</string> + <string name="unblock_contact_text">האם ברצונך לבטל את החסימה ולאפשר קבלת הודעות מאת %s?</string> + <string name="block_domain_text">לחסום את כל האנשים מתוך %s?</string> + <string name="unblock_domain_text">לבטל את חסימת כל האנשים מתוך %s?</string> + <string name="contact_blocked">איש קשר נחסם</string> + <string name="remove_bookmark_text">האם ברצונך להסיר את %s בתור סימנייה? הדיונים אשר משוייכים עם סימנייה זו לא יוסרו.</string> + <string name="register_account">צור חשבון חדש בשרת</string> + <string name="change_password_on_server">שינוי סיסמה בשרת</string> + <string name="share_with">שתף באמצעות...</string> + <string name="start_conversation">התחל שיחה</string> <string name="invite_contact">הזמן איש קשר</string> <string name="contacts">אנשי קשר</string> <string name="cancel">ביטול</string> + <string name="set">הגדר</string> <string name="add">הוסף</string> <string name="edit">ערוך</string> <string name="delete">מחק</string> + <string name="block">חסום</string> + <string name="unblock">בטל חסימה</string> <string name="save">שמור</string> <string name="ok">אישור</string> <string name="crash_report_title">Conversations קרסה</string> - <string name="crash_report_message">על ידי שליחת עקבות מחסנית אתה עוזר להתקדמות הפיתוח של Conversations\n<b>אזהרה:</b> זו תעשה שימוש בחשבון XMPP שלך כדי לשלוח עקבות מחסנית אל המפתח.</string> + <string name="crash_report_message">על ידי שליחת Stacktraces אתה עוזר להתקדמות הפיתוח של Conversations\n<b>אזהרה:</b> פעולה זו תעשה שימוש בחשבון XMPP שלך כדי לשלוח עקבות מחסנית אל המפתח.</string> <string name="send_now">שלח עכשיו</string> <string name="send_never">לעולם אל תשאל שוב</string> - <string name="problem_connecting_to_account">לא מסוגל להתחבר אל חשבון</string> - <string name="problem_connecting_to_accounts">לא מסוגל להתחבר אל חשבונות מרובים</string> + <string name="problem_connecting_to_account">התחברות לחשבון נכשלה</string> + <string name="problem_connecting_to_accounts">התחברות למספר חשבונות נכשלה</string> <string name="touch_to_fix">לחץ כאן כדי לנהל את החשבונות שלך</string> <string name="attach_file">צרף קובץ</string> - <string name="not_in_roster">איש קשר אינו מצוי בתוך הרשימה שלך. האם ברצונך להוסיפו?</string> + <string name="not_in_roster">איש קשר אינו מצוי בתוך רשימת אנשי הקשר שלך. האם ברצונך להוסיפו?</string> <string name="add_contact">הוסף איש קשר</string> <string name="send_failed">מסירה נכשלה</string> <string name="send_rejected">סורב</string> - <string name="preparing_image">כעת מכין תצלום לשם תמסורת</string> - <string name="action_clear_history">טהר היסטוריה</string> - <string name="clear_conversation_history">טהר היסטוריית דיונים</string> - <string name="clear_histor_msg">האם ברצונך למחוק את כל ההודעות בתוך דיון זה?\n\n<b>אזהרה:</b> זו לא תשפיע על הודעות מאוחסנות על מכשירים או שרתים אחרים.</string> + <string name="preparing_image">מכין תמונה לשליחה</string> + <string name="action_clear_history">נקה היסטוריה</string> + <string name="clear_conversation_history">נקה היסטוריית שיחה</string> + <string name="clear_histor_msg">האם ברצונך למחוק את כל ההודעות בשיחה זאת?\n\n<b>אזהרה:</b> פעולה זו לא תשפיע על הודעות מאוחסנות על מכשירים או שרתים אחרים.</string> <string name="delete_messages">מחק הודעות</string> - <string name="also_end_conversation">סיים את דיון זה לאחר מכן</string> + <string name="also_end_conversation">סיים את השיחה לאחר מכן</string> <string name="choose_presence">בחר נוכחות לאיש קשר</string> - <string name="send_plain_text_message">שלח הודעת טקסט גלוי</string> - <string name="send_otr_message">שלח הודעה מוצפנת OTR</string> - <string name="send_pgp_message">שלח הודעה מוצפנת OpenPGP</string> - <string name="your_nick_has_been_changed">שם כינוי שלך השתנה</string> - <string name="send_unencrypted">שלח לא מוצפנת</string> + <string name="send_unencrypted_message">שלח הודעה בלתי מוצפנת</string> + <string name="send_otr_message">שלח הודעה בהצפנת OTR</string> + <string name="send_omemo_message">של הודעה בהצפנת OMEMO</string> + <string name="send_pgp_message">שלח הודעה בהצפנת OpenPGP</string> + <string name="your_nick_has_been_changed">שם הכינוי שלך השתנה</string> + <string name="send_unencrypted">שלח ללא הצפנה</string> <string name="decryption_failed">פענוח נכשל. אולי אין לך את המפתח הפרטי המתאים.</string> <string name="openkeychain_required">OpenKeychain</string> - <string name="openkeychain_required_long">Conversations מפיקה תועלת מן אפליקציית צד-שלישי הקרויה <b>OpenKeychain</b> כדי להצפין ולפענח הודעות וגם כדי לנהל את המפתחות הפומביים שלך.\n\nOpenKeychain הינה רשויה תחת GPLv3 וזמינה אצל F-Droid וגם Google Play.\n\n<small>(אנא התחל מחדש את Conversations לאחר מכן.)</small></string> + <string name="openkeychain_required_long">Conversations מסתמכת על אפליקציית צד-שלישי הקרויה <b>OpenKeychain</b> כדי להצפין ולפענח הודעות וגם כדי לנהל את המפתחות הפומביים שלך.\n\nOpenKeychain הינה רשויה תחת GPLv3 וזמינה ב F-Droid וגם ב Google Play.\n\n<small>(אנא התחל מחדש את Conversations לאחר מכן.)</small></string> <string name="restart">התחל מחדש</string> <string name="install">התקן</string> - <string name="offering">כעת מציע…</string> - <string name="waiting">כעת ממתין…</string> + <string name="openkeychain_not_installed">אנא התקן OpenKeychain</string> + <string name="offering">מציע…</string> + <string name="waiting">ממתין…</string> <string name="no_pgp_key">לא נמצא מפתח OpenPGP</string> - <string name="contact_has_no_pgp_key">Conversations אינה מסוגלת להצפין את הודעותיך משום שאיש הקשר שלך אינו מכריז על המפתח הפומבי שלו או שלה.\n\n<small>אנא בקש מאיש הקשר שלך לארגן OpenPGP.</small></string> + <string name="contact_has_no_pgp_key">Conversations אינה מסוגלת להצפין את הודעותיך משום שאיש הקשר שלך אינו מכריז על המפתח הפומבי שלו או שלה.\n\n<small>אנא בקש מאיש הקשר שלך להגדיר את OpenPGP.</small></string> <string name="no_pgp_keys">לא נמצאו מפתחות OpenPGP</string> <string name="contacts_have_no_pgp_keys">Conversations אינה מסוגלת להצפין את הודעותיך משום שאנשי הקשר שלך אינם מכריזים על המפתח הפומבי שלהם.\n\n<small>אנא בקש מאנשי הקשר שלך לארגן OpenPGP.</small></string> - <string name="encrypted_message_received"><i>הודעה מוצפנת התקבלה. לחץ כדי לצפות ולפענח.</i></string> + <string name="encrypted_message_received"><i>הודעה מוצפנת תנקבלה. גע כדי לפענח צופן.</i></string> + <string name="pref_general">כללי</string> <string name="pref_xmpp_resource">משאב XMPP</string> - <string name="pref_xmpp_resource_summary">השם שלקוח זה מזהה את עצמו עם</string> + <string name="pref_xmpp_resource_summary">השם שבעזרתו לקוח זה מזהה את עצמו</string> <string name="pref_accept_files">קבל קבצים</string> - <string name="pref_accept_files_size_summary">קבל אוטומטית קבצים קטנים יותר מאשר…</string> - <string name="pref_notification_settings">הגדרות התראה</string> + <string name="pref_accept_files_summary">קבל אוטומטית קבצים שגודלם קטן מ…</string> + <string name="pref_notification_settings">הגדרות התראות</string> <string name="pref_notifications">התראות</string> <string name="pref_notifications_summary">תודיע כאשר הודעה חדשה מגיעה</string> <string name="pref_vibrate">הרטט</string> <string name="pref_vibrate_summary">הרטט גם כאשר הודעה חדשה מגיעה</string> <string name="pref_sound">צליל</string> - <string name="pref_sound_summary">נגן צלצול עם התראה</string> - <string name="pref_conference_notifications">התראות ועידה</string> - <string name="pref_conference_notifications_summary">תמיד תודיע כאשר הודעת ועידה חדשה מגיעה במקום רק כאשר מודגשת</string> - <string name="pref_notification_grace_period">משך ארכת התראה</string> - <string name="pref_notification_grace_period_summary">נטרל התראות לזמן קצר לאחר שהודעת פחם התקבלה</string> + <string name="pref_sound_summary">נגן צלצול עם כל התראה</string> + <string name="pref_notification_grace_period">משך תקופת ארכה</string> + <string name="pref_notification_grace_period_summary">נטרל התראות לזמן קצר לאחר שהודעת Carbon Copy מתקבלת</string> <string name="pref_advanced_options">אפשרויות מתקדמות</string> <string name="pref_never_send_crash">לעולם אל תשלח דיווחי קריסה</string> <string name="pref_never_send_crash_summary">על ידי שליחת עקבות מחסנית אתה עוזר להתקדמות הפיתוח של Conversations</string> <string name="pref_confirm_messages">אשר הודעות</string> <string name="pref_confirm_messages_summary">אפשר לאיש קשר שלך לדעת מתי קיבלת וקראת הודעה</string> <string name="pref_ui_options">אפשרויות ממשק משתמש</string> - <string name="openpgp_error">OpenKeychain דיווח שגיאה</string> + <string name="openpgp_error">אפליקציית OpenKeychain דיווחה על שגיאה</string> <string name="error_decrypting_file">שגיאת I/O פענוח קובץ</string> <string name="accept">קבל</string> <string name="error">אירעה שגיאה</string> @@ -113,33 +128,37 @@ <string name="receive_presence_updates">קבל עדכוני נוכחות</string> <string name="ask_for_presence_updates">בקש עדכוני נוכחות</string> <string name="attach_choose_picture">בחר תמונה</string> - <string name="attach_take_picture">קח תמונה</string> - <string name="preemptively_grant">הענק בקשת הרשמה מראש</string> - <string name="error_not_an_image_file">הקובץ שבחרת אינו תצלום</string> - <string name="error_compressing_image">שגיאה במהלך המרת קובץ תצלום</string> + <string name="attach_take_picture">צלם תמונה</string> + <string name="preemptively_grant">הענק מראש בקשת הרשמה</string> + <string name="error_not_an_image_file">הקובץ שבחרת אינו תמונה</string> + <string name="error_compressing_image">שגיאה במהלך המרת תמונה</string> <string name="error_file_not_found">קובץ לא נמצא</string> <string name="error_io_exception">שגיאת I/O כללית. אולי אזל לך נפח אחסון?</string> - <string name="error_security_exception_during_image_copy">האפליקציה בה השתמשת כדי לבחור את תצלום זה לא סיפקה לנו מספיק הרשאות כדי לקרוא את הקובץ.\n\n<small>השתמש במנהל קבצים אחר כדי לבחור תצלום</small></string> + <string name="error_security_exception_during_image_copy">האפליקציה בה השתמשת כדי לבחור תמונה זו לא סיפקה לנו מספיק הרשאות כדי לקרוא את הקובץ.\n\n<small>השתמש במנהל קבצים אחר כדי לבחור תצלום</small></string> <string name="account_status_unknown">לא ידוע</string> <string name="account_status_disabled">מנוטרל זמנית</string> <string name="account_status_online">מקוון</string> - <string name="account_status_connecting">כעת מתחבר\u2026</string> + <string name="account_status_connecting">מתחבר\u2026</string> <string name="account_status_offline">לא מקוון</string> <string name="account_status_unauthorized">לא מורשה</string> <string name="account_status_not_found">שרת לא נמצא</string> <string name="account_status_no_internet">אין חיבוריות</string> <string name="account_status_regis_fail">הרשמה נכשלה</string> - <string name="account_status_regis_conflict">שם משתמש כבר מצוי בשימוש</string> + <string name="account_status_regis_conflict">שם משתמש כבר בשימוש</string> <string name="account_status_regis_success">הרשמה הושלמה</string> - <string name="account_status_regis_not_sup">שרת לא תומך הרשמה</string> - <string name="encryption_choice_none">טקסט גלוי</string> + <string name="account_status_regis_not_sup">השרת לא תומך בהרשמת משתמשים חדשים</string> + <string name="account_status_security_error">שגיאת אבטחה</string> + <string name="account_status_incompatible_server">שרת לא מתאים</string> + <string name="encryption_choice_unencrypted">לא מוצפן</string> <string name="encryption_choice_otr">OTR</string> <string name="encryption_choice_pgp">OpenPGP</string> + <string name="encryption_choice_omemo">OMEMO</string> <string name="mgmt_account_edit">ערוך חשבון</string> <string name="mgmt_account_delete">מחק</string> <string name="mgmt_account_disable">נטרל זמנית</string> - <string name="mgmt_account_publish_avatar">פרסם אווטאר</string> - <string name="mgmt_account_enable">אפשר</string> + <string name="mgmt_account_publish_avatar">פרסם תמונת פרופיל</string> + <string name="mgmt_account_publish_pgp">פרסם מפתח ציבורי של OpenPGP</string> + <string name="mgmt_account_enable">הפעל חשבון</string> <string name="mgmt_account_are_you_sure">האם אתה בטוח?</string> <string name="mgmt_account_delete_confirm_text">אם אתה מוחק את חשבונך כל היסטוריית הדיון שלך תאבד</string> <string name="attach_record_voice">הקלט קול</string> @@ -150,34 +169,48 @@ <string name="password">סיסמה</string> <string name="confirm_password">אמת סיסמה</string> <string name="passwords_do_not_match">סיסמאות לא תואמות</string> - <string name="invalid_jid">זה אינו מזהה Jabber תקף</string> + <string name="invalid_jid">מזהה ה Jabber אינו תקין</string> <string name="error_out_of_memory">חסר זיכרון. תצלום גדול מדי</string> - <string name="add_phone_book_text">האם ברצונך להוסיף את %s אל רשימת קשר טלפונית?</string> <string name="contact_status_online">מקוון</string> <string name="contact_status_free_to_chat">חופשי לשיחה</string> <string name="contact_status_away">נעדר</string> - <string name="contact_status_extended_away">נעדר לזמן מה</string> - <string name="contact_status_do_not_disturb">אל תפריעו</string> + <string name="contact_status_extended_away">נעדר לזמן ממושך</string> + <string name="contact_status_do_not_disturb">נא לא להפריע</string> <string name="contact_status_offline">לא מקוון</string> <string name="muc_details_conference">ועידה</string> <string name="muc_details_other_members">חברים אחרים</string> - <string name="server_info_carbon_messages">הודעות פחם</string> - <string name="server_info_stream_management">ניהול זרם</string> + <string name="server_info_show_more">פרטי השרת</string> + <string name="server_info_mam">XEP-0313: MAM - היסטוריית שרת</string> + <string name="server_info_carbon_messages">XEP-0280: Message Carbons</string> + <string name="server_info_csi">XEP-0352: Client State Indication</string> + <string name="server_info_blocking">XEP-0191: Blocking Command - חסימת אנשי קשר</string> + <string name="server_info_roster_version">XEP-0237: Roster Versioning</string> + <string name="server_info_stream_management">XEP-0198: Stream Management</string> + <string name="server_info_pep">XEP-0163: PEP (Avatars / OMEMO) - תמונת פרופיל והצפנת OMEMO</string> + <string name="server_info_http_upload">XEP-0363: HTTP File Upload - שליחת קבצים דרך HTTP</string> + <string name="server_info_available">זמין</string> + <string name="server_info_unavailable">לא זמין</string> <string name="missing_public_keys">הכרזות מפתח פומבי חסרות</string> <string name="last_seen_now">נראה לאחרונה ממש עכשיו</string> - <string name="last_seen_min">נראה לאחרונה לפני דקה 1</string> + <string name="last_seen_min">נראה לאחרונה לפני דקה</string> <string name="last_seen_mins">נראה לאחרונה לפני %d דקות</string> - <string name="last_seen_hour">נראה לאחרונה לפני שעה 1</string> - <string name="last_seen_hours">נראה לאחרונה לפני %d שעות ago</string> - <string name="last_seen_day">נראה לאחרונה לפני יום 1</string> + <string name="last_seen_hour">נראה לאחרונה לפני שעה</string> + <string name="last_seen_hours">נראה לאחרונה לפני %d שעות</string> + <string name="last_seen_day">נראה לאחרונה לפני יום אחד</string> <string name="last_seen_days">נראה לאחרונה לפני %d ימים</string> <string name="never_seen">לא נראה מעולם</string> - <string name="install_openkeychain">הודעה מוצפנת. אנא התקן OpenKeychain כדי לפענח.</string> + <string name="install_openkeychain">הודעה מוצפנת. אנא התקן את OpenKeychain כדי לפענח.</string> <string name="unknown_otr_fingerprint">טביעת אצבע OTR לא מוכרת</string> <string name="openpgp_messages_found">הודעות מוצפנות OpenPGP נמצאו</string> <string name="reception_failed">קבלה נכשלה</string> <string name="your_fingerprint">טביעת אצבע שלך</string> - <string name="otr_fingerprint">טביעת אצבע OTR</string> + <string name="otr_fingerprint">טביעת אצבע של OTR</string> + <string name="omemo_fingerprint">טביעת אצבע של OMEMO</string> + <string name="omemo_fingerprint_selected_message">טביעת אצבע OMEMO של ההודעה</string> + <string name="this_device_omemo_fingerprint">טביעת אצבע OMEMO שלי</string> + <string name="other_devices">מכשירים אחרים</string> + <string name="trust_omemo_fingerprints">סמוך על טביעות אצבע OMEMO</string> + <string name="done">בוצע</string> <string name="verify">אמת</string> <string name="decrypt">פענח</string> <string name="conferences">ועידות</string> @@ -186,6 +219,8 @@ <string name="join_conference">הצטרף לועידה</string> <string name="delete_contact">מחק איש קשר</string> <string name="view_contact_details">צפה בפרטי איש קשר</string> + <string name="block_contact">חסום איש קשר</string> + <string name="unblock_contact">בטל חסימת איש קשר</string> <string name="create">צור</string> <string name="contact_already_exists">איש קשר כבר קיים</string> <string name="join">הצטרף</string> @@ -201,15 +236,276 @@ <string name="contact_added_you">איש קשר הוסיף אותך אל רשימת קשר</string> <string name="add_back">הוסף בחזרה</string> <string name="contact_has_read_up_to_this_point">%s קרא עד לנקודה זו</string> - <string name="touch_to_choose_picture">לחץ על אווטאר כדי לבחור תמונה מתוך גלריה</string> - <string name="publish_avatar_explanation">לתשומת לבך: כל מי אשר רשום לעדכוני נוכחות שלך יורשה לראות את תמונה זו.</string> - <string name="publishing">כעת מפרסם…</string> - <string name="error_publish_avatar_server_reject">השרת פסל פרסום</string> - <string name="error_publish_avatar_converting">משהו השתבש במהלך המרת תמונה</string> - <string name="error_saving_avatar">לא היה מסוגל לשמור אווטאר אל כונן</string> - <string name="or_long_press_for_default">(או לחיצה ארוכה כדי להחזיר לשגרה)</string> - <string name="error_publish_avatar_no_server_support">שרתך לא תומך בפרסום של אווטארים</string> + <string name="publish">פרסם</string> + <string name="touch_to_choose_picture">לחץ על תמות הפרופיל כדי לבחור תמונה מתוך הגלריה</string> + <string name="publish_avatar_explanation">לתשומת לבך: כל מי שרשום לעדכוני נוכחות שלך יורשה לראות את תמונה זו.</string> + <string name="publishing">מעלה…</string> + <string name="error_publish_avatar_server_reject">השרת דחה את ההעלאה</string> + <string name="error_publish_avatar_converting">משהו השתבש במהלך המרת התמונה</string> + <string name="error_saving_avatar">שגיאה בעת שמירת תמונה לזיכרון</string> + <string name="or_long_press_for_default">(או לחיצה ארוכה כדי להחזיר לברירת מחדל)</string> + <string name="error_publish_avatar_no_server_support">השרת לא תומך בפרסום של אווטארים</string> <string name="private_message">בפרטי</string> <string name="private_message_to">בפרטי אל %s</string> <string name="send_private_message_to">שלח הודעה פרטית אל %s</string> + <string name="connect">התחבר</string> + <string name="account_already_exists">חשבון זה כבר קיים</string> + <string name="next">הבא</string> + <string name="additional_information">מידע נוסף</string> + <string name="skip">דלג</string> + <string name="disable_notifications">השבת התראות</string> + <string name="disable_notifications_for_this_conversation">השבת התראות עבור השיחה הנוכחית</string> + <string name="enable">הפעל</string> + <string name="conference_requires_password">ועידה זאת דורשת סיסמא</string> + <string name="enter_password">הכנס סיסמא</string> + <string name="missing_presence_updates">עדכוני נוסחות חסרים מאיש הקשר</string> + <string name="request_presence_updates">נא לבקש עדכוני נוכחות מאיש הקשר קודם לכך<small>פעולה זו תשמש כדי לקבוע את אקפליקציה שאיש הקשר משתמש בה</small></string> + <string name="request_now">בקש/י כעת</string> + <string name="delete_fingerprint">מחק טביעת אצבע</string> + <string name="sure_delete_fingerprint">האם את/ה בטוח שברצונך למחוק טביעת אצבע זו?</string> + <string name="ignore">התעלם</string> + <string name="without_mutual_presence_updates"><b>אזהרה:</b>שליחה ללא הרשאות עדכוני נוכחות הדדיות עלולה לגרום לתוצאות בלתי צפויות.\n\n<small>השתמש בתפריט \"פרטי משתמש\" ואשר עדכוני נוכחות</small></string> + <string name="pref_encryption_settings">הגדרות הצפנה</string> + <string name="pref_force_encryption">אלץ הצפנת end-to-end</string> + <string name="pref_force_encryption_summary">תמיד שלח הודעות מוצפנות (חוץ מבועידות)</string> + <string name="pref_dont_save_encrypted">אל תשמור הודעות מוצפנות</string> + <string name="pref_dont_save_encrypted_summary">אזהרה: פעולה זו עלולה לגרום לאיבוד הודעות</string> + <string name="pref_expert_options">הגדרות מתקדמות</string> + <string name="pref_expert_options_summary">נא להיזהר!</string> + <string name="title_activity_about">אודות Conversations</string> + <string name="pref_about_conversations_summary">אודות גרסה ורישיון </string> + <string name="title_pref_quiet_hours">שעות שקטות</string> + <string name="title_pref_quiet_hours_start_time">זמן התחלה</string> + <string name="title_pref_quiet_hours_end_time">זמן סיום</string> + <string name="title_pref_enable_quiet_hours">הפעל \"שעות שקטות\"</string> + <string name="pref_quiet_hours_summary">ההתראות יושבתו במהלך שעות שקטות</string> + <string name="pref_use_larger_font">הגדל גופן</string> + <string name="pref_use_larger_font_summary">הגדל גופן בכל האפליקציה</string> + <string name="pref_use_send_button_to_indicate_status">לחצן השליחה מעיד על הסטטוס</string> + <string name="pref_use_indicate_received">בקש אימות הגעת הודעות</string> + <string name="pref_use_indicate_received_summary">יופיע סימן \"וי\" ליד כל הודעה שהתקבלה על ידי איש הקשר, במידה והפעולה נתמכת על ידי איש הקשר.</string> + <string name="pref_use_send_button_to_indicate_status_summary">צבעו של לחצן שליחת ההודעות יעיד על סטטוס איש הקשר.</string> + <string name="pref_expert_options_other">אחר</string> + <string name="pref_conference_name">שם ועידה</string> + <string name="pref_conference_name_summary">השתמש בכותרת הועידה ולא ב- JID כשם לועידה</string> + <string name="toast_message_otr_fingerprint">טביעת אצבע של OTR הועתקה</string> + <string name="toast_message_omemo_fingerprint">טביעת אצבע של OMEMO הועתקה</string> + <string name="conference_banned">אתה חסום מלהיכנס לועידה זו</string> + <string name="conference_members_only">ועידה זו אינה ציבורית</string> + <string name="conference_kicked">גורשת מועידה זו</string> + <string name="using_account">משתמש בחשבון: %s</string> + <string name="not_connected_try_again">אינך מחובר. נסה שוב אחר כך</string> + <string name="check_x_filesize">בדוק גודל %s</string> + <string name="message_options">הגדרות הודעה</string> + <string name="copy_text">העתק טקסט</string> + <string name="copy_original_url">העתק קישור</string> + <string name="send_again">שלח שוב</string> + <string name="file_url">קישור קובץ</string> + <string name="message_text">טקסט הודעה</string> + <string name="url_copied_to_clipboard">הקישור הועתק</string> + <string name="message_copied_to_clipboard">ההודעה הועתקה</string> + <string name="image_transmission_failed">שליחת התמונה נכשלה</string> + <string name="scan_qr_code">סרוק ברקוד QR</string> + <string name="show_qr_code">הראה ברקוד QR</string> + <string name="show_block_list">הראה רשימת חסומים</string> + <string name="account_details">פרטי חשבון</string> + <string name="verify_otr">אמת OTR</string> + <string name="remote_fingerprint">טביעת אצבע מרוחקת</string> + <string name="scan">סרוק</string> + <string name="smp">Socialist Millionaire Protocol</string> + <string name="shared_secret_hint">שאלה או רמז</string> + <string name="shared_secret_secret">סוד משותף</string> + <string name="confirm">אמת</string> + <string name="in_progress">הפעולה מתבצעת</string> + <string name="respond">השב</string> + <string name="failed">נכשל</string> + <string name="secrets_do_not_match">הסודות אינם תואמים</string> + <string name="try_again">נסה שוב</string> + <string name="finish">סיים</string> + <string name="verified">אומת בהצלחה!</string> + <string name="smp_requested">איש הקשר ביקש אימות SMP</string> + <string name="no_otr_session_found">לא נמצא OTR Session תקין</string> + <string name="pref_keep_foreground_service">השאר שירות ב Foreground</string> + <string name="pref_keep_foreground_service_summary">מונע ממערכת ההפעלה לנתק את החיבור לשרת</string> + <string name="pref_export_logs">ייצא Logs</string> + <string name="pref_export_logs_summary">יצוא logs לכרטיס הזיכרון</string> + <string name="notification_export_logs_title">מייצא logs לכרטיס זיכרון</string> + <string name="choose_file">בחר קובץ</string> + <string name="receiving_x_file">מקבל %1$s ( הושלם %2$d%% )</string> + <string name="download_x_file">הורד %s</string> + <string name="file">קובץ</string> + <string name="open_x_file">פתח %s</string> + <string name="sending_file">שולח ( %1$d%% הושלם )</string> + <string name="preparing_file">מכין קובץ לשליחה</string> + <string name="x_file_offered_for_download">הקובץ %s הוצע לאיש הקשר</string> + <string name="cancel_transmission">בטל שליחה</string> + <string name="file_transmission_failed">השליחה נכשלה</string> + <string name="file_deleted">הקובץ נמחק</string> + <string name="no_application_found_to_open_file">אין אפליקציה מתאימה לפתיחת הקובץ</string> + <string name="could_not_verify_fingerprint">אימות טביעת האצבע נכשל</string> + <string name="manually_verify">אימות ידני</string> + <string name="are_you_sure_verify_fingerprint">האם אתה בטוח שברצונך לאמת טביעת אצבע OTR זו?</string> + <string name="pref_show_dynamic_tags">הראה תגים דומיים</string> + <string name="pref_show_dynamic_tags_summary">הראה תגי read-only מתחת לאנשי הקשר</string> + <string name="enable_notifications">אפשר התראות</string> + <string name="conference_with">צור ועידה עם...</string> + <string name="no_conference_server_found">לא נמצא שרת ועידות</string> + <string name="conference_creation_failed">יצירת הועידה נכשלה!</string> + <string name="conference_created">הועידה נוצרה!</string> + <string name="secret_accepted">הסוד התקבל!</string> + <string name="reset">איפוס</string> + <string name="account_image_description">תמונת פרופיל</string> + <string name="copy_otr_clipboard_description">העתק טביעת אצבע OTR</string> + <string name="copy_omemo_clipboard_description">העתק טביעת אצבע OMEMO</string> + <string name="regenerate_omemo_key">צור מפתח OMEMO חדש</string> + <string name="wipe_omemo_pep">מחק מכשרים אחרים מ- PEP</string> + <string name="clear_other_devices">נקה מכשירים</string> + <string name="clear_other_devices_desc">האם אתה בטוח שברצונך לנקות את כל המכשירים מהכרזת ה OMEMO? בפעם הבאה שהמכשירים יתחברו, הם יכריזו שוב על עצמם, אך הם עלולים לאבד הודעות עד להכרזה זו.</string> + <string name="purge_key">מחק מפתח לצמיתות</string> + <string name="purge_key_desc_part1">האם אתה בטוח שברצונך למחוק מפתח זה לצמיתות?</string> + <string name="purge_key_desc_part2">הוא ייחשב ללא תקין לצמיתות, ולעולם לא תצליח להשתמש בו שוב.</string> + <string name="error_no_keys_to_trust">אין מפתחות שימושיים עבור איש קשר זה. אם מחקת אחד מהמפתחות שלהם, יהיה עליהם ליצר מפתח חדש.</string> + <string name="error_trustkeys_title">שגיאה</string> + <string name="fetching_history_from_server">הורדת היסטוריה מהשרת</string> + <string name="no_more_history_on_server">אין עוד היסטוריה בשרת</string> + <string name="updating">מעדכן...</string> + <string name="password_changed">הסיסמה שונתה!</string> + <string name="could_not_change_password">שינוי הסיסמה נכשל</string> + <string name="otr_session_not_started">שלח הודעה בכדי להתחיל בהצפנה</string> + <string name="ask_question">שאל שאלה</string> + <string name="smp_explain_question">במידה ויש לך ולאיש הקשר שלך סוד שרק שניכם יודעים (כמו למשל בדיחה פנימית, או מה אכלתם בפעם האחרונה שנפגשתם), ניתן להשתמש בסוד הנ\"ל על מנת לאמת את מפתחות ההצפנה של אחד את השני.\n\nאיש קשר יספק שאלה או רמז, ואיש הקשר השני יספק תשובה. המלל הוא case-sensitive.</string> + <string name="smp_explain_answer">איש הקשר רוצה לאמת את הזהות שלך על ידי סוד משותף. איש הקשר סיפק את הרמז הבא, עליך להזין סוד משותף.</string> + <string name="shared_secret_hint_should_not_be_empty">הרמז לא יכול להיות ריק</string> + <string name="shared_secret_can_not_be_empty">הסוד המשותף לא יכול להיות ריק</string> + <string name="manual_verification_explanation">יש להשוות בזהירות את הטביעת אצבע שמוצגת פה לבין זאת שמוצגת אצל החבר שלך.\nניתן לעשות זאת על ידי שימוש בכל צורת תקשורת שאתה סומך עליה, כמו שיחת טלפון או אימייל מוצפן.</string> + <string name="change_password">שינוי סיסמה</string> + <string name="current_password">סיסמה נוכחית</string> + <string name="new_password">סיסמה חדשה</string> + <string name="password_should_not_be_empty">הסיסמה לא יכולה להיות ריקה</string> + <string name="enable_all_accounts">הפעל את כל החשבונות</string> + <string name="disable_all_accounts">נטרל את כל החשבונות</string> + <string name="perform_action_with">בצע פעולה באמצעות</string> + <string name="no_affiliation">אין שיוך</string> + <string name="no_role">אין תפקיד</string> + <string name="member">חבר בקבוצה</string> + <string name="advanced_mode">מצב מתקדם</string> + <string name="grant_membership">הענק חברות בקבוצה</string> + <string name="remove_membership">בטל חברות בקבוצה</string> + <string name="grant_admin_privileges">הענק הרשאות מנהל</string> + <string name="remove_admin_privileges">שלול הרשאות מנהל</string> + <string name="remove_from_room">סלק מהועידה</string> + <string name="could_not_change_affiliation">לא ניתן לשנות את השיוך של %s</string> + <string name="ban_from_conference">סלק וחסום כניסה לועידה</string> + <string name="removing_from_public_conference">הינך מנסה למחוק את %s מועידה ציבורית. הדרך היחידה לעשות זאת היא חסימה לצמיתות.</string> + <string name="ban_now">חסום עכשיו</string> + <string name="could_not_change_role">לא ניתן לשנות את התפקיד של %s</string> + <string name="public_conference">ועידה ציבורית</string> + <string name="private_conference">ועידה פרטית, לחברים בלבד</string> + <string name="conference_options">הגדרות ועידה</string> + <string name="members_only">פרטי, חברים בלבד</string> + <string name="non_anonymous">לא-אנונימי</string> + <string name="moderated">Moderated</string> + <string name="you_are_not_participating">אינך משתתף</string> + <string name="modified_conference_options">הגדרות הועידה השתנו בצלחה!</string> + <string name="could_not_modify_conference_options">שינוי הגדרות הועידה נכשל</string> + <string name="never">לעולם לא</string> + <string name="thirty_minutes">30 דקות</string> + <string name="one_hour">שעה אחת</string> + <string name="two_hours">2 שעות</string> + <string name="eight_hours">8 שעות</string> + <string name="until_further_notice">עד אחרית הימים</string> + <string name="pref_input_options">הגדרות קלט</string> + <string name="pref_enter_is_send">לחצן Enter שולח את ההודעה</string> + <string name="pref_enter_is_send_summary">השתמש בלחצן ה-Enter כלחצן השליחה</string> + <string name="pref_display_enter_key">הראה את לחצן ה Enter</string> + <string name="pref_display_enter_key_summary">שנה את לחצן האימוג\'י ללחצן Enter</string> + <string name="audio">קול</string> + <string name="video">סרטון</string> + <string name="image">תמונה</string> + <string name="pdf_document">מסמך PDF</string> + <string name="apk">אפליקציית אנדרויד</string> + <string name="vcard">איש קשר</string> + <string name="received_x_file">התקבל %s</string> + <string name="disable_foreground_service">בטל שירות Foreground</string> + <string name="touch_to_open_conversations">לחץ כדי לפתוח את Conversations</string> + <string name="avatar_has_been_published">תמונת הפרופיל פורסמה!</string> + <string name="sending_x_file">שולח %s</string> + <string name="offering_x_file">מציע %s</string> + <string name="hide_offline">הסתר בלתי מקוונים</string> + <string name="disable_account">השבת חשבון</string> + <string name="contact_is_typing">%s מקליד/ה כעת…</string> + <string name="contact_has_stopped_typing">%s הפסיק/ה להקליד</string> + <string name="pref_chat_states">התראות הקלדה</string> + <string name="pref_chat_states_summary">אפשר לאנשי הקשר שלך לדעת כאשר אתה מקליד הודעה חדשה</string> + <string name="send_location">שלח מיקום</string> + <string name="show_location">הראה מיקום</string> + <string name="no_application_found_to_display_location">לא נמצאה אפליקציית המראה את המיקום</string> + <string name="location">מיקום</string> + <string name="received_location">מיקום שהתקבל</string> + <string name="title_undo_swipe_out_conversation">השיחה נסגרה</string> + <string name="title_undo_swipe_out_muc">עזבת את הועידה</string> + <string name="pref_dont_trust_system_cas_title">אל תסמוך על ה- CAs של המערכת</string> + <string name="pref_dont_trust_system_cas_summary">כל החתימות הדיגטליות יצטרכו לעבור אימות ידני</string> + <string name="pref_remove_trusted_certificates_title">מחק חתימות דיגטליות</string> + <string name="pref_remove_trusted_certificates_summary">מחק חתימות דיגטליות שאומתו באופן ידני</string> + <string name="toast_no_trusted_certs">אין חתימות דיגטליות שאושרו ידנית</string> + <string name="dialog_manage_certs_title">מחק חתימות דיגטליות</string> + <string name="dialog_manage_certs_positivebutton">מחק פריטים שנבחרו</string> + <string name="dialog_manage_certs_negativebutton">ביטול</string> + <plurals name="toast_delete_certificates"> + <item quantity="one">%d חתימה נמחקה</item> + <item quantity="other">%d חתימות נמחקו</item> + </plurals> + <plurals name="select_contact"> + <item quantity="one">בחר איש קשר %d</item> + <item quantity="other">בחר %d אנשי קשר</item> + </plurals> + <string name="pref_quick_action_summary">החלף לחצן שליחה בפעולה מהירה</string> + <string name="pref_quick_action">פעולה מהירה</string> + <string name="none">כלום</string> + <string name="recently_used">לפי השימוש האחרון</string> + <string name="choose_quick_action">בחר פעולה מהירה</string> + <string name="search_for_contacts_or_groups">חפש אנשי קשר או קבוצות</string> + <string name="send_private_message">שלח הודעה פרטית</string> + <string name="user_has_left_conference">%s עזב את הועידה</string> + <string name="username">שם משתמש</string> + <string name="username_hint">שם משתמש</string> + <string name="invalid_username">שם משתמש זה אינו חוקי</string> + <string name="download_failed_server_not_found">ההורדה נכשלה: שרת לא נמצא</string> + <string name="download_failed_file_not_found">ההורדה נכשלה: הקובץ לא נמצא</string> + <string name="download_failed_could_not_connect">ההורדה נכשלה: נכשל ביצוע חיבור לשרת</string> + <string name="pref_use_white_background">השתמש ברקע לבן</string> + <string name="pref_use_white_background_summary">הראה הודעות שהתקבלו בטקסט שחור על גבי רקע לבען</string> + <string name="server_info_broken">לא עובד</string> + <string name="pref_presence_settings">הגדרות נוכחות Presence</string> + <string name="pref_away_when_screen_off">העבר למצב \"לא נמצא\" כאשר המסך כבוי</string> + <string name="pref_away_when_screen_off_summary">מעביר את המכשיר לסטטוס \"לא נמצא\" כאשר המסך כבוי</string> + <string name="pref_xa_on_silent_mode">העבר למצב \"לא זמין\" כאשר במצב שקט</string> + <string name="action_add_account_with_certificate">הוסף חשבון עם תעודה</string> + <string name="unable_to_parse_certificate">תקלה בפענוח תעודה</string> + <string name="authenticate_with_certificate">השאר ריק כדי להזדהות בלי תעודה</string> + <string name="captcha_ocr">טקסט Captcha</string> + <string name="captcha_required">דרוש טקסט Captcha</string> + <string name="captcha_hint">אנא הזן את הטקסט מתוך התמונה</string> + <string name="certificate_chain_is_not_trusted">שרשרת תעודה אינה מהימנה</string> + <string name="jid_does_not_match_certificate">אין התאמה בין מזהה Jabber לבין תעודה</string> + <string name="action_renew_certificate">חידוש תעודה</string> + <string name="error_fetching_omemo_key">שגיאה בתפיסת OMEMO!</string> + <string name="pref_connection_options">אפשרוית חיבור</string> + <string name="pref_use_tor">התחבר דרך Tor</string> + <string name="account_settings_hostname">שם מארח</string> + <string name="account_settings_port">פורט</string> + <string name="hostname_or_onion">שרת- או כתובת onion.</string> + <string name="not_a_valid_port">זהו אינו מספר פורט תקין</string> + <string name="not_valid_hostname">זהו אינו שם מארח תקין</string> + <string name="connected_accounts">%1$d מתוך %2$d חשבונות מחוברים</string> + <plurals name="x_messages"> + <item quantity="one">הודעה %d</item> + <item quantity="other">%d הודעות</item> + </plurals> + <string name="shared_file_with_x">שתף קובץ בעזרת %s</string> + <string name="shared_image_with_x">שתף תמונה בעזרת %s</string> + <string name="no_storage_permission">Conversations זקוקה לגישה לאחסון חיצוני</string> + <string name="sync_with_contacts">סנכרן עם אנשי קשר</string> + <string name="sync_with_contacts_long">Conversations מבקשת להתאים את רשימת XMPP שלך עם אנשי הקשר שלך כדי להציג את השמות המלאים שלהם כולל אווטארים.\n\nConversations רק תקרא את אנשי הקשר שלך ותתאים אותם באופן מקומי מבלי להעלות אותם לשרת שלך.\n\nכעת אתה תתבקש להעניק הרשאה כדי לגשת להתקן שלך.</string> </resources> diff --git a/src/main/res/values-ja/strings.xml b/src/main/res/values-ja/strings.xml index 35347734..9c8b4fed 100644 --- a/src/main/res/values-ja/strings.xml +++ b/src/main/res/values-ja/strings.xml @@ -9,7 +9,7 @@ <string name="action_secure">安全に会話</string> <string name="action_add_account">アカウントを追加</string> <string name="action_edit_contact">名前の編集</string> - <string name="action_add_phone_book">電話帳に追加</string> + <string name="action_add_phone_book">アドレス帳に追加</string> <string name="action_delete_contact">名簿から削除</string> <string name="action_block_contact">連絡先をブロック</string> <string name="action_unblock_contact">連絡先のブロックを解除</string> @@ -28,7 +28,8 @@ <string name="minutes_ago">%d 分前</string> <string name="unread_conversations">未読の会話</string> <string name="sending">送信中…</string> - <string name="encrypted_message">メッセージを復号しています。しばらくお待ちください…</string> + <string name="message_decrypting">メッセージを復号化しています。しばらくお待ちください…</string> + <string name="pgp_message">OpenPGP 暗号化メッセージ</string> <string name="nick_in_use">ニックネームは既に使用されています</string> <string name="admin">管理者</string> <string name="owner">オーナー</string> @@ -76,8 +77,10 @@ <string name="delete_messages">メッセージを削除</string> <string name="also_end_conversation">その後、この会話を終了</string> <string name="choose_presence">連絡する参加を選択</string> - <string name="send_plain_text_message">プレーンテキストを送信</string> + <string name="send_unencrypted_message">暗号化されていないメッセージを送信</string> <string name="send_otr_message">OTR 暗号化メッセージを送信</string> + <string name="send_omemo_message">OMEMO 暗号化メッセージを送信</string> + <string name="send_omemo_x509_message">v\\OMEMO 暗号化メッセージを送信</string> <string name="send_pgp_message">OpenPGP 暗号化メッセージを送信</string> <string name="your_nick_has_been_changed">あなたのニックネームが変更されました</string> <string name="send_unencrypted">暗号化されていない送信</string> @@ -86,13 +89,14 @@ <string name="openkeychain_required_long">Conversations は <b>OpenKeychain</b> と呼ばれるサードパーティのアプリを利用して、メッセージの暗号化および復号化、そしてあなたの公開鍵を管理します。\n\nOpenKeychain は GPLv3 ライセンスの下で、F-Droid および Google Play から利用可能です。\n\n<small>(後で Conversations を再起動してください。)</small></string> <string name="restart">再起動</string> <string name="install">インストール</string> + <string name="openkeychain_not_installed">OpenKeychain をインストールしてください</string> <string name="offering">依頼中…</string> <string name="waiting">待機中…</string> <string name="no_pgp_key">OpenPGP の鍵はありません</string> <string name="contact_has_no_pgp_key">連絡先が公開鍵を通知しないため、Conversations はあなたのメッセージを暗号化することができません。\n\n<small>連絡先に OpenPGP をセットアップするように依頼してください。</small></string> <string name="no_pgp_keys">OpenPGP の鍵はありません</string> <string name="contacts_have_no_pgp_keys">連絡先が公開鍵を通知しないため、Conversations はあなたのメッセージを暗号化することができません。\n\n<small>連絡先に OpenPGP をセットアップするように依頼してください。</small></string> - <string name="encrypted_message_received"><i>暗号化されたメッセージを受信しました。タッチすると、表示および復号化します。</i></string> + <string name="encrypted_message_received"><i>暗号化されたメッセージを受信しました。タッチすると復号化します。</i></string> <string name="pref_general">全般</string> <string name="pref_xmpp_resource">XMPP リソース</string> <string name="pref_xmpp_resource_summary">自分自身を識別するこのクライアントの名前</string> @@ -105,8 +109,6 @@ <string name="pref_vibrate_summary">新しいメッセージが到着したときに振動もします</string> <string name="pref_sound">サウンド</string> <string name="pref_sound_summary">通知で着信音を再生します</string> - <string name="pref_conference_notifications">会議通知</string> - <string name="pref_conference_notifications_summary">新しい会議メッセージが到着したとき、ハイライト表示ではなく、常に通知します</string> <string name="pref_notification_grace_period">通知猶予期間</string> <string name="pref_notification_grace_period_summary">カーボンコピーを受信した後、短時間、通知を無効にします</string> <string name="pref_advanced_options">詳細オプション</string> @@ -149,9 +151,10 @@ <string name="account_status_regis_not_sup">サーバーが登録をサポートしていません</string> <string name="account_status_security_error">セキュリティ エラー</string> <string name="account_status_incompatible_server">互換性のないサーバー</string> - <string name="encryption_choice_none">プレーンテキスト</string> + <string name="encryption_choice_unencrypted">暗号化されていない</string> <string name="encryption_choice_otr">OTR</string> <string name="encryption_choice_pgp">OpenPGP</string> + <string name="encryption_choice_omemo">OMEMO</string> <string name="mgmt_account_edit">アカウントの編集</string> <string name="mgmt_account_delete">アカウントを削除</string> <string name="mgmt_account_disable">一時的に無効にする</string> @@ -170,7 +173,7 @@ <string name="passwords_do_not_match">パスワードが一致しません</string> <string name="invalid_jid">これは有効な Jabber ID ではありません</string> <string name="error_out_of_memory">メモリ不足です。画像が大きすぎます</string> - <string name="add_phone_book_text">電話の連絡先リストに %s を追加しますか?</string> + <string name="add_phone_book_text">%s をお使いのアドレス帳に追加しますか?</string> <string name="contact_status_online">オンライン</string> <string name="contact_status_free_to_chat">自由にチャットできます</string> <string name="contact_status_away">離席中</string> @@ -186,7 +189,8 @@ <string name="server_info_blocking">XEP-0191: ブロッキング コマンド</string> <string name="server_info_roster_version">XEP-0237: 名簿バージョニング</string> <string name="server_info_stream_management">XEP-0198: ストリーム管理</string> - <string name="server_info_pep">XEP-0163: 個人イベントプロトコル (アバター)</string> + <string name="server_info_pep">XEP-0163: PEP (アバター / OMEMO)</string> + <string name="server_info_http_upload">XEP-0363: HTTP ファイルアップロード</string> <string name="server_info_available">利用可能</string> <string name="server_info_unavailable">利用不可</string> <string name="missing_public_keys">公開鍵の通知がありません</string> @@ -204,17 +208,28 @@ <string name="reception_failed">受信に失敗しました</string> <string name="your_fingerprint">あなたのフィンガープリント</string> <string name="otr_fingerprint">OTR フィンガープリント</string> + <string name="omemo_fingerprint">OMEMO フィンガープリント</string> + <string name="omemo_fingerprint_x509">v\\OMEMO フィンガープリント</string> + <string name="omemo_fingerprint_selected_message">メッセージの OMEMO フィンガープリント</string> + <string name="omemo_fingerprint_x509_selected_message">メッセージの v\\OMEMO フィンガープリント</string> + <string name="this_device_omemo_fingerprint">自分の OMEMO フィンガープリント</string> + <string name="other_devices">他のデバイス</string> + <string name="trust_omemo_fingerprints">OMEMO フィンガープリントを信頼</string> + <string name="fetching_keys">鍵の取得中…</string> + <string name="done">完了</string> <string name="verify">検証</string> <string name="decrypt">復号化</string> <string name="conferences">会議</string> <string name="search">検索</string> <string name="create_contact">連絡先を作成</string> + <string name="enter_contact">連絡先を入力</string> <string name="join_conference">会議に参加</string> <string name="delete_contact">連絡先を削除</string> <string name="view_contact_details">連絡先の詳細を表示</string> <string name="block_contact">連絡先をブロック</string> <string name="unblock_contact">連絡先のブロックを解除</string> <string name="create">作成</string> + <string name="select">選択</string> <string name="contact_already_exists">連絡先はすでに存在します</string> <string name="join">参加</string> <string name="conference_address">会議アドレス</string> @@ -249,7 +264,6 @@ <string name="skip">スキップ</string> <string name="disable_notifications">通知を無効にする</string> <string name="disable_notifications_for_this_conversation">この会話の通知を無効にします</string> - <string name="notifications_disabled">通知を無効にしました</string> <string name="enable">有効</string> <string name="conference_requires_password">会議はパスワードが必要です</string> <string name="enter_password">パスワードを入力してください</string> @@ -284,6 +298,7 @@ <string name="pref_conference_name">会議名</string> <string name="pref_conference_name_summary">会議を識別するために JID の代わりにルームのテーマを使用します</string> <string name="toast_message_otr_fingerprint">OTR フィンガープリントをクリップボードにコピーしました!</string> + <string name="toast_message_omemo_fingerprint">OMEMO フィンガープリントをクリップボードにコピーしました!</string> <string name="conference_banned">あなたはこの会議から禁止されています</string> <string name="conference_members_only">この会議はメンバーのみです</string> <string name="conference_kicked">あなたはこの会議からキックされました</string> @@ -307,7 +322,6 @@ <string name="verify_otr">OTR を検証</string> <string name="remote_fingerprint">リモート フィンガープリント</string> <string name="scan">スキャン</string> - <string name="or_touch_phones">(または電話をタッチ)</string> <string name="smp">ソーシャリスト ミリオネア プロトコル</string> <string name="shared_secret_hint">ヒントまたは質問</string> <string name="shared_secret_secret">共有の秘密</string> @@ -324,6 +338,9 @@ <string name="conversations_foreground_service">Conversations</string> <string name="pref_keep_foreground_service">サービスをフォアグラウンドに保持</string> <string name="pref_keep_foreground_service_summary">オペレーティングシステムが接続を切断するのを防止します</string> + <string name="pref_export_logs">ログをエクスポート</string> + <string name="pref_export_logs_summary">ログを SD カードに書き込みます</string> + <string name="notification_export_logs_title">ログを SD カードに書き込み中</string> <string name="choose_file">ファイルの選択</string> <string name="receiving_x_file">%1$s 受信中 (%2$d%% 完了)</string> <string name="download_x_file">%s のダウンロード</string> @@ -349,7 +366,18 @@ <string name="secret_accepted">秘密を受取ました!</string> <string name="reset">リセット</string> <string name="account_image_description">アカウント アバター</string> - <string name="copy_otr_clipboard_description">OTR フィンガープリントをクリップボードにコピーしました</string> + <string name="copy_otr_clipboard_description">OTR フィンガープリントをクリップボードにコピー</string> + <string name="copy_omemo_clipboard_description">OMEMO フィンガープリントをクリップボードにコピー</string> + <string name="regenerate_omemo_key">OMEMO キーを再生成</string> + <string name="wipe_omemo_pep">PEP から他のデバイスを消去</string> + <string name="clear_other_devices">デバイスをクリア</string> + <string name="clear_other_devices_desc">OMEMO のアナウンスから他のすべてのデバイスをクリアしてもよろしいですか? お使いのデバイスが次回接続したとき、それらは自分自身を再アナウンスしますが、それらはその間に送信されたメッセージを受信できない場合があります。</string> + <string name="purge_key">鍵を消去</string> + <string name="purge_key_desc_part1">この鍵を消去してもよろしいですか?</string> + <string name="purge_key_desc_part2">これは不可逆的に侵害とみなされ、あなたは再びそのセッションを構築することはできません。</string> + <string name="error_no_keys_to_trust_server_error">この連絡先で利用可能な鍵がありません。\nサーバーから新しい鍵の取得に失敗しました。おそらく、お使いの連絡先サーバーに何か問題があります。</string> + <string name="error_no_keys_to_trust">この連絡先で利用可能な鍵はありません。あなたがその鍵を消去している場合は、新しいものを生成してもらう必要があります。</string> + <string name="error_trustkeys_title">エラー</string> <string name="fetching_history_from_server">サーバーから履歴を取得中</string> <string name="no_more_history_on_server">サーバーにこれ以上履歴はありません</string> <string name="updating">アップデート中…</string> @@ -387,8 +415,10 @@ <string name="public_conference">公開アクセス可能な会議</string> <string name="private_conference">プライベート、メンバーのみの会議</string> <string name="conference_options">会議オプション</string> - <string name="members_only">プライベート (メンバーのみ)</string> + <string name="members_only">プライベート、メンバーのみ</string> <string name="non_anonymous">匿名でない</string> + <string name="moderated">司会</string> + <string name="you_are_not_participating">あなたは参加していません</string> <string name="modified_conference_options">会議オプションを変更しました!</string> <string name="could_not_modify_conference_options">会議オプションを変更できません</string> <string name="never">なし</string> @@ -416,7 +446,7 @@ <string name="offering_x_file">%s の依頼中</string> <string name="hide_offline">オフラインを非表示にする</string> <string name="disable_account">アカウントを無効にする</string> - <string name="contact_is_typing">%s は入力中...</string> + <string name="contact_is_typing">%s は入力中…</string> <string name="contact_has_stopped_typing">%s は入力を停止しました</string> <string name="pref_chat_states">入力中通知</string> <string name="pref_chat_states_summary">あなたが新しいメッセージを書いている時に、連絡先に知らせます</string> @@ -426,8 +456,7 @@ <string name="location">位置</string> <string name="received_location">位置を受信しました</string> <string name="title_undo_swipe_out_conversation">会話が閉じられました</string> - <string name="title_undo_swipe_out_muc">退出した会話</string> - <string name="pref_certificate_options">証明書オプション</string> + <string name="title_undo_swipe_out_muc">会議を退出しました</string> <string name="pref_dont_trust_system_cas_title">システムの CA を信頼しない</string> <string name="pref_dont_trust_system_cas_summary">すべての証明書を手動で承認する必要があります</string> <string name="pref_remove_trusted_certificates_title">証明書を削除</string> @@ -447,6 +476,72 @@ <string name="none">なし</string> <string name="recently_used">最近使用した</string> <string name="choose_quick_action">クイックアクションの選択</string> - <string name="file_not_found_on_remote_host">リモートサーバーにファイルが見つかりません</string> <string name="search_for_contacts_or_groups">連絡先またはグループの検索</string> + <string name="send_private_message">プライベートメッセージを送信</string> + <string name="user_has_left_conference">%s が会議を退出しました!</string> + <string name="username">ユーザー名</string> + <string name="username_hint">ユーザー名</string> + <string name="invalid_username">これは有効なユーザー名ではありません</string> + <string name="download_failed_server_not_found">ダウンロードに失敗しました: サーバーが見つかりません</string> + <string name="download_failed_file_not_found">ダウンロードに失敗しました: ファイルが見つかりません</string> + <string name="download_failed_could_not_connect">ダウンロードに失敗しました: ホストに接続できませんでした</string> + <string name="pref_use_white_background">白い背景を使用する</string> + <string name="pref_use_white_background_summary">白地に黒の文字で、受け取ったメッセージを表示します</string> + <string name="account_status_tor_unavailable">Tor ネットワークが利用できません</string> + <string name="server_info_broken">壊れています</string> + <string name="pref_presence_settings">参加設定</string> + <string name="pref_away_when_screen_off">画面がオフのときは離席</string> + <string name="pref_away_when_screen_off_summary">画面がオフになっているとき、リソースを離席としてマークします</string> + <string name="pref_xa_on_silent_mode">サイレントモード時は利用不可</string> + <string name="pref_xa_on_silent_mode_summary">デバイスがサイレントモードのとき、リソースが利用不可としてマークします</string> + <string name="action_add_account_with_certificate">アカウントに証明書を追加</string> + <string name="unable_to_parse_certificate">証明書を解析できません</string> + <string name="authenticate_with_certificate">空にすると、証明書で認証します</string> + <string name="captcha_ocr">キャプチャ テキスト</string> + <string name="captcha_required">キャプチャが必要です</string> + <string name="captcha_hint">画像からテキストを入力してください</string> + <string name="certificate_chain_is_not_trusted">証明書チェーンは信頼済ではありません</string> + <string name="jid_does_not_match_certificate">Jabber ID が証明書と一致しません</string> + <string name="action_renew_certificate">証明書を更新</string> + <string name="error_fetching_omemo_key">OMEMO 鍵の取得中にエラー!</string> + <string name="verified_omemo_key_with_certificate">OMEMO 鍵の取得中にエラー!</string> + <string name="device_does_not_support_certificates">お使いのデバイスはクライアント証明書の選択をサポートしていません!</string> + <string name="pref_connection_options">接続オプション</string> + <string name="pref_use_tor">Tor 経由で接続</string> + <string name="pref_use_tor_summary">Tor ネットワークを介してすべての接続をトンネルします。 Orbot が必要です</string> + <string name="account_settings_hostname">ホスト名</string> + <string name="account_settings_port">ポート</string> + <string name="hostname_or_onion">サーバーまたは .onion アドレス</string> + <string name="not_a_valid_port">これは有効なポート番号ではありません</string> + <string name="not_valid_hostname">これは有効なホスト名ではありません</string> + <string name="connected_accounts">%1$d / %2$d アカウントが接続しました</string> + <plurals name="x_messages"> + <item quantity="other">%d メッセージ</item> + </plurals> + <string name="shared_file_with_x">%s でファイルを共有</string> + <string name="shared_image_with_x">%s で画像を共有</string> + <string name="no_storage_permission">Conversations は外部ストレージにアクセスが必要です</string> + <string name="sync_with_contacts">連絡先と同期</string> + <string name="sync_with_contacts_long">Conversations はフルネームやアバターを表示するために、連絡先と XMPP 名簿と一致するようにしたいです。\n\nConversations は、サーバーにアップロードすることはなく、ローカルで連絡先を読んで一致させるだけです。\n\n今、連絡先へのアクセス許可を付与するように求められます。</string> + <string name="certificate_information">証明書情報</string> + <string name="certificate_subject">表題</string> + <string name="certificate_issuer">発行者</string> + <string name="certificate_cn">一般名称</string> + <string name="certificate_o">組織</string> + <string name="certificate_sha1">SHA1</string> + <string name="certicate_info_not_available">(利用不可)</string> + <string name="certificate_not_found">証明書がありません</string> + <string name="notify_on_all_messages">すべてのメッセージで通知</string> + <string name="notify_only_when_highlighted">ハイライトされたときにのみ通知</string> + <string name="notify_never">通知は無効</string> + <string name="notify_paused">通知は一時停止</string> + <string name="pref_picture_compression">写真を圧縮</string> + <string name="pref_picture_compression_summary">写真をサイズ変更や圧縮します</string> + <string name="always">常に</string> + <string name="automatically">自動</string> + <string name="battery_optimizations_enabled">バッテリー最適化が有効</string> + <string name="battery_optimizations_enabled_explained">お使いのデバイスは、Conversations で、通知の遅延やメッセージの損失につながる可能性のある、いくつかの重いバッテリーの最適化を行っています。\nそれらを無効にすることをお勧めします。</string> + <string name="battery_optimizations_enabled_dialog">お使いのデバイスは、Conversations で通知の遅延やメッセージの損失につながる可能性のある、いくつかの重いバッテリーの最適化を行っています。\n\n今、それらを無効にするように求められます。</string> + <string name="disable">無効</string> + <string name="selection_too_large">選択した範囲が大きすぎます</string> </resources> diff --git a/src/main/res/values-ko/strings.xml b/src/main/res/values-ko/strings.xml index cdc3737a..4c00d81f 100644 --- a/src/main/res/values-ko/strings.xml +++ b/src/main/res/values-ko/strings.xml @@ -9,7 +9,6 @@ <string name="action_secure">안전한 대화 </string> <string name="action_add_account">계정 추가 </string> <string name="action_edit_contact">이름 편집 </string> - <string name="action_add_phone_book">주소록에 추가 </string> <string name="action_delete_contact">명단에서 삭제 </string> <string name="action_block_contact">연락처 </string> <string name="action_unblock_contact">연락처 차단 해제 </string> @@ -28,7 +27,6 @@ <string name="minutes_ago">%d 분 전 </string> <string name="unread_conversations">읽지 않은 대화 </string> <string name="sending">보내는중... </string> - <string name="encrypted_message">메세지 복호화중입니다. 기다리세요...</string> <string name="nick_in_use">사용중인 별명입니다 </string> <string name="admin">관리자 </string> <string name="owner">소유자 </string> @@ -74,9 +72,7 @@ <string name="clear_conversation_history">대화 기록 삭제 </string> <string name="clear_histor_msg">이 대화의 모든 메세지를 삭제하시겠습니까? 경고: 이것은 다른 기기나 서버에 있는 메세지에는 영향을 미치지 않습니다. </string> <string name="delete_messages">메세지 삭제 </string> - <string name="also_end_conversation">나중에 이 대화 끝내기 </string> <string name="choose_presence">연락할 프레즌스 선택 </string> - <string name="send_plain_text_message">평문 메세지 전송 </string> <string name="send_otr_message">OTR 암호화된 메세지 전송 </string> <string name="send_pgp_message">OpenPGP 암호화된 메세지 전송 </string> <string name="your_nick_has_been_changed">닉네임이 변경되었습니다 </string> @@ -92,7 +88,6 @@ <string name="contact_has_no_pgp_key">당신의 연락처가 그들의 공개 키를 선언하지 않고 있기 때문에 Conversations는 당신의 메세지를 암호화할 수 없습니다. OpenPGP를 설정하도록 당신의 연락처에게 물어보세요. </string> <string name="no_pgp_keys">OpenPGP 키가 발견되지 않음 </string> <string name="contacts_have_no_pgp_keys">당신의 연락처가 그들의 공개 키를 선언하지 않고 있기 때문에 Conversations는 당신의 메세지를 암호화할 수 없습니다. OpenPGP를 설정하도록 당신의 연락처에게 물어보세요. </string> - <string name="encrypted_message_received">암호화된 메세지 수신됨. 터치해서 복호화 및 열람하세요. </string> <string name="pref_general">일반 </string> <string name="pref_xmpp_resource">XMPP 자원 </string> <string name="pref_xmpp_resource_summary">이 클라이언트가 자신을 알아보는 이름</string> @@ -105,8 +100,6 @@ <string name="pref_vibrate_summary">새 메세지 도착시 진동 </string> <string name="pref_sound">소리 </string> <string name="pref_sound_summary">알림과 동시에 벨소리 재생 </string> - <string name="pref_conference_notifications">회의 알림 </string> - <string name="pref_conference_notifications_summary">새 회의 메세지가 도착시, 강조됐을 때 뿐만 아니라 항상 알림 </string> <string name="pref_notification_grace_period">알림 유예 </string> <string name="pref_notification_grace_period_summary">Carbon Copy 수신 후에 잠시동안 알림 해제</string> <string name="pref_advanced_options">추가 설정 </string> @@ -149,7 +142,6 @@ <string name="account_status_regis_not_sup">서버가 등록을 지원하지 않습니다</string> <string name="account_status_security_error">보안 오류 </string> <string name="account_status_incompatible_server">호환되지 않는 서버 </string> - <string name="encryption_choice_none">평문 </string> <string name="encryption_choice_otr">OTR</string> <string name="encryption_choice_pgp">OpenPGP </string> <string name="mgmt_account_edit">계정 편집 </string> @@ -170,7 +162,6 @@ <string name="passwords_do_not_match">암호가 일치하지 않습니다 </string> <string name="invalid_jid">올바른 Jabber ID가 아닙니다 </string> <string name="error_out_of_memory">메모리 부족. 이미지 용량이 너무 큽니다 </string> - <string name="add_phone_book_text">%s를 기기의 연락처 목록에 추가하시겠습니까? </string> <string name="contact_status_online">접속중 </string> <string name="contact_status_free_to_chat">대화 가능 </string> <string name="contact_status_away">자리 비움 </string> @@ -186,7 +177,6 @@ <string name="server_info_blocking">XEP-0191: Blocking Command </string> <string name="server_info_roster_version">XEP-0237: Roster Versioning </string> <string name="server_info_stream_management">XEP-0198: Stream Management </string> - <string name="server_info_pep">XEP-0163: PEP (Avatars) </string> <string name="server_info_available">가능 </string> <string name="server_info_unavailable">불가 </string> <string name="missing_public_keys">공개 키 선언 누락 </string> @@ -249,7 +239,6 @@ <string name="skip">건너뛰기 </string> <string name="disable_notifications">알림 해제 </string> <string name="disable_notifications_for_this_conversation">이 대화의 알림 해제 </string> - <string name="notifications_disabled">알림이 해제되었습니다 </string> <string name="enable">사용 </string> <string name="conference_requires_password">회의에 암호가 필요합니다 </string> <string name="enter_password">암호 입력 </string> @@ -304,7 +293,6 @@ <string name="verify_otr">OTR 검증 </string> <string name="remote_fingerprint">Remote Fingerprint</string> <string name="scan">스캔 </string> - <string name="or_touch_phones">(혹은 기기 터치) </string> <string name="smp">Socialist Millionaire Protocol</string> <string name="shared_secret_hint">힌트 혹은 질문 </string> <string name="shared_secret_secret">공유된 비밀 </string> @@ -384,7 +372,6 @@ <string name="public_conference">공개적으로 접근 가능한 회의 </string> <string name="private_conference">멤버 전용 사설 회의 </string> <string name="conference_options">회의 설정 </string> - <string name="members_only">사설 (멤버 전용) </string> <string name="non_anonymous">익명 아님 </string> <string name="modified_conference_options">회의 설정 변경됨 </string> <string name="could_not_modify_conference_options">회의 설정을 변경할 수 없습니다 </string> @@ -413,7 +400,6 @@ <string name="offering_x_file">%s 제공중 </string> <string name="hide_offline">오프라인 숨기기 </string> <string name="disable_account">계정 해제 </string> - <string name="contact_is_typing">%s 이(가) 입력중입니다... </string> <string name="contact_has_stopped_typing">%s 이(가) 입력을 중단했습니다 </string> <string name="pref_chat_states">입력 알림 </string> <string name="pref_chat_states_summary">새 메세지를 작성할 때 이를 연락처에게 알립니다 </string> @@ -424,7 +410,6 @@ <string name="received_location">위치 수신 </string> <string name="title_undo_swipe_out_conversation">대화 끝남 </string> <string name="title_undo_swipe_out_muc">회의에서 나감 </string> - <string name="pref_certificate_options">인증 설정 </string> <string name="pref_dont_trust_system_cas_title">시스템 CA를 신뢰하지 않음 </string> <string name="pref_dont_trust_system_cas_summary">모든 인증서는 수동으로 승인되어야 함 </string> <string name="pref_remove_trusted_certificates_title">인증서 삭제 </string> diff --git a/src/main/res/values-nb-rNO/strings.xml b/src/main/res/values-nb-rNO/strings.xml new file mode 100644 index 00000000..0b8ad688 --- /dev/null +++ b/src/main/res/values-nb-rNO/strings.xml @@ -0,0 +1,550 @@ +<?xml version='1.0' encoding='UTF-8'?> +<resources> + <string name="action_settings">Innstillinger</string> + <string name="action_add">Ny samtale</string> + <string name="action_accounts">Kontobehandling</string> + <string name="action_end_conversation">Avslutt denne samtalen</string> + <string name="action_contact_details">Kontaktdetaljer</string> + <string name="action_muc_details">Konferansedetaljer</string> + <string name="action_secure">Sikret samtale</string> + <string name="action_add_account">Legg til samtale</string> + <string name="action_edit_contact">Rediger navn</string> + <string name="action_add_phone_book">Legg til i kontaktliste</string> + <string name="action_delete_contact">Fjern fra kontaktliste</string> + <string name="action_block_contact">Blokker kontakt</string> + <string name="action_unblock_contact">Avblokker kontakt</string> + <string name="action_block_domain">Blokker domene</string> + <string name="action_unblock_domain">Avblokker domene</string> + <string name="title_activity_manage_accounts">Kontobehandling</string> + <string name="title_activity_settings">Innstillinger</string> + <string name="title_activity_conference_details">Konferansedetaljer</string> + <string name="title_activity_contact_details">Kontaktdetaljer</string> + <string name="title_activity_sharewith">Del med Conversation</string> + <string name="title_activity_start_conversation">Start samtale</string> + <string name="title_activity_choose_contact">Velg kontakt</string> + <string name="title_activity_block_list">Blokkeringsliste</string> + <string name="just_now">akkurat nå</string> + <string name="minute_ago">1 minutt siden</string> + <string name="minutes_ago">%d minutter siden</string> + <string name="unread_conversations">uleste samtaler</string> + <string name="sending">sender...</string> + <string name="message_decrypting">Dekrypterer melding mens du venter.</string> + <string name="pgp_message">OpenPGP-kryptert melding</string> + <string name="nick_in_use">Kallenavn allerede i bruk</string> + <string name="admin">Admin</string> + <string name="owner">Eier</string> + <string name="moderator">Moderator</string> + <string name="participant">Deltager</string> + <string name="visitor">Besøkende</string> + <string name="remove_contact_text">Bekreft fjerning av %s fra din kontaktliste. Samtalen med denne kontakten vil ikke bli fjernet.</string> + <string name="block_contact_text">Vil du forhindre %s fra å sende deg meldinger?</string> + <string name="unblock_contact_text">Ønsker du å avblokkere %s og tillate dem å sende deg meldinger?</string> + <string name="block_domain_text">Blokker alle kontakter fra %s?</string> + <string name="unblock_domain_text">Avblokker alle kontakter fra %s?</string> + <string name="contact_blocked">Kontakt blokkert</string> + <string name="remove_bookmark_text">Vil du fjerne %s som bokmerke? Samtalen det skriver seg fra vil ikke bli fjernet.</string> + <string name="register_account">Registrer ny konto på tjeneren</string> + <string name="change_password_on_server">Endre passord på tjeneren</string> + <string name="share_with">Del med...</string> + <string name="start_conversation">Start samtale</string> + <string name="invite_contact">Inviter kontakt</string> + <string name="contacts">Kontakter</string> + <string name="cancel">Avbryt</string> + <string name="set">Sett</string> + <string name="add">Legg til</string> + <string name="edit">Rediger</string> + <string name="delete">Slett</string> + <string name="block">Blokker</string> + <string name="unblock">Avblokker</string> + <string name="save">Lagre</string> + <string name="ok">OK</string> + <string name="crash_report_title">Conversations har kræsjet</string> + <string name="crash_report_message">Ved å sende inn stabelsporinger hjelper du den pågående utviklingen av Conversations\n<b>Advarsel:</b> Dette bruker din XMPP-konto til å sende stabelsporinger til utvikleren.</string> + <string name="send_now">Send nå</string> + <string name="send_never">Aldri spør igjen</string> + <string name="problem_connecting_to_account">Kunne ikke koble til konto</string> + <string name="problem_connecting_to_accounts">Kunne ikkekoble til flerforldige kontoer</string> + <string name="touch_to_fix">Trykk her for behandling av kontaktene dine</string> + <string name="attach_file">Legg til fil</string> + <string name="not_in_roster">Kontakten finnes ikke i din liste. Vil du legge den til?</string> + <string name="add_contact">Legg til kontakt</string> + <string name="send_failed">forsendelse feilet</string> + <string name="send_rejected">avslått</string> + <string name="preparing_image">Forbereder bilde for forsendelse</string> + <string name="action_clear_history">Tøm historikk</string> + <string name="clear_conversation_history">Tøm samtalehistorikk</string> + <string name="clear_histor_msg">Ønsker du å slette alle meldinger i denne samtalen?\n\n<b>Advarsel:</b> Dette har ingen innvirkning på meldinger lagret på andre enheter eller tjenere.</string> + <string name="delete_messages">Slett meldinger</string> + <string name="also_end_conversation">Avslutt denne samtalen etterpå</string> + <string name="choose_presence">Velg tilgjengelighetsoppdatering til kontakt</string> + <string name="send_unencrypted_message">Send ukryptert melding</string> + <string name="send_otr_message">Send OTR-kryptert melding</string> + <string name="send_omemo_message">Send OMEMO-kryptert melding</string> + <string name="send_omemo_x509_message">Send \\OMEMO-kryptert melding</string> + <string name="send_pgp_message">Send OpenPGP-kryptert melding</string> + <string name="your_nick_has_been_changed">Kallenavnet ditt har blitt endret</string> + <string name="send_unencrypted">Send ukryptert</string> + <string name="decryption_failed">Dekryptering feilet. Kanskje du ikke lenger har den rette private nøkkelen.</string> + <string name="openkeychain_required">OpenKeychain</string> + <string name="openkeychain_required_long">Conversations nyttegjør seg av et tredjepartsprogram kalt <b>OpenKeychain</b> for å kryptere og dekryptere meldinger og behandle offentlige nøkler. \n\nOpenKeychain er lisensiert GPLv3 og er tilgjengelig på F-Droid og Google Play.\n\n<small>(Husk å gjøre en omstart av Conversations etterpå.)</small></string> + <string name="restart">Omstart</string> + <string name="install">Installer</string> + <string name="openkeychain_not_installed">Installer OpenKeychain</string> + <string name="offering">tilbyr...</string> + <string name="waiting">venter...</string> + <string name="no_pgp_key">Ingen OpenPGP-nøkkel funnet</string> + <string name="contact_has_no_pgp_key">Conversations kan ikke kryptere din melding fordi din kontakt ikke annonserer sin offentlige nøkkel.\n\n<small>Spør din kontakt om å sette opp OpenPGP.</small></string> + <string name="no_pgp_keys">Ingen OpenPGP-nøkler funnet</string> + <string name="contacts_have_no_pgp_keys">Conversations kan ikke kryptere din melding fordi dine kontakter ikke annonserer sine offentlige nøkkler.\n\n<small>Spør din kontakt om å sette opp OpenPGP.</small></string> + <string name="encrypted_message_received"><i>Kryptert melding mottatt. Trykk for å dekryptere.</i></string> + <string name="pref_general">Generelt</string> + <string name="pref_xmpp_resource">XMPP-ressurs</string> + <string name="pref_xmpp_resource_summary">Navnet denne klienten identifiserer seg med</string> + <string name="pref_accept_files">Godta filer</string> + <string name="pref_accept_files_summary">Automatisk godkjenning av filer mindre enn...</string> + <string name="pref_notification_settings">Varslingsinnstillinger</string> + <string name="pref_notifications">Varslinger</string> + <string name="pref_notifications_summary">Varsle når en ny melding ankommer</string> + <string name="pref_vibrate">Vibrer</string> + <string name="pref_vibrate_summary">Vibrer også når ny melding ankommer</string> + <string name="pref_sound">Lyd</string> + <string name="pref_sound_summary">Spill av ringetone ved varsel</string> + <string name="pref_notification_grace_period">Stilleperiode</string> + <string name="pref_notification_grace_period_summary">Deaktiver varslinger for en kort periode etter at en kopi er mottatt</string> + <string name="pref_advanced_options">Avanserte valg</string> + <string name="pref_never_send_crash">Aldri send feilrettingsrapporter</string> + <string name="pref_never_send_crash_summary">Ved å sende inn stabelsporinger hjelper du den pågående utviklingen av Conversations</string> + <string name="pref_confirm_messages">Bekreft meldinger</string> + <string name="pref_confirm_messages_summary">La din kontakt få vite når du har mottatt og lest en melding</string> + <string name="pref_ui_options">Valg for grensesnitt</string> + <string name="openpgp_error">Feilmelding fra OpenKeychain</string> + <string name="error_decrypting_file">I/O-feil ved dekryptering av fil</string> + <string name="accept">Godta</string> + <string name="error">En feil har inntruffet</string> + <string name="pref_grant_presence_updates">Tillat oppdateringer for tilstedeværelse</string> + <string name="pref_grant_presence_updates_summary">Gi og spør om tilstandsabbonementer på forhånd for kontakter du har opprettet</string> + <string name="subscriptions">Abonnement</string> + <string name="your_account">Din konto</string> + <string name="keys">Nøkler</string> + <string name="send_presence_updates">Send oppdateringer for tilstedeværelse</string> + <string name="receive_presence_updates">Motta oppdateringer for tilstedeværelse</string> + <string name="ask_for_presence_updates">Etterspør oppdateringer for tilstedeværelse</string> + <string name="attach_choose_picture">Velg bilde</string> + <string name="attach_take_picture">Ta bilde</string> + <string name="preemptively_grant">Tillat abbonnementsforespørsel på forhånd</string> + <string name="error_not_an_image_file">Filen du valgte er ikke et bilde</string> + <string name="error_compressing_image">Feil ved konvertering av bildefila</string> + <string name="error_file_not_found">Finner ikke filen</string> + <string name="error_io_exception">Generell I/O-feil. Har du sluppet opp for lagringsplass?</string> + <string name="error_security_exception_during_image_copy">Programmet du brukte til å velge dette bildet ga oss ikke nok tillatelser til å lese filen. \n\n<small>Bruk en annen filbehandler til valg av bilde</small></string> + <string name="account_status_unknown">Ukjent</string> + <string name="account_status_disabled">Midlertidig avskrudd</string> + <string name="account_status_online">Pålogget</string> + <string name="account_status_connecting">Kobler til\u2026</string> + <string name="account_status_offline">Avlogget</string> + <string name="account_status_unauthorized">Ikke tillatt</string> + <string name="account_status_not_found">Fant ikke tjener</string> + <string name="account_status_no_internet">Ingen tilkobling</string> + <string name="account_status_regis_fail">Registrering feilet</string> + <string name="account_status_regis_conflict">Brukernavn allerede i bruk</string> + <string name="account_status_regis_success">Registrering fullført</string> + <string name="account_status_regis_not_sup">Tjeneren støtter ikke registrering</string> + <string name="account_status_security_error">Sikkerhetsfeil</string> + <string name="account_status_incompatible_server">Ukompatibel tjener</string> + <string name="encryption_choice_unencrypted">Ukryptert</string> + <string name="encryption_choice_otr">OTR</string> + <string name="encryption_choice_pgp">OpenPGP</string> + <string name="encryption_choice_omemo">OMEMO</string> + <string name="mgmt_account_edit">Rediger konto</string> + <string name="mgmt_account_delete">Slett konto</string> + <string name="mgmt_account_disable">Skru av midlertidig</string> + <string name="mgmt_account_publish_avatar">Publiser avatar</string> + <string name="mgmt_account_publish_pgp">Publiser OpenPGP offentlig nøkkel</string> + <string name="mgmt_account_enable">Skru på konto</string> + <string name="mgmt_account_are_you_sure">Bekreft.</string> + <string name="mgmt_account_delete_confirm_text">Hvis du sletter din konto vil hele din konversasjonshistorikk gå tapt</string> + <string name="attach_record_voice">Ta opp stemme</string> + <string name="account_settings_jabber_id">Jabber-ID</string> + <string name="account_settings_password">Passord</string> + <string name="account_settings_example_jabber_id">brukernavn@eksempel.no</string> + <string name="account_settings_confirm_password">Bekreft passord</string> + <string name="password">Passord</string> + <string name="confirm_password">Bekreft passord</string> + <string name="passwords_do_not_match">Passordene samsvarer ikke</string> + <string name="invalid_jid">Dette er ikke en gyldig Jabber-ID</string> + <string name="error_out_of_memory">Slapp opp for minne, bildet er for stort</string> + <string name="add_phone_book_text">Ønsker du å legge %s til i din kontaktliste?</string> + <string name="contact_status_online">pålogget</string> + <string name="contact_status_free_to_chat">tilgjengelig for sludring</string> + <string name="contact_status_away">fraværende</string> + <string name="contact_status_extended_away">borte</string> + <string name="contact_status_do_not_disturb">ikke forstyrr</string> + <string name="contact_status_offline">avlogget</string> + <string name="muc_details_conference">Konferanse</string> + <string name="muc_details_other_members">Andre medlemmer</string> + <string name="server_info_show_more">Tjenerinfo</string> + <string name="server_info_mam">XEP-0313: MAM</string> + <string name="server_info_carbon_messages">XEP-0280: Meldingskarboner</string> + <string name="server_info_csi">XEP-0352: Identifisering av klientstatus</string> + <string name="server_info_blocking">XEP-0191: Blokkeringskommando</string> + <string name="server_info_roster_version">XEP-0237: Kontaktliste-versjonering</string> + <string name="server_info_stream_management">XEP-0198: Behandling av dataflyt</string> + <string name="server_info_pep">XEP-0163: PEP (Avatarer / OMEMO)</string> + <string name="server_info_http_upload">XEP-0363: HTTP-filopplasting</string> + <string name="server_info_available">tilgjengelig</string> + <string name="server_info_unavailable">utilgjengelig</string> + <string name="missing_public_keys">Manglende annonsering av offentlig nøkkel</string> + <string name="last_seen_now">i syne</string> + <string name="last_seen_min">sist sett for ett minutt siden</string> + <string name="last_seen_mins">sist sett for %d minutter siden</string> + <string name="last_seen_hour">sist sett én time siden</string> + <string name="last_seen_hours">sist sett %d timer siden</string> + <string name="last_seen_day">sist sett i går</string> + <string name="last_seen_days">sist sett for %d dager siden</string> + <string name="never_seen">aldri sett</string> + <string name="install_openkeychain">Kryptert melding. Installer OpenKeychain for å dekryptere.</string> + <string name="unknown_otr_fingerprint">Ukjent OTR-fingeravtrykk</string> + <string name="openpgp_messages_found">OpenPGP-krypterte meldinger funnet</string> + <string name="reception_failed">Mottak mislyktes</string> + <string name="your_fingerprint">Ditt fingeravtrykk</string> + <string name="otr_fingerprint">OTR-fingeravtrykk</string> + <string name="omemo_fingerprint">OMEMO-fingeravtrykk</string> + <string name="omemo_fingerprint_x509">v\\OMEMO-fingeravtrykk</string> + <string name="omemo_fingerprint_selected_message">Meldingens OMEMO-fingeravtrykk</string> + <string name="omemo_fingerprint_x509_selected_message">Meldingens v\\OMEMO-fingeravtrykk</string> + <string name="this_device_omemo_fingerprint">Eget OMEMO-fingeravtrykk</string> + <string name="other_devices">Andre enheter</string> + <string name="trust_omemo_fingerprints">Stol på OMEMO-fingeravtrykk</string> + <string name="fetching_keys">Hener inn nøkler…</string> + <string name="done">Ferdig</string> + <string name="verify">Bekreft</string> + <string name="decrypt">Dekrypter</string> + <string name="conferences">Konferanser</string> + <string name="search">Søk</string> + <string name="create_contact">Opprett kontakt</string> + <string name="enter_contact">Angi kontakt</string> + <string name="join_conference">Ta del i konferanse</string> + <string name="delete_contact">Slett kontakt</string> + <string name="view_contact_details">Vis kontaktdetaljer</string> + <string name="block_contact">Blokker kontakt</string> + <string name="unblock_contact">Avblokker kontakt</string> + <string name="create">Lag</string> + <string name="select">Velg</string> + <string name="contact_already_exists">Kontakten finnes allerede</string> + <string name="join">Ta del i</string> + <string name="conference_address">Konferanse-adresse</string> + <string name="conference_address_example">rom@konferanse.eksempel.no</string> + <string name="save_as_bookmark">Lagre som bokmerke</string> + <string name="delete_bookmark">Slett bokmerke</string> + <string name="bookmark_already_exists">Dette bokmerket finnes allerede</string> + <string name="you">Deg</string> + <string name="action_edit_subject">Rediger temaet for konferansen</string> + <string name="conference_not_found">Fant ikke konferansen</string> + <string name="leave">Forlat</string> + <string name="contact_added_you">Kontakt la deg til i sin liste</string> + <string name="add_back">Gjengjeld tjenesten</string> + <string name="contact_has_read_up_to_this_point">%s har lest hit</string> + <string name="publish">Publiser</string> + <string name="touch_to_choose_picture">Trykk på avataren for å velge blide fra galleriet</string> + <string name="publish_avatar_explanation">Merk, alle som kan se dine tilgjengelighetsoppdateringer vil kunne se dette bildet.</string> + <string name="publishing">Publiserer…</string> + <string name="error_publish_avatar_server_reject">Tjeneren avslo din publisering</string> + <string name="error_publish_avatar_converting">Noe gikk galt under konvertering av bildet ditt</string> + <string name="error_saving_avatar">Kunne ikke lagre avatarbilde til lagringsområde</string> + <string name="or_long_press_for_default">(Eller trykk lenge for å gå tilbake til forvalg)</string> + <string name="error_publish_avatar_no_server_support">Tjeneren din støtter ikke publisering av avatarer</string> + <string name="private_message">hvisket</string> + <string name="private_message_to">til %s</string> + <string name="send_private_message_to">Send privat melding til %s</string> + <string name="connect">Koble til</string> + <string name="account_already_exists">Denne kontoen finnes allerede</string> + <string name="next">Neste</string> + <string name="server_info_session_established">Nåværende økt etablert</string> + <string name="additional_information">Ytterligere informasjon</string> + <string name="skip">Hopp over</string> + <string name="disable_notifications">Deaktiver varslinger</string> + <string name="disable_notifications_for_this_conversation">Skru av varslinger for denne samtalen</string> + <string name="enable">Skru på</string> + <string name="conference_requires_password">Konferansen krever passord</string> + <string name="enter_password">Skriv inn passord</string> + <string name="missing_presence_updates">Mangler tilgjengelighetsoppdateringer fra kontakt</string> + <string name="request_presence_updates">Forespør tilstedeværelseoppdateringer fra din kontakt først.\n\n<small>Dette brukes til å fastslå hvilke(n) klient(er) din kontakt bruker.</small></string> + <string name="request_now">Send forespørsel nå</string> + <string name="delete_fingerprint">Slett fingeravtrykk</string> + <string name="sure_delete_fingerprint">Bekreft fjerning av fingeravtrykk.</string> + <string name="ignore">Ignorer</string> + <string name="without_mutual_presence_updates"><b>Advarsel:</b> Å sende dette uten at tilstandsoppdateringer er i overenstemmelse kan forårsake uventede problemer.\n\n<small>Gå til kontaktdetaljer for å bekrefte dine tilstedeværelsesabonnementer.</small></string> + <string name="pref_encryption_settings">Krypteringsinnstillinger</string> + <string name="pref_force_encryption">Krev ende-til-ende-kryptering</string> + <string name="pref_force_encryption_summary">Alltid send meldinger kryptert (bortsett fra konferanser)</string> + <string name="pref_dont_save_encrypted">Ikke lagre krypterte meldinger</string> + <string name="pref_dont_save_encrypted_summary">Advarsel: Dette kan føre til at meldinger går tapt</string> + <string name="pref_expert_options">Ekspertinnstillinger</string> + <string name="pref_expert_options_summary">Vær forsiktig med disse</string> + <string name="title_activity_about">Om Conversations</string> + <string name="pref_about_conversations_summary">Utgave og lisensinformasjon</string> + <string name="title_pref_quiet_hours">Stille tidsavgrensning</string> + <string name="title_pref_quiet_hours_start_time">Oppstart</string> + <string name="title_pref_quiet_hours_end_time">Avslutning</string> + <string name="title_pref_enable_quiet_hours">Aktiver stille tidsavgrensning</string> + <string name="pref_quiet_hours_summary">Varslinger blir ikke spilt under stilletid</string> + <string name="pref_use_larger_font">Øk tekststørrelse</string> + <string name="pref_use_larger_font_summary">Bruk større tekststørrelser i hele programmet</string> + <string name="pref_use_send_button_to_indicate_status">Forsendelsesknappen indikerer status</string> + <string name="pref_use_indicate_received">Forespørr meldingskvitteringer</string> + <string name="pref_use_indicate_received_summary">Mottatte meldinger vil bli avmerket i grønt hvis støttet</string> + <string name="pref_use_send_button_to_indicate_status_summary">Fargelegg send-knapp for å indikere kontakt-status</string> + <string name="pref_expert_options_other">Annet</string> + <string name="pref_conference_name">Konferanse-rom</string> + <string name="pref_conference_name_summary">Bruk rommets samtaletema istedenfor JID til å identifisere konferanser</string> + <string name="toast_message_otr_fingerprint">OTR-fingeravtrykk kopiert til utklippstavle!</string> + <string name="toast_message_omemo_fingerprint">OMEMO-fingeravtrykk kopiert til utklippstavle!</string> + <string name="conference_banned">Du er bannlyst fra denne konferansen</string> + <string name="conference_members_only">Denne konferansen er forbeholdt medlemmer</string> + <string name="conference_kicked">Du har blitt kastet ut av denne konferansen</string> + <string name="using_account">bruker konto %s</string> + <string name="checking_x">Sjekker %s på HTTP-tjener</string> + <string name="not_connected_try_again">Du er ikke tilkoblet. Prøv igjen senere</string> + <string name="check_x_filesize">Sjekk %s størrelse</string> + <string name="message_options">Meldingsvalg</string> + <string name="copy_text">Kopier tekst</string> + <string name="copy_original_url">Kopier orginal nettadresse</string> + <string name="send_again">Send igjen</string> + <string name="file_url">Filens nettadresse</string> + <string name="message_text">Meldingstekst</string> + <string name="url_copied_to_clipboard">Nettadresse kopiert til utklippstavle</string> + <string name="message_copied_to_clipboard">Melding kopiert til utklippstavle</string> + <string name="image_transmission_failed">Bildeoverføring feilet</string> + <string name="scan_qr_code">Skann QR-kode</string> + <string name="show_qr_code">Vis QR-kode</string> + <string name="show_block_list">Vis blokkeringsliste</string> + <string name="account_details">Kontodetaljer</string> + <string name="verify_otr">Bekreft OTR</string> + <string name="remote_fingerprint">Eksternt fingeravtrykk</string> + <string name="scan">skann</string> + <string name="smp">Sosialistisk-millionær-protokoll</string> + <string name="shared_secret_hint">Ledetråd eller spørsmål</string> + <string name="shared_secret_secret">Delt hemmelighet</string> + <string name="confirm">Bekreft</string> + <string name="in_progress">Underveis</string> + <string name="respond">Svar</string> + <string name="failed">Feilet</string> + <string name="secrets_do_not_match">Hemmelighetene samsvarer ikke</string> + <string name="try_again">Prøv igjen</string> + <string name="finish">Fullfør</string> + <string name="verified">Tiltrodd!</string> + <string name="smp_requested">Kontakt forespurte SMP-verifisering</string> + <string name="no_otr_session_found">Ingen gyldig OTR-økt funnet!</string> + <string name="conversations_foreground_service">Conversations</string> + <string name="pref_keep_foreground_service">Behold tjenesten i forgrunnen</string> + <string name="pref_keep_foreground_service_summary">Forhindrer operativsystemet fra å drepe tilkoblingen din</string> + <string name="pref_export_logs">Eksporter loggføringer</string> + <string name="pref_export_logs_summary">Skriv loggføringer til SD-kort</string> + <string name="notification_export_logs_title">Skriver loggføringer til SD-kort</string> + <string name="choose_file">Velg fil</string> + <string name="receiving_x_file">Mottak av %1$s (%2$d%% fullført)</string> + <string name="download_x_file">Last ned %s</string> + <string name="file">fil</string> + <string name="open_x_file">Åpne %s</string> + <string name="sending_file">forsendelse av (%1$d%% fullført)</string> + <string name="preparing_file">Forbereder fil for oversending</string> + <string name="x_file_offered_for_download">%s tilbudt for nedlasting</string> + <string name="cancel_transmission">Avbryt overføring</string> + <string name="file_transmission_failed">fil-overføring feilet</string> + <string name="file_deleted">Filen har blitt slettet</string> + <string name="no_application_found_to_open_file">Fant inget program til åpning av fil</string> + <string name="could_not_verify_fingerprint">Kunne ikke bekrefte fingeravtrykk</string> + <string name="manually_verify">Bekreft manuelt</string> + <string name="are_you_sure_verify_fingerprint">Bekreft verifisering av din kontakts OTR-fingeravtrykk.</string> + <string name="pref_show_dynamic_tags">Vis dynamiske merkelapper</string> + <string name="pref_show_dynamic_tags_summary">Vis \"bare-les\"-merkelapper under kontakter</string> + <string name="enable_notifications">Aktiver varslinger</string> + <string name="conference_with">Opprett konferanse med…</string> + <string name="no_conference_server_found">Ingen konferanse-tjener funnet</string> + <string name="conference_creation_failed">Opprettelse av konferanse feilet!</string> + <string name="conference_created">Konferanse opprettet!</string> + <string name="secret_accepted">Hemmelighet godtatt!</string> + <string name="reset">Tilbakestill</string> + <string name="account_image_description">Konto-avatar</string> + <string name="copy_otr_clipboard_description">Kopier OTR-fingeravtrykk til utklippstavle</string> + <string name="copy_omemo_clipboard_description">Kopier OMEMO-fingeravtrykk til utklippstavle</string> + <string name="regenerate_omemo_key">Regenerer OMEMO-nøkkel</string> + <string name="wipe_omemo_pep">Rens andre enheter fra PEP</string> + <string name="clear_other_devices">Rens enheter</string> + <string name="clear_other_devices_desc">Bekreft rensinga av alla andre enheter fra OMEMO-kunngjøringen. Neste gang dine enheter kobler til, vil de tilkjennegi seg på ny, men det kan hende de ikke mottar meldinger sendt i mellomtiden.</string> + <string name="purge_key">Tilintetgjør nøkkel</string> + <string name="purge_key_desc_part1">Tilintetgjør denne nøkkelen?</string> + <string name="purge_key_desc_part2">Den vil for all fremtid bli ansett som kompromittert, og du kan aldri starte en økt med den igjen.</string> + <string name="error_no_keys_to_trust_server_error">Ingen brukbare nøkler tilgjengelige for denne kontakten.\nInnhenting av nye nøkler fra tjeneren var ikke vellykket. Kanskje det er noe galt med tjeneren kontakten din bruker?</string> + <string name="error_no_keys_to_trust">Ingen brukbare nøkler tilgjengelige for denne kontakten. Hvis du har tilintetgjort noen av nøklene deres, må de generere nye.</string> + <string name="error_trustkeys_title">Feil</string> + <string name="fetching_history_from_server">Henter inn historikk fra tjener</string> + <string name="no_more_history_on_server">Ikke mer historikk på tjeneren</string> + <string name="updating">Oppdaterer…</string> + <string name="password_changed">Passord endret!</string> + <string name="could_not_change_password">Kunne ikke endre passord</string> + <string name="otr_session_not_started">Send en melding for å igangsette kryptert sludring</string> + <string name="ask_question">Still et spørsmål</string> + <string name="smp_explain_question">Om du og din kontakt har en hemmelighet dere deler som ingen andre vet om (som en intern vits eller hva du spiste til lunsj sist gang dere møttes) kan du bruke det til å bekrefte hverandres fingeravtrykk.\n\nDu gir en ledetråd eller stiller et spørsmål til din kontakt som i sin tur gir et versalsensitivt svar.</string> + <string name="smp_explain_answer">Din kontakt vil bekrefte ditt fingeravtrykk gjennom å utfordre deg med en delt hemmelighet. Din kontakt oppga følgende ledetråd eller spørsmål for hemmeligheten.</string> + <string name="shared_secret_hint_should_not_be_empty">Ledetråden din bør ikke stå tom</string> + <string name="shared_secret_can_not_be_empty">Din delte hemmelighet kan ikke være et tomt felt</string> + <string name="manual_verification_explanation">Jamfør nøye fingeravtrykket vis nedenfor med din kontakts fingeravtrykk.\nDu kan bruke enhver form for betrodd kommunikasjon som kryptert e-post eller en telefonsamtale for å utveksle disse.</string> + <string name="change_password">Endre passord</string> + <string name="current_password">Gjeldende passord</string> + <string name="new_password">Nytt passord</string> + <string name="password_should_not_be_empty">Passord burde ikke være et tomt felt</string> + <string name="enable_all_accounts">Skru på alle kontoer</string> + <string name="disable_all_accounts">Koble fra alle kontoer</string> + <string name="perform_action_with">Utfør handling med</string> + <string name="no_affiliation">Ingen tilknytning</string> + <string name="no_role">Ingen rolle</string> + <string name="outcast">Fredløs</string> + <string name="member">Medlem</string> + <string name="advanced_mode">Avansert modus</string> + <string name="grant_membership">Innlem som medlem</string> + <string name="remove_membership">Tilbakekall medlemskap</string> + <string name="grant_admin_privileges">Innlem som administrator</string> + <string name="remove_admin_privileges">Tilbakekall administratorrettigheter</string> + <string name="remove_from_room">Fjern fra konferanse</string> + <string name="could_not_change_affiliation">Kunne ikke endre tilknytningen til %s</string> + <string name="ban_from_conference">Bannlys fra konferanse</string> + <string name="removing_from_public_conference">Du prøver å fjerne %s fra en offentlig konferanse. Den eneste måten å gjøre det på er å bannlyse denne brukeren for godt.</string> + <string name="ban_now">Bannlys nå</string> + <string name="could_not_change_role">Kunne ikke endre rollen til %s</string> + <string name="public_conference">Konferanse i offentligheten</string> + <string name="private_conference">Privat konferanse kun for medlemmer</string> + <string name="conference_options">Valg for konferanse</string> + <string name="members_only">Privat, kun for medlemmer</string> + <string name="non_anonymous">Ikke-anonym</string> + <string name="moderated">Moderert</string> + <string name="you_are_not_participating">Du deltar ikke</string> + <string name="modified_conference_options">Endret konferanse-valg!</string> + <string name="could_not_modify_conference_options">Kunne ikke endre konferanse-valg</string> + <string name="never">Aldri</string> + <string name="thirty_minutes">30 minutter</string> + <string name="one_hour">1 time</string> + <string name="two_hours">2 timer</string> + <string name="eight_hours">8 timer</string> + <string name="until_further_notice">Til videre beskjed</string> + <string name="pref_input_options">Inndata-valg</string> + <string name="pref_enter_is_send">Enter er forsendelsesknapp</string> + <string name="pref_enter_is_send_summary">Bruk enter for å sende en melding</string> + <string name="pref_display_enter_key">Vis enter-tast</string> + <string name="pref_display_enter_key_summary">Endre smilefjas-tast til en enter-tast</string> + <string name="audio">lyd</string> + <string name="video">film</string> + <string name="image">stillbilde</string> + <string name="pdf_document">PDF-dokument</string> + <string name="apk">Android-app</string> + <string name="vcard">Kontakt</string> + <string name="received_x_file">Mottatt %s</string> + <string name="disable_foreground_service">Skru av forgrunns-tjeneste</string> + <string name="touch_to_open_conversations">Trykk for å åpne Conversations</string> + <string name="avatar_has_been_published">Avatar publisert!</string> + <string name="sending_x_file">Sender %s</string> + <string name="offering_x_file">Tilbyr %s</string> + <string name="hide_offline">Ikke vis frakoblede</string> + <string name="disable_account">Deaktiver konto</string> + <string name="contact_is_typing">%s skriver…</string> + <string name="contact_has_stopped_typing">%s har sluttet å skrive</string> + <string name="pref_chat_states">Varsler for skriving</string> + <string name="pref_chat_states_summary">La din kontakt få vite når du skriver en ny melding</string> + <string name="send_location">Send plasseringsdata</string> + <string name="show_location">Vis plasseringsdata</string> + <string name="no_application_found_to_display_location">Ingen programmer funnet til visning av plasseringsdata</string> + <string name="location">Plasseringsdata</string> + <string name="received_location">Mottok plasseringsdata</string> + <string name="title_undo_swipe_out_conversation">Samtale lukket</string> + <string name="title_undo_swipe_out_muc">Forlot konferansen</string> + <string name="pref_dont_trust_system_cas_title">Ikke stol på systemets CA-er</string> + <string name="pref_dont_trust_system_cas_summary">Alle sertifikat må godkjennes manuelt</string> + <string name="pref_remove_trusted_certificates_title">Fjern sertifikater</string> + <string name="pref_remove_trusted_certificates_summary">Slett sertifikater som er godkjent manuelt</string> + <string name="toast_no_trusted_certs">Ingen manuelt godkjente sertifikater</string> + <string name="dialog_manage_certs_title">Fjern sertifikater</string> + <string name="dialog_manage_certs_positivebutton">Slett innhold i merket område</string> + <string name="dialog_manage_certs_negativebutton">Avbryt</string> + <plurals name="toast_delete_certificates"> + <item quantity="one">%d sertifikat slettet</item> + <item quantity="other">%d sertifikater slettet</item> + </plurals> + <plurals name="select_contact"> + <item quantity="one">Velg %d kontakt</item> + <item quantity="other">Velg %d kontakter</item> + </plurals> + <string name="pref_quick_action_summary">Erstatt forsendelsesknapp med hurtighandling</string> + <string name="pref_quick_action">Hurtighandling</string> + <string name="none">Ingen</string> + <string name="recently_used">Senest brukt</string> + <string name="choose_quick_action">Velg hurtighendelse</string> + <string name="search_for_contacts_or_groups">Søk etter kontakter eller grupper</string> + <string name="send_private_message">Send privat melding</string> + <string name="user_has_left_conference">%s har forlatt konferansen!</string> + <string name="username">Brukernavn</string> + <string name="username_hint">Brukernavn</string> + <string name="invalid_username">Dette er ikke et gyldig brukernavn</string> + <string name="download_failed_server_not_found">Nedlasting feilet: Fant ikke tjener</string> + <string name="download_failed_file_not_found">Nedlasting feilet: Fant ikke fila</string> + <string name="download_failed_could_not_connect">Nedlasting feilet: Kunne ikke koble til tjeneren</string> + <string name="pref_use_white_background">Bruk hvit bakgrunn</string> + <string name="pref_use_white_background_summary">Vis mottatte meldinger som svart tekst på hvit bakgrunn</string> + <string name="account_status_tor_unavailable">Tor-nettverk utilgjengelig</string> + <string name="server_info_broken">Knekt</string> + <string name="pref_presence_settings">Tilstedeværelse-innstillinger</string> + <string name="pref_away_when_screen_off">Borte når skjermen er av</string> + <string name="pref_away_when_screen_off_summary">Markerer din ressurs som borte når skjermen er avskrudd</string> + <string name="pref_xa_on_silent_mode">Ikke tilgjengelig i stille-modus</string> + <string name="pref_xa_on_silent_mode_summary">Markerer din ressurs som \'ikke tilgjengelig\' når enheten er i stille-modus.</string> + <string name="action_add_account_with_certificate">Legg til konto med sertifikat</string> + <string name="unable_to_parse_certificate">Kunne ikke behandle sertifikat</string> + <string name="authenticate_with_certificate">La stå tom for bekreftelse med sertifikat</string> + <string name="captcha_ocr">CAPTCHA-tekst</string> + <string name="captcha_required">CAPTCHA-påkrevd</string> + <string name="captcha_hint">skriv inn teksten på bildet</string> + <string name="certificate_chain_is_not_trusted">Sertifikat-kjeden er ikke betrodd</string> + <string name="jid_does_not_match_certificate">Jabber-ID-en samsvarer ikke med sertifikatet</string> + <string name="action_renew_certificate">Forny sertifikat</string> + <string name="error_fetching_omemo_key">Feil ved innhenting av OMEMO-nøkkel!</string> + <string name="verified_omemo_key_with_certificate">Bekreftet OMEMO-nøkkel med sertifikat!</string> + <string name="device_does_not_support_certificates">Din enhet støtter ikke valg av klientsertifikat!</string> + <string name="pref_connection_options">Tilkoblingsalternativ</string> + <string name="pref_use_tor">Koble til via Tor</string> + <string name="pref_use_tor_summary">Send alle tilkoblinger i tunnel gjennom Tor-nettverket. Krever Orbot</string> + <string name="account_settings_hostname">Tjenernavn</string> + <string name="account_settings_port">Port</string> + <string name="hostname_or_onion">Tjener- eller .onion-adresse</string> + <string name="not_a_valid_port">Dette er ikke et gyldig portnummer</string> + <string name="not_valid_hostname">Dette er ikke et gyldig tjenernavn</string> + <string name="connected_accounts">%1$d av %2$d kontoer tilkoblet</string> + <plurals name="x_messages"> + <item quantity="one">%d melding</item> + <item quantity="other">%dmeldinger</item> + </plurals> + <string name="shared_file_with_x">Fil delt med %s</string> + <string name="shared_image_with_x">Bilde delt med %s</string> + <string name="no_storage_permission">Conversations trenger tilgang til eksternt lagringsmedie</string> + <string name="sync_with_contacts">Synkroniser med kontakter</string> + <string name="sync_with_contacts_long">Conversations vil jamføre din XMPP-kontaktliste med dine kontakter for å vise dem med navn og profilbilde.\n\nConversations leser bare dine kontakter for å jamføre dem lokalt, uten å laste dem opp til tjeneren din.\n\nDu kommer nå til å bli spurt om tilgangstillatelse til dine kontakter.</string> + <string name="certificate_information">Sertifikatsinformasjon</string> + <string name="certificate_subject">Emne</string> + <string name="certificate_issuer">Utsteder</string> + <string name="certificate_cn">Vanlig navn</string> + <string name="certificate_o">Organisasjon</string> + <string name="certificate_sha1">SHA1</string> + <string name="certicate_info_not_available">(Ikke tilgjengelig)</string> + <string name="certificate_not_found">Fant ikke noe sertifikat</string> + <string name="notify_on_all_messages">Varsle ved alle meldinger</string> + <string name="notify_only_when_highlighted">Varsle bare når fremhevet</string> + <string name="notify_never">Varslinger deaktivert</string> + <string name="notify_paused">Varslinger pauset</string> + <string name="pref_picture_compression">Komprimer bilder</string> + <string name="pref_picture_compression_summary">Komprimer og juster bildestørrelser</string> + <string name="always">Alltid</string> + <string name="automatically">Automatisk</string> + <string name="battery_optimizations_enabled">Batterioptimaliseringer aktivert</string> + <string name="battery_optimizations_enabled_explained">Enheten din gjør noen tunge batterioptimaliseringer på Conversations som kan føre til forsinkede varslinger eller tap av meldinger.\nDet anbefales at du deaktiverer disse.</string> + <string name="battery_optimizations_enabled_dialog">Enheten din gjør noen tunge batterioptimaliseringer på Conversations som kan føre til forsinkede varslinger eller tap av meldinger.\n\nDu vil nå bli bedt om å deaktivere disse.</string> + <string name="disable">Deaktiver</string> + <string name="selection_too_large">Det valgte området er for stort</string> +</resources> diff --git a/src/main/res/values-nl/strings.xml b/src/main/res/values-nl/strings.xml index 2873a793..2a3138df 100644 --- a/src/main/res/values-nl/strings.xml +++ b/src/main/res/values-nl/strings.xml @@ -2,25 +2,25 @@ <resources> <string name="action_settings">Instellingen</string> <string name="action_add">Nieuw gesprek</string> - <string name="action_accounts">Beheer account</string> - <string name="action_end_conversation">Beëindig gesprek</string> + <string name="action_accounts">Accounts beheren</string> + <string name="action_end_conversation">Gesprek beëindigen</string> <string name="action_contact_details">Contactgegevens</string> <string name="action_muc_details">Gespreksgegevens</string> <string name="action_secure">Beveiligd gesprek</string> - <string name="action_add_account">Voeg account toe</string> - <string name="action_edit_contact">Verander naam</string> - <string name="action_add_phone_book">Voeg toe aan telefoonboek</string> - <string name="action_delete_contact">Verwijder uit lijst</string> - <string name="action_block_contact">Blokkeer contact</string> - <string name="action_unblock_contact">Deblokkeer contact</string> - <string name="action_block_domain">Blokkeer domein</string> - <string name="action_unblock_domain">Deblokkeer domein</string> - <string name="title_activity_manage_accounts">Beheer accounts</string> + <string name="action_add_account">Account toevoegen</string> + <string name="action_edit_contact">Naam veranderen</string> + <string name="action_add_phone_book">Toevoegen aan adresboek</string> + <string name="action_delete_contact">Verwijderen uit lijst</string> + <string name="action_block_contact">Contact blokkeren</string> + <string name="action_unblock_contact">Contact deblokkeren</string> + <string name="action_block_domain">Domein blokkeren</string> + <string name="action_unblock_domain">Domein deblokkeren</string> + <string name="title_activity_manage_accounts">Accounts beheren</string> <string name="title_activity_settings">Instellingen</string> <string name="title_activity_conference_details">Groepsgespreksgegevens</string> <string name="title_activity_contact_details">Contactgegevens</string> <string name="title_activity_sharewith">Delen met gesprek</string> - <string name="title_activity_start_conversation">Start gesprek</string> + <string name="title_activity_start_conversation">Gesprek starten</string> <string name="title_activity_choose_contact">Kies contact</string> <string name="title_activity_block_list">Geblokkeerde contacten</string> <string name="just_now">net</string> @@ -28,7 +28,8 @@ <string name="minutes_ago">%d min. geleden</string> <string name="unread_conversations">ongelezen gesprekken</string> <string name="sending">versturen…</string> - <string name="encrypted_message">Bericht aan het ontsleutelen. Even geduld…</string> + <string name="message_decrypting">Bericht aan het ontsleutelen. Even geduld…</string> + <string name="pgp_message">OpenPGP-versleuteld bericht</string> <string name="nick_in_use">Naam is al in gebruik</string> <string name="admin">Beheerder</string> <string name="owner">Eigenaar</string> @@ -42,101 +43,102 @@ <string name="unblock_domain_text">Alle contacten van %s deblokkeren?</string> <string name="contact_blocked">Contact geblokkeerd</string> <string name="remove_bookmark_text">Wil je %s als bladwijzer verwijderen? Het gesprek met deze account zal niet worden verwijderd.</string> - <string name="register_account">Registreer nieuwe account op server</string> - <string name="change_password_on_server">Verander wachtwoord op server</string> - <string name="share_with">Deel met</string> - <string name="start_conversation">Start gesprek</string> - <string name="invite_contact">Nodig contact uit</string> + <string name="register_account">Nieuwe account op server registreren</string> + <string name="change_password_on_server">Wachtwoord op server veranderen</string> + <string name="share_with">Delen met…</string> + <string name="start_conversation">Gesprek starten</string> + <string name="invite_contact">Contact uitnodigen</string> <string name="contacts">Contacten</string> - <string name="cancel">Annuleer</string> - <string name="set">Stel in</string> - <string name="add">Voeg toe</string> - <string name="edit">Bewerk</string> - <string name="delete">Verwijder</string> - <string name="block">Blokkeer</string> - <string name="unblock">Deblokkeer</string> - <string name="save">Sla op</string> - <string name="ok">OK</string> + <string name="cancel">Annuleren</string> + <string name="set">Instellen</string> + <string name="add">Toevoegen</string> + <string name="edit">Bewerken</string> + <string name="delete">Verwijderen</string> + <string name="block">Blokkeren</string> + <string name="unblock">Deblokkeren</string> + <string name="save">Opslaan</string> + <string name="ok">Oké</string> <string name="crash_report_title">Conversations is gecrasht</string> - <string name="crash_report_message">Door het versturen van crash rapportages help je de ontwikkeling van Conversations.\n\n<b>Waarschuwing:</b> Deze app zal je XMPP account gebruiken om de crash rapportages te versturen naar de ontwikkelaars.</string> + <string name="crash_report_message">Door het versturen van crashrapportages help je de ontwikkeling van Conversations.\n\n<b>Waarschuwing:</b> Deze app zal je XMPP-account gebruiken om de crashrapportages te versturen naar de ontwikkelaars.</string> <string name="send_now">Nu versturen</string> <string name="send_never">Niet opnieuw vragen</string> <string name="problem_connecting_to_account">Account verbinden mislukt</string> <string name="problem_connecting_to_accounts">Verbinden met meerdere accounts mislukt</string> <string name="touch_to_fix">Raak hier aan om accounts te beheren</string> - <string name="attach_file">Voeg bestand bij</string> - <string name="not_in_roster">Het contact is geen onderdeel van uw lijst. Wil je het toevoegen?</string> - <string name="add_contact">Voeg contact toe</string> + <string name="attach_file">Bestand bijvoegen</string> + <string name="not_in_roster">Het contact is geen onderdeel van je lijst. Wil je hem/haar toevoegen?</string> + <string name="add_contact">Contact toevoegen</string> <string name="send_failed">afleveren mislukt</string> <string name="send_rejected">geweigerd</string> <string name="preparing_image">Bezig met voorbereiden van versturen van afbeelding</string> - <string name="action_clear_history">Wis geschiedenis</string> - <string name="clear_conversation_history">Wis gespreksgeschiedenis</string> + <string name="action_clear_history">Geschiedenis wissen</string> + <string name="clear_conversation_history">Gespreksgeschiedenis wissen</string> <string name="clear_histor_msg">Wil je alle berichten in dit gesprek verwijderen?\n\n<b>Waarschuwing:</b> Dit zal geen invloed hebben op de berichten opgeslagen op andere apparaten of servers.</string> - <string name="delete_messages">Verwijder berichten</string> - <string name="also_end_conversation">Beëindig dit gesprek na afloop</string> + <string name="delete_messages">Berichten verwijderen</string> + <string name="also_end_conversation">Dit gesprek na afloop beëindigen</string> <string name="choose_presence">Kies aanwezigheid om te tonen aan contact</string> - <string name="send_plain_text_message">Verstuur eenvoudig tekst bericht</string> - <string name="send_otr_message">Verstuur OTR versleuteld bericht</string> - <string name="send_pgp_message">Verstuur OpenPGP versleuteld bericht</string> + <string name="send_unencrypted_message">Verstuur onversleuteld bericht</string> + <string name="send_otr_message">Verstuur OTR-versleuteld bericht</string> + <string name="send_omemo_message">Verstuur OMEMO-versleuteld bericht</string> + <string name="send_omemo_x509_message">Verstuur v\\OMEMO-versleuteld bericht</string> + <string name="send_pgp_message">Verstuur OpenPGP-versleuteld bericht</string> <string name="your_nick_has_been_changed">Je naam is veranderd</string> <string name="send_unencrypted">Verstuur onversleuteld</string> <string name="decryption_failed">Ontsleutelen mislukt. Misschien heb je niet de juiste private sleutel.</string> <string name="openkeychain_required">OpenKeychain</string> <string name="openkeychain_required_long">Conversations gebruikt een derde partij app genaamd <b>OpenKeychain</b> om berichten te versleutelen en ontsleutelen, en om publieke sleutels te beheren.\n\nOpenKeychain is beschikbaar onder de GPLv3 en beschikbaar op F-Droid en Google Play.\n\n<small>(Herstart Conversations na installatie.)</small></string> - <string name="restart">Herstart</string> - <string name="install">Installeer</string> - <string name="offering">offering…</string> + <string name="restart">Herstarten</string> + <string name="install">Installeren</string> + <string name="openkeychain_not_installed">Gelieve OpenKeychain te installeren</string> + <string name="offering">bezig met aanbieden…</string> <string name="waiting">wachten…</string> - <string name="no_pgp_key">Geen OpenPGP sleutel gevonden</string> + <string name="no_pgp_key">Geen OpenPGP-sleutel gevonden</string> <string name="contact_has_no_pgp_key">Conversations kan je berichten niet versleutelen omdat je contact geen publieke sleutel heeft ingesteld.\n\n<small>Vraag je contact om OpenPGP te configureren.</small></string> - <string name="no_pgp_keys">Geen OpenPGP sleutels gevonden</string> + <string name="no_pgp_keys">Geen OpenPGP-sleutels gevonden</string> <string name="contacts_have_no_pgp_keys">Conversations kan je berichten niet versleutelen omdat je contacten geen publieke sleutel hebben ingesteld.\n\n<small>Vraag je contacten om OpenPGP te configureren.</small></string> - <string name="encrypted_message_received"><i>Versleuteld bericht ontvangen. Raak aan om te bekijken en te ontsleutelen.</i></string> + <string name="encrypted_message_received"><i>Versleuteld bericht ontvangen. Raak aan om te ontsleutelen.</i></string> <string name="pref_general">Algemeen</string> - <string name="pref_xmpp_resource">XMPP resource</string> - <string name="pref_xmpp_resource_summary">De naam waarmee deze client zich identificeert</string> - <string name="pref_accept_files">Accepteer bestanden</string> - <string name="pref_accept_files_size_summary">Accepteer automatisch bestanden kleiner dan…</string> + <string name="pref_xmpp_resource">XMPP-bron</string> + <string name="pref_xmpp_resource_summary">De naam waarmee deze cliënt zich identificeert</string> + <string name="pref_accept_files">Aanvaard bestanden</string> + <string name="pref_accept_files_summary">Aanvaard automatisch bestanden kleiner dan…</string> <string name="pref_notification_settings">Meldingsinstellingen</string> <string name="pref_notifications">Meldingen</string> <string name="pref_notifications_summary">Melding als een nieuw bericht arriveert</string> <string name="pref_vibrate">Trillen</string> <string name="pref_vibrate_summary">Tril ook wanneer een nieuw bericht arriveert</string> <string name="pref_sound">Geluid</string> - <string name="pref_sound_summary">Speel ringtone af bij melding</string> - <string name="pref_conference_notifications">Groepsgespreksmeldingen</string> - <string name="pref_conference_notifications_summary">Toon altijd meldingen als er nieuwe berichten arriveren in groepsgesprekken in plaats van alleen wanneer gemarkeerd</string> + <string name="pref_sound_summary">Speel beltoon af bij melding</string> <string name="pref_notification_grace_period">Uitstelperiode voor meldingen</string> - <string name="pref_notification_grace_period_summary">Zet meldingen voor korte tijd uit als er een carbon copy wordt ontvangen</string> + <string name="pref_notification_grace_period_summary">Schakel meldingen voor korte tijd uit als er een carbon copy wordt ontvangen</string> <string name="pref_advanced_options">Geavanceerde instellingen</string> - <string name="pref_never_send_crash">Verstuur nooit crash rapportages</string> - <string name="pref_never_send_crash_summary">Door crash rapportages te versturen help je de ontwikkeling van Conversations</string> + <string name="pref_never_send_crash">Verstuur nooit crashrapportages</string> + <string name="pref_never_send_crash_summary">Door crashrapportages te versturen help je de ontwikkeling van Conversations</string> <string name="pref_confirm_messages">Bevestig berichten</string> <string name="pref_confirm_messages_summary">Laat je contacten weten wanneer je berichten hebt ontvangen en gelezen</string> - <string name="pref_ui_options">UI opties</string> + <string name="pref_ui_options">UI-opties</string> <string name="openpgp_error">OpenKeychain rapporteerde een fout</string> - <string name="error_decrypting_file">I/O fout tijdens ontsleutelen bestand</string> - <string name="accept">Aanvaard</string> + <string name="error_decrypting_file">I/O-fout tijdens ontsleutelen van bestand</string> + <string name="accept">Aanvaarden</string> <string name="error">Er is een fout opgetreden</string> <string name="pref_grant_presence_updates">Verleen toestemming voor aanwezigheidsupdates</string> - <string name="pref_grant_presence_updates_summary">Op voorhand toestemming verlenen en vragen aan contacten die je hebt aangemaakt</string> + <string name="pref_grant_presence_updates_summary">Op voorhand toestemming voor aanwezigheidsabonnementen verlenen en vragen aan contacten die je hebt aangemaakt</string> <string name="subscriptions">Abonnementen</string> <string name="your_account">Je account</string> <string name="keys">Sleutels</string> <string name="send_presence_updates">Verstuur aanwezigheidsupdates</string> <string name="receive_presence_updates">Ontvang aanwezigheidsupdates</string> <string name="ask_for_presence_updates">Vraag naar aanwezigheidsupdates</string> - <string name="attach_choose_picture">Kies afbeelding</string> - <string name="attach_take_picture">Neem foto</string> + <string name="attach_choose_picture">Afbeelding kiezen</string> + <string name="attach_take_picture">Foto nemen</string> <string name="preemptively_grant">Op voorhand toestemming verlenen voor abonneren</string> <string name="error_not_an_image_file">Het bestand dat je gekozen hebt is geen afbeelding</string> <string name="error_compressing_image">Fout tijdens converteren van afbeelding</string> <string name="error_file_not_found">Bestand niet gevonden</string> - <string name="error_io_exception">Algemene I/O fout. Misschien is er geen opslagruimte meer beschikbaar?</string> - <string name="error_security_exception_during_image_copy">De app die je gebruikte om de afbeelding te selecteren heeft niet voldoende toegang geleverd om het bestand te lezen.\n\n<small>Gebruik een andere app om een afbeelding te kiezen</small></string> + <string name="error_io_exception">Algemene I/O-fout. Misschien is er geen opslagruimte meer beschikbaar?</string> + <string name="error_security_exception_during_image_copy">De app die je gebruikte om de afbeelding te selecteren heeft niet voldoende toegang geleverd om het bestand te lezen.\n\n<small>Gebruik een andere bestandsbeheerder om een afbeelding te kiezen</small></string> <string name="account_status_unknown">Onbekend</string> - <string name="account_status_disabled">Tijdelijk uitgezet</string> + <string name="account_status_disabled">Tijdelijk uitgeschakeld</string> <string name="account_status_online">Online</string> <string name="account_status_connecting">Verbinden\u2026</string> <string name="account_status_offline">Offline</string> @@ -144,33 +146,34 @@ <string name="account_status_not_found">Server niet gevonden</string> <string name="account_status_no_internet">Geen verbinding</string> <string name="account_status_regis_fail">Registratie mislukt</string> - <string name="account_status_regis_conflict">Gebruikersnaam bezet</string> - <string name="account_status_regis_success">Registratie compleet</string> + <string name="account_status_regis_conflict">Gebruikersnaam is al in gebruik</string> + <string name="account_status_regis_success">Registratie voltooid</string> <string name="account_status_regis_not_sup">Server ondersteunt geen registratie</string> <string name="account_status_security_error">Fout bij beveiliging</string> <string name="account_status_incompatible_server">Incompatibele server</string> - <string name="encryption_choice_none">Onversleuteld</string> + <string name="encryption_choice_unencrypted">Onversleuteld</string> <string name="encryption_choice_otr">OTR</string> <string name="encryption_choice_pgp">OpenPGP</string> - <string name="mgmt_account_edit">Bewerk account</string> - <string name="mgmt_account_delete">Verwijder</string> - <string name="mgmt_account_disable">Tijdelijk uitzetten</string> - <string name="mgmt_account_publish_avatar">Publish avatar</string> - <string name="mgmt_account_publish_pgp">Publish OpenPGP public key</string> - <string name="mgmt_account_enable">Aanzetten</string> + <string name="encryption_choice_omemo">OMEMO</string> + <string name="mgmt_account_edit">Account bewerken</string> + <string name="mgmt_account_delete">Account verwijderen</string> + <string name="mgmt_account_disable">Tijdelijk uitschakelen</string> + <string name="mgmt_account_publish_avatar">Avatar publiceren</string> + <string name="mgmt_account_publish_pgp">Publiceer OpenPGP publieke sleutel</string> + <string name="mgmt_account_enable">Account inschakelen</string> <string name="mgmt_account_are_you_sure">Ben je zeker?</string> <string name="mgmt_account_delete_confirm_text">Als je je account verwijdert wordt je volledige gespreksgeschiedenis gewist</string> - <string name="attach_record_voice">Neem stem op</string> - <string name="account_settings_jabber_id">Jabber ID:</string> + <string name="attach_record_voice">Stem opnemen</string> + <string name="account_settings_jabber_id">Jabber-ID:</string> <string name="account_settings_password">Wachtwoord:</string> <string name="account_settings_example_jabber_id">gebruikersnaam@voorbeeld.nl</string> - <string name="account_settings_confirm_password">Bevestig wachtwoord:</string> + <string name="account_settings_confirm_password">Wachtwoord bevestigen</string> <string name="password">Wachtwoord</string> - <string name="confirm_password">Bevestig wachtwoord</string> + <string name="confirm_password">Wachtwoord bevestigen</string> <string name="passwords_do_not_match">Wachtwoorden komen niet overeen</string> - <string name="invalid_jid">Dit is geen geldig Jabber ID</string> + <string name="invalid_jid">Dit is geen geldige Jabber-ID</string> <string name="error_out_of_memory">Geen geheugen beschikbaar. Afbeelding is te groot</string> - <string name="add_phone_book_text">Wil je %s toevoegen aan de contactenlijst op je telefoon?</string> + <string name="add_phone_book_text">Wil je %s toevoegen aan je adresboek?</string> <string name="contact_status_online">online</string> <string name="contact_status_free_to_chat">beschikbaar</string> <string name="contact_status_away">weg</string> @@ -179,17 +182,18 @@ <string name="contact_status_offline">offline</string> <string name="muc_details_conference">Groepsgesprek</string> <string name="muc_details_other_members">Andere leden</string> - <string name="server_info_show_more">Server info</string> + <string name="server_info_show_more">Server-info</string> <string name="server_info_mam">XEP-0313: MAM</string> <string name="server_info_carbon_messages">XEP-0280: Message Carbons</string> <string name="server_info_csi">XEP-0352: Client State Indication</string> <string name="server_info_blocking">XEP-0191: Blocking Command</string> <string name="server_info_roster_version">XEP-0237: Roster Versioning</string> <string name="server_info_stream_management">XEP-0198: Stream Management</string> - <string name="server_info_pep">XEP-0163: PEP (Avatars)</string> + <string name="server_info_pep">XEP-0163: PEP (Avatars / OMEMO)</string> + <string name="server_info_http_upload">XEP-0363: HTTP File Upload</string> <string name="server_info_available">beschikbaar</string> <string name="server_info_unavailable">niet beschikbaar</string> - <string name="missing_public_keys">Ontbrekende publieke sleutel aankondigingen</string> + <string name="missing_public_keys">Ontbrekende publieke sleutel-aankondigingen</string> <string name="last_seen_now">zonet voor het laatst gezien</string> <string name="last_seen_min">1 minuut geleden voor het laatst gezien</string> <string name="last_seen_mins">%d minuten geleden voor het laatst gezien</string> @@ -199,22 +203,33 @@ <string name="last_seen_days">%d dagen geleden voor het laatst gezien</string> <string name="never_seen">nog nooit gezien</string> <string name="install_openkeychain">Versleuteld bericht. Installeer OpenKeychain om te ontsleutelen.</string> - <string name="unknown_otr_fingerprint">Onbekende OTR vingerafdruk</string> + <string name="unknown_otr_fingerprint">Onbekende OTR-vingerafdruk</string> <string name="openpgp_messages_found">OpenPGP-versleutelde berichten gevonden</string> <string name="reception_failed">Ontvangen mislukt</string> <string name="your_fingerprint">Jouw vingerafdruk</string> - <string name="otr_fingerprint">OTR vingerafdruk</string> - <string name="verify">Bevestig</string> - <string name="decrypt">Ontsleutel</string> + <string name="otr_fingerprint">OTR-vingerafdruk</string> + <string name="omemo_fingerprint">OMEMO-vingerafdruk</string> + <string name="omemo_fingerprint_x509">v\\OMEMO-vingerafdruk</string> + <string name="omemo_fingerprint_selected_message">OMEMO-vingerafdruk van bericht</string> + <string name="omemo_fingerprint_x509_selected_message">v\\OMEMO-vingerafdruk van bericht</string> + <string name="this_device_omemo_fingerprint">Eigen OMEMO-vingerafdruk</string> + <string name="other_devices">Andere apparaten</string> + <string name="trust_omemo_fingerprints">Vertrouw OMEMO-vingerafdrukken</string> + <string name="fetching_keys">Sleutels ophalen…</string> + <string name="done">Klaar</string> + <string name="verify">Bevestigen</string> + <string name="decrypt">Ontsleutelen</string> <string name="conferences">Groepsgesprekken</string> <string name="search">Zoeken</string> - <string name="create_contact">Maak contact aan</string> + <string name="create_contact">Contact aanmaken</string> + <string name="enter_contact">Contact invoeren</string> <string name="join_conference">Aan groepsgesprek deelnemen</string> - <string name="delete_contact">Verwijder contact</string> - <string name="view_contact_details">Bekijk contactgegevens</string> - <string name="block_contact">Blokkeer contact</string> - <string name="unblock_contact">Deblokkeer contact</string> + <string name="delete_contact">Contact verwijderen</string> + <string name="view_contact_details">Contactgegevens bekijken</string> + <string name="block_contact">Contact blokkeren</string> + <string name="unblock_contact">Contact deblokkeren</string> <string name="create">Aanmaken</string> + <string name="select">Selecteren</string> <string name="contact_already_exists">Het contact bestaat al</string> <string name="join">Deelnemen</string> <string name="conference_address">Gespreksadres</string> @@ -223,15 +238,15 @@ <string name="delete_bookmark">Bladwijzer verwijderen</string> <string name="bookmark_already_exists">Deze bladwijzer bestaat al</string> <string name="you">Jij</string> - <string name="action_edit_subject">Onderwerp groepsgesprek bewerken</string> + <string name="action_edit_subject">Onderwerp van groepsgesprek bewerken</string> <string name="conference_not_found">Groepsgesprek niet gevonden</string> <string name="leave">Verlaten</string> <string name="contact_added_you">Contact heeft je toegevoegd aan zijn/haar contacten</string> <string name="add_back">Contact toevoegen aan eigen contacten</string> <string name="contact_has_read_up_to_this_point">%s heeft tot hier gelezen</string> - <string name="publish">Publiceer</string> + <string name="publish">Publiceren</string> <string name="touch_to_choose_picture">Raak avatar aan om een foto uit de galerij te kiezen</string> - <string name="publish_avatar_explanation"><b>Aandacht:</b> Iedereen die je aanwezigheidsupdates ontvangt zal deze foto kunnen zien.</string> + <string name="publish_avatar_explanation">Let op: iedereen die je aanwezigheidsupdates ontvangt zal deze foto kunnen zien.</string> <string name="publishing">Publiceren…</string> <string name="error_publish_avatar_server_reject">De server weigerde de publicatie van je afbeelding</string> <string name="error_publish_avatar_converting">Fout bij converteren van afbeelding</string> @@ -240,19 +255,18 @@ <string name="error_publish_avatar_no_server_support">Je server ondersteunt de publicatie van avatars niet</string> <string name="private_message">gefluisterd</string> <string name="private_message_to">naar %s</string> - <string name="send_private_message_to">Stuur privébericht naar %s</string> + <string name="send_private_message_to">Privébericht sturen naar %s</string> <string name="connect">Verbinden</string> <string name="account_already_exists">Deze account bestaat al</string> <string name="next">Volgende</string> <string name="server_info_session_established">Huidige sessie gevestigd</string> <string name="additional_information">Bijkomstige informatie</string> <string name="skip">Overslaan</string> - <string name="disable_notifications">Meldingen uitzetten</string> - <string name="disable_notifications_for_this_conversation">Meldingen uitzetten voor dit gesprek</string> - <string name="notifications_disabled">Meldingen zijn uitgezet</string> - <string name="enable">Aanzetten</string> + <string name="disable_notifications">Meldingen uitschakelen</string> + <string name="disable_notifications_for_this_conversation">Meldingen uitschakelen voor dit gesprek</string> + <string name="enable">Inschakelen</string> <string name="conference_requires_password">Wachtwoord nodig voor toegang tot groepsgesprek</string> - <string name="enter_password">Wachtwoord:</string> + <string name="enter_password">Wachtwoord invoeren</string> <string name="missing_presence_updates">Ontbrekende aanwezigheidsupdates van contact</string> <string name="request_presence_updates">Vraag eerst aanwezigheidsupdates van je contact aan.\n\n<small>Dit wordt gebruikt om te bepalen welke client(s) je contact gebruikt.</small></string> <string name="request_now">Nu aanvragen</string> @@ -261,10 +275,10 @@ <string name="ignore">Negeren</string> <string name="without_mutual_presence_updates"><b>Waarschuwing:</b> Dit verzenden zonder wederzijdse aanwezigheidsupdates kan voor onverwachte problemen zorgen.\n\n<small>Ga naar contactgegevens om je aanwezigheidsupdates te bevestigen.</small></string> <string name="pref_encryption_settings">Versleutelingsinstellingen</string> - <string name="pref_force_encryption">Verplicht end-to-end versleuteling</string> + <string name="pref_force_encryption">Verplicht end-to-end-versleuteling</string> <string name="pref_force_encryption_summary">Stuur berichten altijd versleuteld (behalve in groepsgesprekken)</string> <string name="pref_dont_save_encrypted">Sla versleutelde berichten niet op</string> - <string name="pref_dont_save_encrypted_summary"><b>Waarschuwing:</b> Dit kan leiden tot verlies van berichten</string> + <string name="pref_dont_save_encrypted_summary">Waarschuwing: dit kan leiden tot verlies van berichten</string> <string name="pref_expert_options">Expert-instellingen</string> <string name="pref_expert_options_summary">Wees voorzichtig met deze instellingen</string> <string name="title_activity_about">Over Conversations</string> @@ -272,7 +286,7 @@ <string name="title_pref_quiet_hours">Stille uren</string> <string name="title_pref_quiet_hours_start_time">Begintijd</string> <string name="title_pref_quiet_hours_end_time">Eindtijd</string> - <string name="title_pref_enable_quiet_hours">Stille uren aanzetten</string> + <string name="title_pref_enable_quiet_hours">Stille uren inschakelen</string> <string name="pref_quiet_hours_summary">Tijdens stille uren worden meldingen onderdrukt</string> <string name="pref_use_larger_font">Vergroot lettergrootte</string> <string name="pref_use_larger_font_summary">Gebruik grotere lettertypes over de hele app</string> @@ -283,30 +297,31 @@ <string name="pref_expert_options_other">Andere</string> <string name="pref_conference_name">Groepsgespreksnaam</string> <string name="pref_conference_name_summary">Gebruik onderwerp van kamer ipv JID om groepsgesprekken te identificeren</string> - <string name="toast_message_otr_fingerprint">OTR vingerafdruk naar klembord gekopieerd!</string> + <string name="toast_message_otr_fingerprint">OTR-vingerafdruk gekopieerd naar klembord!</string> + <string name="toast_message_omemo_fingerprint">OMEMO-vingerafdruk gekopieerd naar klembord!</string> <string name="conference_banned">Je bent verbannen uit dit groepsgesprek</string> <string name="conference_members_only">Dit groepsgesprek is enkel voor leden</string> <string name="conference_kicked">Je bent uit dit groepsgesprek geschopt</string> - <string name="using_account">account %s gebruiken</string> + <string name="using_account">met account %s</string> + <string name="checking_x">%s op HTTP-host nakijken</string> <string name="not_connected_try_again">Je bent niet verbonden. Probeer later opnieuw</string> - <string name="check_x_filesize">Bekijk bestandsgrootte van %s</string> + <string name="check_x_filesize">Bestandsgrootte van %s controleren</string> <string name="message_options">Berichtopties</string> - <string name="copy_text">Kopieer tekst</string> - <string name="copy_original_url">Kopieer oorspronkelijke URL</string> - <string name="send_again">Verstuur opnieuw</string> + <string name="copy_text">Tekst kopiëren</string> + <string name="copy_original_url">Oorspronkelijke URL kopiëren</string> + <string name="send_again">Opnieuw versturen</string> <string name="file_url">Bestands-URL</string> <string name="message_text">Berichttekst</string> <string name="url_copied_to_clipboard">URL gekopieerd naar klembord</string> <string name="message_copied_to_clipboard">Bericht gekopieerd naar klembord</string> <string name="image_transmission_failed">Versturen van afbeelding mislukt</string> - <string name="scan_qr_code">Scan QR code</string> - <string name="show_qr_code">Toon QR code</string> - <string name="show_block_list">Toon geblokkeerde contacten</string> + <string name="scan_qr_code">QR-code scannen</string> + <string name="show_qr_code">QR-code tonen</string> + <string name="show_block_list">Geblokkeerde contacten weergeven</string> <string name="account_details">Accountgegevens</string> - <string name="verify_otr">Bevestig OTR</string> + <string name="verify_otr">OTR bevestigen</string> <string name="remote_fingerprint">Externe vingerafdruk</string> <string name="scan">scan</string> - <string name="or_touch_phones">(of raak gsm\'s aan)</string> <string name="smp">Socialist Millionaire Protocol</string> <string name="shared_secret_hint">Hint of vraag</string> <string name="shared_secret_secret">Gedeeld geheim</string> @@ -321,17 +336,20 @@ <string name="smp_requested">Contact vraagt SMP-bevestiging</string> <string name="no_otr_session_found">Geen geldige OTR-sessie gevonden!</string> <string name="conversations_foreground_service">Conversations</string> - <string name="pref_keep_foreground_service">Hou service in voorgrond</string> + <string name="pref_keep_foreground_service">Dienst in voorgrond houden</string> <string name="pref_keep_foreground_service_summary">Belet het besturingssysteem van je verbinding te onderbreken</string> - <string name="choose_file">Kies bestand</string> + <string name="pref_export_logs">Exporteer logs</string> + <string name="pref_export_logs_summary">Schrijf logs naar SD-kaart</string> + <string name="notification_export_logs_title">Logs schrijven naar SD-kaart</string> + <string name="choose_file">Bestand kiezen</string> <string name="receiving_x_file">Ontvangen van %1$s (%2$d%% voltooid)</string> - <string name="download_x_file">Download %s</string> + <string name="download_x_file">%s downloaden</string> <string name="file">bestand</string> - <string name="open_x_file">Open %s</string> + <string name="open_x_file">%s openen</string> <string name="sending_file">versturen (%1$d%% voltooid)</string> <string name="preparing_file">Bestand klaarmaken voor versturen</string> <string name="x_file_offered_for_download">%s aangeboden om te downloaden</string> - <string name="cancel_transmission">Annuleer bestandsoverdracht</string> + <string name="cancel_transmission">Bestandsoverdracht annuleren</string> <string name="file_transmission_failed">bestandsoverdracht mislukt</string> <string name="file_deleted">Het bestand is verwijderd</string> <string name="no_application_found_to_open_file">Geen applicatie om bestand te openen</string> @@ -340,7 +358,7 @@ <string name="are_you_sure_verify_fingerprint">Ben je zeker dat je de OTR-vingerafdruk van je contact wil bevestigen?</string> <string name="pref_show_dynamic_tags">Toon dynamische tags</string> <string name="pref_show_dynamic_tags_summary">Toon enkel-lezen tags onder contacten</string> - <string name="enable_notifications">Meldingen aanzetten</string> + <string name="enable_notifications">Meldingen inschakelen</string> <string name="conference_with">Groepsgesprek aanmaken met…</string> <string name="no_conference_server_found">Geen groepsgespreksserver gevonden</string> <string name="conference_creation_failed">Aanmaken van groepsgesprek mislukt!</string> @@ -348,7 +366,18 @@ <string name="secret_accepted">Geheim aanvaard!</string> <string name="reset">Opnieuw instellen</string> <string name="account_image_description">Account-avatar</string> - <string name="copy_otr_clipboard_description">Kopieer OTR-vingerafdruk naar klembord</string> + <string name="copy_otr_clipboard_description">OTR-vingerafdruk kopiëren naar klembord</string> + <string name="copy_omemo_clipboard_description">OMEMO-vingerafdruk kopiëren naar klembord</string> + <string name="regenerate_omemo_key">OMEMO-sleutel opnieuw aanmaken</string> + <string name="wipe_omemo_pep">Andere apparaten van PEP verwijderen</string> + <string name="clear_other_devices">Apparaten wissen</string> + <string name="clear_other_devices_desc">Ben je zeker dat je alle andere apparaten van de OMEMO-aankondiging wil wissen? De volgende keer dat je apparaten verbinding maken zullen ze zich opnieuw aankondigen, maar zullen ze misschien niet de berichten ontvangen die intussen zijn verzonden.</string> + <string name="purge_key">Sleutel verwijderen</string> + <string name="purge_key_desc_part1">Ben je zeker dat je deze sleutel wil verwijderen?</string> + <string name="purge_key_desc_part2">Ze zal onherroepelijk beschouwd worden als gecompromitteerd en je zal er nooit meer een nieuwe sessie mee kunnen bouwen.</string> + <string name="error_no_keys_to_trust_server_error">Er zijn geen bruikbare sleutels beschikbaar voor dit contact.\nHet ophalen van nieuwe sleutels van de server is mislukt. Misschien is er iets mis met de server van je contact.</string> + <string name="error_no_keys_to_trust">Er zijn geen bruikbare sleutels beschikbaar voor dit contact. Als je zijn/haar sleutels hebt verwijderd dient hij/zij nieuwe aan te maken.</string> + <string name="error_trustkeys_title">Fout</string> <string name="fetching_history_from_server">Geschiedenis van server halen</string> <string name="no_more_history_on_server">Geen verdere geschiedenis op server</string> <string name="updating">Bijwerken…</string> @@ -365,8 +394,8 @@ <string name="current_password">Huidig wachtwoord</string> <string name="new_password">Nieuw wachtwoord</string> <string name="password_should_not_be_empty">Wachtwoord zou niet leeg mogen zijn</string> - <string name="enable_all_accounts">Alle accounts aanzetten</string> - <string name="disable_all_accounts">Alle accounts uitzetten</string> + <string name="enable_all_accounts">Alle accounts inschakelen</string> + <string name="disable_all_accounts">Alle accounts uitschakelen</string> <string name="perform_action_with">Actie uitvoeren met</string> <string name="no_affiliation">Geen aansluiting</string> <string name="no_role">Geen rol</string> @@ -377,7 +406,7 @@ <string name="remove_membership">Lidmaatschap verwijderen</string> <string name="grant_admin_privileges">Administratorprivileges verlenen</string> <string name="remove_admin_privileges">Administratorprivileges verwijderen</string> - <string name="remove_from_room">Verwijderen uit kamer</string> + <string name="remove_from_room">Verwijderen uit groepsgesprek</string> <string name="could_not_change_affiliation">Kon aansluiting niet wijzigen</string> <string name="ban_from_conference">Verbannen uit groepsgesprek</string> <string name="removing_from_public_conference">Je probeert %s te verwijderen uit een publiek groepsgesprek. De enige manier om dat te doen is door hem/haar permanent te verbannen.</string> @@ -386,8 +415,10 @@ <string name="public_conference">Publiek toegankelijk groepsgesprek</string> <string name="private_conference">Privé groepsgesprek, enkel toegankelijk voor leden</string> <string name="conference_options">Groepsgespreksopties</string> - <string name="members_only">Privé (alleen leden)</string> + <string name="members_only">Privé, enkel leden</string> <string name="non_anonymous">Niet anoniem</string> + <string name="moderated">Gemodereerd</string> + <string name="you_are_not_participating">Je neemt geen deel</string> <string name="modified_conference_options">Groepsgespreksopties aangepast!</string> <string name="could_not_modify_conference_options">Kon groepsgespreksopties niet aanpassen</string> <string name="never">Nooit</string> @@ -396,7 +427,7 @@ <string name="two_hours">2 uur</string> <string name="eight_hours">8 uur</string> <string name="until_further_notice">Voor onbepaalde duur</string> - <string name="pref_input_options">Input-opties</string> + <string name="pref_input_options">Invoer-opties</string> <string name="pref_enter_is_send">Enter is versturen</string> <string name="pref_enter_is_send_summary">Gebruik de enter-toets om berichten te versturen</string> <string name="pref_display_enter_key">Toon enter-toets</string> @@ -408,33 +439,32 @@ <string name="apk">Android-applicatie</string> <string name="vcard">Contact</string> <string name="received_x_file">%s ontvangen</string> - <string name="disable_foreground_service">Voorgrond-service uitzetten</string> + <string name="disable_foreground_service">Voorgronddienst uitschakelen</string> <string name="touch_to_open_conversations">Raak aan om Conversations te openen</string> <string name="avatar_has_been_published">Avatar is gepubliceerd!</string> <string name="sending_x_file">Bezig met versturen van %s</string> <string name="offering_x_file">Bezig met aanbieden van %s</string> - <string name="hide_offline">Offline verbergen</string> - <string name="disable_account">Account uitzetten</string> - <string name="contact_is_typing">%s is aan het typen...</string> + <string name="hide_offline">Offline contacten verbergen</string> + <string name="disable_account">Account uitschakelen</string> + <string name="contact_is_typing">%s is aan het typen…</string> <string name="contact_has_stopped_typing">%s is gestopt met typen</string> - <string name="pref_chat_states">Type-meldingen</string> + <string name="pref_chat_states">Aan-het-typen-meldingen</string> <string name="pref_chat_states_summary">Laat je contacten weten wanneer je een nieuw bericht aan het schrijven bent</string> <string name="send_location">Locatie versturen</string> <string name="show_location">Locatie weergeven</string> - <string name="no_application_found_to_display_location">Geen applicatie gevonden om locatie weer te geven</string> + <string name="no_application_found_to_display_location">Geen applicatie om locatie weer te geven</string> <string name="location">Locatie</string> <string name="received_location">Locatie ontvangen</string> <string name="title_undo_swipe_out_conversation">Gesprek gesloten</string> <string name="title_undo_swipe_out_muc">Groepsgesprek verlaten</string> - <string name="pref_certificate_options">Certificaatopties</string> - <string name="pref_dont_trust_system_cas_title">Vertrouw geen systeem-CA\'s.</string> + <string name="pref_dont_trust_system_cas_title">Systeem-CA\'s niet vertrouwen</string> <string name="pref_dont_trust_system_cas_summary">Alle certificaten moeten handmatig goedgekeurd worden</string> - <string name="pref_remove_trusted_certificates_title">Verwijder certificaten</string> - <string name="pref_remove_trusted_certificates_summary">Verwijder handmatig goedgekeurde certificaten</string> + <string name="pref_remove_trusted_certificates_title">Certificaten verwijderen</string> + <string name="pref_remove_trusted_certificates_summary">Handmatig goedgekeurde certificaten verwijderen</string> <string name="toast_no_trusted_certs">Geen handmatig goedgekeurde certificaten</string> - <string name="dialog_manage_certs_title">Verwijder certificaten</string> - <string name="dialog_manage_certs_positivebutton">Verwijder selectie</string> - <string name="dialog_manage_certs_negativebutton">Annuleer</string> + <string name="dialog_manage_certs_title">Certificaten verwijderen</string> + <string name="dialog_manage_certs_positivebutton">Selectie verwijderen</string> + <string name="dialog_manage_certs_negativebutton">Annuleren</string> <plurals name="toast_delete_certificates"> <item quantity="one">%d certificaat verwijderd</item> <item quantity="other">%d certificaten verwijderd</item> @@ -447,6 +477,74 @@ <string name="pref_quick_action">Snelle actie</string> <string name="none">Geen</string> <string name="recently_used">Recent gebruikt</string> - <string name="choose_quick_action">Kies snelle actie</string> - <string name="file_not_found_on_remote_host">Bestand niet gevonden op externe server</string> + <string name="choose_quick_action">Snelle actie kiezen</string> + <string name="search_for_contacts_or_groups">Zoeken naar contacten of groepen</string> + <string name="send_private_message">Privébericht sturen</string> + <string name="user_has_left_conference">%s heeft het groepsgesprek verlaten!</string> + <string name="username">Gebruikersnaam</string> + <string name="username_hint">Gebruikersnaam</string> + <string name="invalid_username">Dit is geen geldige gebruikersnaam</string> + <string name="download_failed_server_not_found">Downloaden mislukt: server niet gevonden</string> + <string name="download_failed_file_not_found">Downloaden mislukt: bestand niet gevonden</string> + <string name="download_failed_could_not_connect">Downloaden mislukt: kon geen verbinding maken met host</string> + <string name="pref_use_white_background">Gebruik witte achtergrond</string> + <string name="pref_use_white_background_summary">Toon ontvangen berichten als zwarte tekst op een witte achtergrond</string> + <string name="account_status_tor_unavailable">Tor-netwerk niet beschikbaar</string> + <string name="server_info_broken">Gebroken</string> + <string name="pref_presence_settings">Aanwezigheidsinstellingen</string> + <string name="pref_away_when_screen_off">Even weg wanneer scherm uit staat</string> + <string name="pref_away_when_screen_off_summary">Stelt je bron in als even weg wanneer het scherm uitgeschakeld is</string> + <string name="pref_xa_on_silent_mode">Niet beschikbaar in stille modus</string> + <string name="pref_xa_on_silent_mode_summary">Stelt je bron in als niet beschikbaar wanneer je apparaat in stille modus staat</string> + <string name="action_add_account_with_certificate">Account met certificaat toevoegen</string> + <string name="unable_to_parse_certificate">Kan certificaat niet verwerken</string> + <string name="authenticate_with_certificate">Laat leeg om te authenticeren met certificaat</string> + <string name="captcha_ocr">Captcha-tekst</string> + <string name="captcha_required">Captcha vereist</string> + <string name="captcha_hint">voer de tekst van de afbeelding in</string> + <string name="certificate_chain_is_not_trusted">Certificaatsketen is niet vertrouwd</string> + <string name="jid_does_not_match_certificate">Jabber-ID komt niet overeen met certificaat</string> + <string name="action_renew_certificate">Certificaat vernieuwen</string> + <string name="error_fetching_omemo_key">Fout bij ophalen van OMEMO-sleutel!</string> + <string name="verified_omemo_key_with_certificate">OMEMO-sleutel geverifieerd met certificaat!</string> + <string name="device_does_not_support_certificates">Je apparaat ondersteunt de selectie van cliënt-certificaten niet!</string> + <string name="pref_connection_options">Verbindingsopties</string> + <string name="pref_use_tor">Verbinden via Tor</string> + <string name="pref_use_tor_summary">Tunnel alle verbindingen door het Tor-netwerk. Vereist Orbot</string> + <string name="account_settings_hostname">Hostnaam</string> + <string name="account_settings_port">Poort</string> + <string name="hostname_or_onion">Server- of .onion-adres</string> + <string name="not_a_valid_port">Dit is geen geldig poortnummer</string> + <string name="not_valid_hostname">Dit is geen geldige hostnaam</string> + <string name="connected_accounts">%1$d van %2$d accounts verbonden</string> + <plurals name="x_messages"> + <item quantity="one">%d bericht</item> + <item quantity="other">%d berichten</item> + </plurals> + <string name="shared_file_with_x">Bestand gedeeld met %s</string> + <string name="shared_image_with_x">Afbeelding gedeeld met %s</string> + <string name="no_storage_permission">Conversations heeft toegang nodig tot de externe opslag</string> + <string name="sync_with_contacts">Synchroniseer met contacten</string> + <string name="sync_with_contacts_long">Conversations wil je XMPP-rooster met je contacten vergelijken om hun volledige namen en profielfoto\'s te tonen.\n\nConversations zal je contacten enkel lokaal lezen en vergelijken zonder ze te uploaden naar je server.\n\nJe zal nu gevraagd worden Conversations toegang te verlenen tot je contacten.</string> + <string name="certificate_information">Certificaatinformatie</string> + <string name="certificate_subject">Onderwerp</string> + <string name="certificate_issuer">Uitgever</string> + <string name="certificate_cn">Algemene naam</string> + <string name="certificate_o">Organisatie</string> + <string name="certificate_sha1">SHA1</string> + <string name="certicate_info_not_available">(Niet beschikbaar)</string> + <string name="certificate_not_found">Geen certificaat gevonden</string> + <string name="notify_on_all_messages">Melding bij alle berichten</string> + <string name="notify_only_when_highlighted">Melding enkel wanneer vermeld</string> + <string name="notify_never">Meldingen uitgeschakeld</string> + <string name="notify_paused">Meldingen gepauzeerd</string> + <string name="pref_picture_compression">Afbeeldingen comprimeren</string> + <string name="pref_picture_compression_summary">Afbeeldingen verkleinen en comprimeren</string> + <string name="always">Altijd</string> + <string name="automatically">Automatisch</string> + <string name="battery_optimizations_enabled">Batterij-optimalisaties ingeschakeld</string> + <string name="battery_optimizations_enabled_explained">Je apparaat voert sterke batterij-optimalisaties uit op Conversations, die kunnen leiden tot vertraagde meldingen of zelfs verlies van berichten.\nHet is aangeraden deze optimalisaties uit te schakelen.</string> + <string name="battery_optimizations_enabled_dialog">Je apparaat voert sterke batterij-optimalisaties uit op Conversations, die kunnen leiden tot vertraagde meldingen of zelfs verlies van berichten.\nJe zal nu gevraagd worden deze optimalisaties uit te schakelen.</string> + <string name="disable">Uitschakelen</string> + <string name="selection_too_large">Het gekozen vlak is te groot</string> </resources> diff --git a/src/main/res/values-pl/strings.xml b/src/main/res/values-pl/strings.xml index ea2ecc52..518a4a3a 100644 --- a/src/main/res/values-pl/strings.xml +++ b/src/main/res/values-pl/strings.xml @@ -9,7 +9,6 @@ <string name="action_secure">Konwersacja szyfrowana</string> <string name="action_add_account">Dodaj konto</string> <string name="action_edit_contact">Edytuj nazwę</string> - <string name="action_add_phone_book">Dodaj do książki telefonicznej</string> <string name="action_delete_contact">Usuń z rostera</string> <string name="action_block_contact">Zablokuj kontakt</string> <string name="action_unblock_contact">Odblokuj kontakt</string> @@ -28,7 +27,6 @@ <string name="minutes_ago">%d minut temu</string> <string name="unread_conversations">nieprzeczytanych konwersacji</string> <string name="sending">wysyłanie...</string> - <string name="encrypted_message">Deszyfrowanie wiadomości. Proszę czekać...</string> <string name="nick_in_use">Nazwa jest już w użyciu</string> <string name="admin">Admin</string> <string name="owner">Właściciel</string> @@ -74,10 +72,10 @@ <string name="clear_conversation_history">Wyczyść historię konwersacji</string> <string name="clear_histor_msg">Czy na pewno usunąć wszystkie wiadomości powiązane z konwersacją?\n\n<b>Uwaga:</b> Działanie nie wpływa na wiadomości przechowywane na innych urządzeniach lub serwerach.</string> <string name="delete_messages">Usuń wiadomości</string> - <string name="also_end_conversation">Zakończ konwersację po usunięciu historii</string> <string name="choose_presence">Wybierz widoczność dla kontaktu</string> - <string name="send_plain_text_message">Wyślij wiadomość jawną</string> + <string name="send_unencrypted_message">Wyślij wiadomość bez szyfrowania</string> <string name="send_otr_message">Wyślij zaszyfrowaną wiadomość (OTR)</string> + <string name="send_omemo_message">Wyślij wiadomość zaszyfrowaną OMEMO</string> <string name="send_pgp_message">Wyślij zaszyfrowaną wiadomość (OpenPGP)</string> <string name="your_nick_has_been_changed">Twoja nazwa została zmieniona</string> <string name="send_unencrypted">Wyślij bez szyfrowania</string> @@ -92,7 +90,6 @@ <string name="contact_has_no_pgp_key">Conversations nie może zaszyfrować wiadomości, ponieważ kontakt nie udostępnia klucza publicznego.\n\n<small>Zasugeruj rozmówcy instalację OpenPGP.</small></string> <string name="no_pgp_keys">Nie znaleziono kluczy OpenPGP</string> <string name="contacts_have_no_pgp_keys">Conversations nie może zaszyfrować wiadomości, ponieważ kontakty nie udostępniają kluczy publicznych.\n\n<small>Zasugeruj rozmówcom instalację OpenPGP.</small></string> - <string name="encrypted_message_received"><i>Otrzymano zaszyfrowaną wiadomość. Dotknij, aby odszyfrować i wyświetlić.</i></string> <string name="pref_general">Główne</string> <string name="pref_xmpp_resource">Zasób XMPP</string> <string name="pref_xmpp_resource_summary">Nazwa identyfikująca urządzenie</string> @@ -105,8 +102,6 @@ <string name="pref_vibrate_summary">Wibruj, gdy nadejdzie wiadomość</string> <string name="pref_sound">Dźwięk</string> <string name="pref_sound_summary">Odtwórz dźwięk z powiadomieniem</string> - <string name="pref_conference_notifications">Powiadomienia konferencji</string> - <string name="pref_conference_notifications_summary">Zawsze powiadamiaj o nowej wiadomości w konferencji</string> <string name="pref_notification_grace_period">Opóźnienie powiadomień</string> <string name="pref_notification_grace_period_summary">Wyłącz powiadomienia przez krótki czas po otrzymaniu kopii wiadomości</string> <string name="pref_advanced_options">Opcje zaawansowane</string> @@ -149,9 +144,10 @@ <string name="account_status_regis_not_sup">Serwer nie umożliwia rejestracji</string> <string name="account_status_security_error">Błąd zabezpieczeń</string> <string name="account_status_incompatible_server">Serwer niekompatybilny</string> - <string name="encryption_choice_none">Tekst jawny</string> + <string name="encryption_choice_unencrypted">Bez szyfrowania</string> <string name="encryption_choice_otr">OTR</string> <string name="encryption_choice_pgp">OpenPGP</string> + <string name="encryption_choice_omemo">OMEMO</string> <string name="mgmt_account_edit">Edytuj konto</string> <string name="mgmt_account_delete">Usuń konto</string> <string name="mgmt_account_disable">Wyłącz tymczasowo</string> @@ -170,7 +166,6 @@ <string name="passwords_do_not_match">Hasła są niezgodne</string> <string name="invalid_jid">Wprowadzono niepoprawny Jabber ID</string> <string name="error_out_of_memory">Brak pamięci, obraz jest za duży</string> - <string name="add_phone_book_text">Czy chcesz dodać kontakt %s do książki telefonicznej?</string> <string name="contact_status_online">dostępny</string> <string name="contact_status_free_to_chat">chętny do rozmowy</string> <string name="contact_status_away">zaraz wracam</string> @@ -186,7 +181,8 @@ <string name="server_info_blocking">XEP-0191: Blocking Command</string> <string name="server_info_roster_version">XEP-0237: Roster Versioning</string> <string name="server_info_stream_management">XEP-0198: Stream Management</string> - <string name="server_info_pep">XEP-0163: PEP (Avatars)</string> + <string name="server_info_pep">XEP-0163: PEP (Awatary / OMEMO)</string> + <string name="server_info_http_upload">XEP-0363: Przesyłanie plików przez HTTP</string> <string name="server_info_available">dostępny</string> <string name="server_info_unavailable">niedostępny</string> <string name="missing_public_keys">Brak informacji o kluczu publicznym</string> @@ -204,6 +200,12 @@ <string name="reception_failed">Odbiór nieudany</string> <string name="your_fingerprint">Twój odcisk klucza</string> <string name="otr_fingerprint">Odcisk klucza OTR</string> + <string name="omemo_fingerprint">Odcisk OMEMO</string> + <string name="omemo_fingerprint_selected_message">Odcisk OMEMO wiadomości</string> + <string name="this_device_omemo_fingerprint">Własny odcisk OMEMO</string> + <string name="other_devices">Pozostałe urządzenia</string> + <string name="trust_omemo_fingerprints">Zaufane odciski OMEMO</string> + <string name="done">Ukończono</string> <string name="verify">Weryfikuj</string> <string name="decrypt">Odszyfruj</string> <string name="conferences">Konferencje</string> @@ -249,7 +251,6 @@ <string name="skip">Pomiń</string> <string name="disable_notifications">Wyłącz powiadomienia</string> <string name="disable_notifications_for_this_conversation">Wyłącz powiadomienia tej konwersacji</string> - <string name="notifications_disabled">Powiadomienia są wyłączone</string> <string name="enable">Włącz</string> <string name="conference_requires_password">Konferencja jest zabezpieczona hasłem</string> <string name="enter_password">Wprowadź hasło</string> @@ -284,15 +285,19 @@ <string name="pref_conference_name">Nazwa konferencji</string> <string name="pref_conference_name_summary">Nazywaj konferencję tematem zamiast Jabber ID</string> <string name="toast_message_otr_fingerprint">Odcisk klucza OTR został skopiowany do schowka</string> + <string name="toast_message_omemo_fingerprint">Odcisk klucza OMEMO został skopiowany do schowka!</string> <string name="conference_banned">Zbanowano cię w konferencji</string> <string name="conference_members_only">To jest zamknięty pokój</string> <string name="conference_kicked">Wyrzucono cię z konferencji</string> <string name="using_account">używając konta %s</string> + <string name="checking_x">Sprawdzanie %s na hoście HTTP</string> <string name="not_connected_try_again">Brak połączenia. Spróbuj ponownie później</string> + <string name="check_x_filesize">Sprawdź rozmiar %s</string> <string name="message_options">Opcje wiadomości</string> <string name="copy_text">Skopiuj tekst</string> <string name="copy_original_url">Skopiuj oryginalny URL</string> <string name="send_again">Wyślij ponownie</string> + <string name="file_url">URL pliku</string> <string name="message_text">Treść wiadomości</string> <string name="url_copied_to_clipboard">URL obrazu został skopiowany do schowka</string> <string name="message_copied_to_clipboard">Wiadomość została skopiowana do schowka</string> @@ -304,7 +309,6 @@ <string name="verify_otr">Weryfikuj OTR</string> <string name="remote_fingerprint">Zdalny odcisk klucza</string> <string name="scan">skanuj</string> - <string name="or_touch_phones">(lub zetknij telefony)</string> <string name="smp">Protokół socialist millionaire</string> <string name="shared_secret_hint">Podpowiedź lub pytanie</string> <string name="shared_secret_secret">Wspólny sekret</string> @@ -347,6 +351,16 @@ <string name="reset">Resetuj</string> <string name="account_image_description">Awatar konta</string> <string name="copy_otr_clipboard_description">Skopiuj odcisk klucza OTR do schowka</string> + <string name="copy_omemo_clipboard_description">Skopiuj odcisk klucza OMEMO do schowka</string> + <string name="regenerate_omemo_key">Wygeneruj ponownie klucz OMEMO</string> + <string name="wipe_omemo_pep">Usuń inne urządzenia z PEP</string> + <string name="clear_other_devices">Wyczyść urządzenia</string> + <string name="clear_other_devices_desc">Czy na pewno chcesz usunąć wszystkie inne urządzenia z ogłoszenia OMEMO? Następnym razem gdy połączą się Twoje urdzącenia, ogłoszą się one ponownie, ale mogą nie otrzymać wiadomości wysłanych w międzyczasie.</string> + <string name="purge_key">Skasuj klucz</string> + <string name="purge_key_desc_part1">Czy na pewno chcesz skasować usunąć odcisk klucza?</string> + <string name="purge_key_desc_part2">Zostanie bez odwołania uznane za zdradzone, i nigdy więcej nie będzie można stworzyć z nim sesji.</string> + <string name="error_no_keys_to_trust">Nie ma dostępnych żadnych użytecznych kluczy dla tego kontaktu. Jeśli usunąłeś jakieś jego klucze, kontakt będzie musiał wygenerować nowe.</string> + <string name="error_trustkeys_title">Błąd</string> <string name="fetching_history_from_server">Pobieranie historii z serwera</string> <string name="no_more_history_on_server">Koniec historii na serwerze</string> <string name="updating">Aktualizowanie...</string> @@ -384,8 +398,10 @@ <string name="public_conference">Konferencja publiczna</string> <string name="private_conference">Konferencja prywatna, dla zaakceptowanych uczestników</string> <string name="conference_options">Opcje konferencji</string> - <string name="members_only">Prywatna (tylko zaakceptowani)</string> + <string name="members_only">Prywatne, tylko dla członków.</string> <string name="non_anonymous">Nieanonimowa</string> + <string name="moderated">Moderowany</string> + <string name="you_are_not_participating">Nie bierzesz udziału</string> <string name="modified_conference_options">Opcje konferencji zostały zmienione!</string> <string name="could_not_modify_conference_options">Nie udało się zmienić opcji konferencji</string> <string name="never">Nigdy</string> @@ -413,7 +429,6 @@ <string name="offering_x_file">Oferowanie %s</string> <string name="hide_offline">Ukryj niedostępnych</string> <string name="disable_account">Wyłącz konto</string> - <string name="contact_is_typing">%s pisze...</string> <string name="contact_has_stopped_typing">%s przestał(a) pisać</string> <string name="pref_chat_states">Powiadomienia pisania</string> <string name="pref_chat_states_summary">Powiadamiaj rozmówcę, kiedy rozpoczynasz nową wiadomość</string> @@ -424,7 +439,6 @@ <string name="received_location">Otrzymano lokalizację</string> <string name="title_undo_swipe_out_conversation">Zamknięto konwersację</string> <string name="title_undo_swipe_out_muc">Opuszczono konferencję</string> - <string name="pref_certificate_options">Ustawienia certyfikatów</string> <string name="pref_dont_trust_system_cas_title">Nie ufaj certyfikatom systemowym</string> <string name="pref_dont_trust_system_cas_summary">Wymagaj ręcznego potwierdzania certyfikatów</string> <string name="pref_remove_trusted_certificates_title">Usuń certyfikat</string> @@ -448,4 +462,16 @@ <string name="none">Brak</string> <string name="recently_used">Ostatnio używana</string> <string name="choose_quick_action">Wybierz szybką akcję</string> + <string name="search_for_contacts_or_groups">Szukaj kontaktów i grup</string> + <string name="send_private_message">Wyślij wiadomość prywatną</string> + <string name="user_has_left_conference">%s opuścił(a) konferencję!</string> + <string name="username">Nazwa użytkownika</string> + <string name="username_hint">Nazwa użytkownika</string> + <string name="invalid_username">Błędna nazwa użytkownika</string> + <string name="download_failed_server_not_found">Pobieranie nieudane: Nie odnaleziono serwera</string> + <string name="download_failed_file_not_found">Pobieranie nieudane: Nie odnaleziono pliku</string> + <string name="download_failed_could_not_connect">Pobieranie nieudane: Nie można połączyć z hostem</string> + <string name="pref_use_white_background">Białe tło</string> + <string name="pref_use_white_background_summary">Pokazuj otrzymane wiadomości jako czarny tekst na białym tle</string> + <string name="server_info_broken">Zepsute</string> </resources> diff --git a/src/main/res/values-pt/strings.xml b/src/main/res/values-pt/strings.xml index 559ae6cf..137d38b4 100644 --- a/src/main/res/values-pt/strings.xml +++ b/src/main/res/values-pt/strings.xml @@ -9,7 +9,6 @@ <string name="action_secure">Conversa segura</string> <string name="action_add_account">Adicionar conta</string> <string name="action_edit_contact">Editar nome</string> - <string name="action_add_phone_book">Adicionar aos contatos</string> <string name="action_block_contact">Bloquear contato</string> <string name="action_unblock_contact">Desbloquear contato</string> <string name="action_block_domain">Bloquear domínio</string> @@ -27,7 +26,6 @@ <string name="minutes_ago">%d minutos atrás</string> <string name="unread_conversations">Conversas não lidas</string> <string name="sending">enviando...</string> - <string name="encrypted_message">Descriptografando mensagem. Por favor aguarde...</string> <string name="nick_in_use">O apelido já está em uso</string> <string name="admin">Administrador</string> <string name="owner">Dono</string> @@ -72,9 +70,7 @@ <string name="clear_conversation_history">Limpar o histórico de conversas</string> <string name="clear_histor_msg">Você deseja remover todas as mensagens nessa conversa?\n\n<b>Atenção:<b> Isso não irá influenciar mensagens salvas em outros dispositivos ou servidores.</string> <string name="delete_messages">Remover mensagens</string> - <string name="also_end_conversation">Finalizar essa conversa ao final</string> <string name="choose_presence">Escolha a presença do contato</string> - <string name="send_plain_text_message">Enviar mensagem de texto puro</string> <string name="send_otr_message">Enviar mensagem criptografada com OTR</string> <string name="send_pgp_message">Enviar mensagem criptografada com OpenPGP</string> <string name="your_nick_has_been_changed">Seu apelido foi alterado</string> @@ -99,8 +95,6 @@ <string name="pref_vibrate_summary">Vibrar também quando uma nova mensagem for recebida</string> <string name="pref_sound">Som</string> <string name="pref_sound_summary">Tocar um som com a notificação</string> - <string name="pref_conference_notifications">Notificações de conferência</string> - <string name="pref_conference_notifications_summary">Sempre notificar quando uma nova mensagem de conferencia for recebida ao invés de apenas quando a mesma for ressaltada</string> <string name="pref_notification_grace_period">Período de carência da notificação</string> <string name="pref_notification_grace_period_summary">Desativar notificações por um curto período após a copia oculta ser recebida</string> <string name="pref_advanced_options">Opções avançadas</string> @@ -139,7 +133,6 @@ <string name="account_status_regis_not_sup">O servidor não aceita o registro</string> <string name="account_status_security_error">Erro de segurança</string> <string name="account_status_incompatible_server">Servidor incompatível</string> - <string name="encryption_choice_none">Texto puro</string> <string name="encryption_choice_otr">OTR</string> <string name="encryption_choice_pgp">OpenPGP</string> <string name="mgmt_account_edit">Editar conta</string> @@ -159,7 +152,6 @@ <string name="passwords_do_not_match">As senhas não combina</string> <string name="invalid_jid">Esse não é um ID Jabber válido</string> <string name="error_out_of_memory">Memória insuficiente. A imagem é muito grande</string> - <string name="add_phone_book_text">Você tem certeza que deseja adicionar %s à sua lista de contato do telefone?</string> <string name="contact_status_online">online</string> <string name="contact_status_free_to_chat">disponível para conversa</string> <string name="contact_status_away">fora</string> @@ -231,7 +223,6 @@ <string name="skip">Pular</string> <string name="disable_notifications">Desativar notificações</string> <string name="disable_notifications_for_this_conversation">Desativar notificações para essa conversa</string> - <string name="notifications_disabled">As notificações foram desativadas</string> <string name="enable">Ativar</string> <string name="conference_requires_password">Essa conferencia requer uma senha</string> <string name="enter_password">Informar a senha</string> @@ -286,7 +277,6 @@ <string name="vcard">Contato</string> <string name="sending_x_file">Enviando %s</string> <string name="offering_x_file">Oferecendo %s</string> - <string name="contact_is_typing">%s está digitando...</string> <string name="contact_has_stopped_typing">%s parou de digitar</string> <string name="pref_chat_states">Notificações de digitação</string> <string name="send_location">Enviar localização</string> diff --git a/src/main/res/values-ro-rRO/strings.xml b/src/main/res/values-ro-rRO/strings.xml index 9cbfcdbf..e4fdb4cc 100644 --- a/src/main/res/values-ro-rRO/strings.xml +++ b/src/main/res/values-ro-rRO/strings.xml @@ -1,15 +1,15 @@ <?xml version='1.0' encoding='UTF-8'?> <resources> - <string name="action_settings">Configuratie</string> + <string name="action_settings">Setari</string> <string name="action_add">Conversatie noua</string> <string name="action_accounts">Configureaza conturi</string> - <string name="action_end_conversation">Termina conversatie</string> + <string name="action_end_conversation">Termina conversatia</string> <string name="action_contact_details">Detalii contact</string> <string name="action_muc_details">Detalii conferinta</string> <string name="action_secure">Securizeaza conferinta</string> <string name="action_add_account">Adauga cont</string> <string name="action_edit_contact">Editeaza nume</string> - <string name="action_add_phone_book">Adauga in agenda</string> + <string name="action_add_phone_book">Adauga la lista de contacte</string> <string name="action_delete_contact">Sterge din lista</string> <string name="action_block_contact">Blocheaza contact</string> <string name="action_unblock_contact">Deblocheaza contact</string> @@ -19,18 +19,19 @@ <string name="title_activity_settings">Configuratie</string> <string name="title_activity_conference_details">Detalii conferinta</string> <string name="title_activity_contact_details">Detalii contact</string> - <string name="title_activity_sharewith">Distribuie catre Conversatie</string> - <string name="title_activity_start_conversation">Porneste Conversatie</string> + <string name="title_activity_sharewith">Distribuie catre Conversations</string> + <string name="title_activity_start_conversation">Porneste conversatie</string> <string name="title_activity_choose_contact">Alege contact</string> <string name="title_activity_block_list">Blocheaza lista</string> <string name="just_now">in acest moment</string> - <string name="minute_ago">acuma 1 minut</string> - <string name="minutes_ago">acuma %d minute</string> - <string name="unread_conversations">Conversatii necitite</string> + <string name="minute_ago">acum un minut</string> + <string name="minutes_ago">acum %d minute</string> + <string name="unread_conversations">conversatii necitite</string> <string name="sending">trimitere...</string> - <string name="encrypted_message">Decriptez mesaj. Te rog asteapta...</string> + <string name="message_decrypting">Decriptez mesaj. Te rog asteapta...</string> + <string name="pgp_message">Mesaj criptat cu OpenPGP</string> <string name="nick_in_use">Nume utilizator este deja folosit.</string> - <string name="admin">Admin</string> + <string name="admin">Administrator</string> <string name="owner">Proprietar</string> <string name="moderator">Moderator</string> <string name="participant">Participant</string> @@ -41,11 +42,12 @@ <string name="block_domain_text">Blocheaza toate contactele de la %s?</string> <string name="unblock_domain_text">Deblocheaza toate contactele de la %s?</string> <string name="contact_blocked">Contact blocat</string> - <string name="remove_bookmark_text">Ai dori sa stergi pe %s ca semn de carte? Conversatia asociata cu acest semn de carte nu va fi stearsa.</string> + <string name="remove_bookmark_text">Ai dori sa stergi pe %s din semne de carte? Conversatia asociata cu acest semn de carte nu va fi stearsa.</string> <string name="register_account">Inregistreaza un cont nou pe server</string> <string name="change_password_on_server">Schimba parola pe server</string> - <string name="start_conversation">Porneste Conversatie</string> - <string name="invite_contact">Invita Contact</string> + <string name="share_with">Partajeaza cu...</string> + <string name="start_conversation">Porneste conversatie</string> + <string name="invite_contact">Invita contact</string> <string name="contacts">Contacte</string> <string name="cancel">Anuleaza</string> <string name="set">Seteaza</string> @@ -56,37 +58,497 @@ <string name="unblock">Deblocheaza</string> <string name="save">Salveaza</string> <string name="ok">DA</string> - <string name="crash_report_title">Conversatii s-a oprit neasteptat</string> - <string name="crash_report_message">Trimitand date ajuti la dezvoltarea aplicatiei Conversatii\n<b>Atentie:</b> Se va utiliza contul XMPP pentru a trimite informatii catre programatori.</string> - <string name="send_now">Trimie acum</string> - <string name="send_never">Nu mai intreba in viitor</string> + <string name="crash_report_title">Conversations s-a oprit neasteptat</string> + <string name="crash_report_message">Trimitand jurnalele de eroare ajuti la buna dezvoltarea a aplicatiei Conversations\n<b>Atentie:</b> Se va utiliza contul XMPP pentru a trimite informatiile din jurnal catre programatori.</string> + <string name="send_now">Trimite acum</string> + <string name="send_never">Nu ma mai intreba</string> <string name="problem_connecting_to_account">Nu ma pot conecta la cont</string> <string name="problem_connecting_to_accounts">Nu ma pot conecta la conturi multiple</string> <string name="touch_to_fix">Apasa aici pentru a configura conturile tale</string> <string name="attach_file">Ataseaza fisier</string> <string name="not_in_roster">Contactul nu este in lista ta. Ai vrea sa il adaugi?</string> <string name="add_contact">Adauga contact</string> - <string name="send_failed">Trimitere esuata</string> - <string name="send_rejected">rejectat</string> + <string name="send_failed">trimitere esuata</string> + <string name="send_rejected">respins</string> <string name="preparing_image">Pregatesc imaginea pentru transmisie</string> - <string name="action_clear_history">Sterge istoria</string> - <string name="clear_conversation_history">Sterge istoria conversatiei</string> - <string name="clear_histor_msg">Doresti sa stergi toate mesajele din Conversatii?\n\n<b>Atentie:</b> Aceasta actiune nu va influenta mesajele aflate pe alte telefoane/tabelete/servere.</string> + <string name="action_clear_history">Sterge istoric</string> + <string name="clear_conversation_history">Sterge istoricul conversatiei</string> + <string name="clear_histor_msg">Doresti sa stergi toate mesajele din aceasta conversatie?\n\n<b>Atentie:</b> Aceasta actiune nu va influenta mesajele aflate pe alte telefoane/tabelete/servere.</string> <string name="delete_messages">Sterge mesajele</string> - <string name="also_end_conversation">Termina conversatia aceasta dupa</string> + <string name="also_end_conversation">Dupa, incheie conversatia</string> <string name="choose_presence">Alege prezenta pentru a contacta</string> - <string name="send_plain_text_message">Trimite text necriptat</string> + <string name="send_unencrypted_message">Trimite mesaje necriptate</string> <string name="send_otr_message">Trimite mesaj criptat cu OTR</string> + <string name="send_omemo_message">Trimite mesaj criptat cu OMEMO</string> + <string name="send_omemo_x509_message">Trimite mesaj criptat cu v\\OMEMO</string> <string name="send_pgp_message">Trimite mesaj criptat cu OpenPGP</string> <string name="your_nick_has_been_changed">Numele tau a fost schimbat</string> <string name="send_unencrypted">Trimite necriptat</string> <string name="decryption_failed">Decriptia a esuat. Poate nu ai cheia privata corecta.</string> <string name="openkeychain_required">OpenKeychain</string> - <string name="openkeychain_required_long">Coonversatii utilizeaza o aplicatia externa <b>OpenKeychain</b> pentru a cripta si decripta mesaje si a administra cheile publice.\n\nOpenKeychain este licentiat sub GPLv3 si se gaseste pentru copiere pe F-Droid si Google Play.\n\n<small>(Te rog sa repornesti Conversatii dupa.)</small></string> + <string name="openkeychain_required_long">Conversations utilizeaza o aplicatia externa <b>OpenKeychain</b> pentru a cripta si decripta mesaje si a administra cheile publice.\n\nOpenKeychain este licentiat sub GPLv3 si se gaseste pentru copiere pe F-Droid si Google Play.\n\n<small>(Te rog sa repornesti Conversations dupa)</small></string> <string name="restart">Reporneste</string> <string name="install">Instaleaza</string> - <string name="offering">Transmit...</string> - <string name="waiting">In asteptare...</string> + <string name="openkeychain_not_installed">Te rog instaleaza OpenKeychain</string> + <string name="offering">transmit...</string> + <string name="waiting">in asteptare...</string> <string name="no_pgp_key">Nu am gasit cheie OpenPGP</string> - <string name="contact_has_no_pgp_key">Conversatii nu a putut sa cirpteze mesajele tale din cauza contactului care nu isi anunta cheia publica.\n\n<small>Roaga contactul sa isi configureze OpenPGP.</small></string> + <string name="contact_has_no_pgp_key">Conversations nu a putut sa cripteze mesajele tale din cauza contactului care nu isi anunta cheia publica.\n\n<small>Roaga contactul sa isi configureze OpenPGP.</small></string> + <string name="no_pgp_keys">Nu am gasit chei OpenPGP</string> + <string name="contacts_have_no_pgp_keys">Conversations nu poate cripta mesajele tale pentru contactele tale care nu isi anunta cheia publica.\n\n<small>Te rog cere contactelor sa configureze OpenPGP.</small></string> + <string name="encrypted_message_received"><i>Ai primit mesaj criptat. Apasa aici pentru a-l decripta.</i></string> + <string name="pref_general">General</string> + <string name="pref_xmpp_resource">Nume client XMPP</string> + <string name="pref_xmpp_resource_summary">Numele cu care acest client se identifica</string> + <string name="pref_accept_files">Accepta fisiere</string> + <string name="pref_accept_files_summary">Accepta automat fisiere mai mici decat...</string> + <string name="pref_notification_settings">Setari notificari</string> + <string name="pref_notifications">Notificari</string> + <string name="pref_notifications_summary">Notifica cand un nou mesaj este primit</string> + <string name="pref_vibrate">Vibreaza</string> + <string name="pref_vibrate_summary">Vibreaza cand un nou mesaj este primit</string> + <string name="pref_sound">Sunet</string> + <string name="pref_sound_summary">Ton de apel pentru notificare</string> + <string name="pref_notification_grace_period">Perioada de gratie notificari </string> + <string name="pref_notification_grace_period_summary">Opreste notificari pentru o scurta perioada dupa ce o copie a mesajului a fost primita</string> + <string name="pref_advanced_options">Optiuni avansate</string> + <string name="pref_never_send_crash">Nu trimite rapoarte de erori</string> + <string name="pref_never_send_crash_summary">Trimitand date ajuti la dezvoltarea aplicatiei Conversations\n<b>Atentie:</b> Se va utiliza contul XMPP pentru a trimite informatii catre programatori.</string> + <string name="pref_confirm_messages">Confirma mesaje</string> + <string name="pref_confirm_messages_summary">Notifica contactul cand ai primit un mesaj si l-ai citit</string> + <string name="pref_ui_options">Optiuni interfata</string> + <string name="openpgp_error">OpenKeychain a raportat o eroare</string> + <string name="error_decrypting_file">Eroare I/O la decriptarea fisierului</string> + <string name="accept">Accepta</string> + <string name="error">A aparut o eroare</string> + <string name="pref_grant_presence_updates">Trimite actualizari de prezenta</string> + <string name="pref_grant_presence_updates_summary">Acorda si cere anticipat abonarea la actualizarile de prezenta pentru contactele create de tine</string> + <string name="subscriptions">Abonari</string> + <string name="your_account">Contul tau</string> + <string name="keys">Chei</string> + <string name="send_presence_updates">Trimite actualizari de prezenta</string> + <string name="receive_presence_updates">Primeste actualizari de prezenta</string> + <string name="ask_for_presence_updates">Cere actualizari de prezenta</string> + <string name="attach_choose_picture">Alege imagine</string> + <string name="attach_take_picture">Fa o poza</string> + <string name="preemptively_grant">Acorda anticipat cererea de abonare</string> + <string name="error_not_an_image_file">Fisierul selectat nu este o imagine</string> + <string name="error_compressing_image">Eroare la conversia fisierului de imagine</string> + <string name="error_file_not_found">Fisierul nu a fost gasit</string> + <string name="error_io_exception">Eroare I/O gemerala. Poate ai ramas fara spatiu liber?</string> + <string name="error_security_exception_during_image_copy">Aplicatia folosita pentru selectia acestei imagini nu a oferit destule permisiuni pentru a putea citi fisierul.\n\n<small>Foloseste un alt manager de fisiere pentru a alege o imagine</small></string> + <string name="account_status_unknown">Necunoscut</string> + <string name="account_status_disabled">Dezactivat temporar</string> + <string name="account_status_online">Conectat</string> + <string name="account_status_connecting">In curs de conectare\u2026</string> + <string name="account_status_offline">Deconectat</string> + <string name="account_status_unauthorized">Neautorizat</string> + <string name="account_status_not_found">Serverul nu a fost gasit</string> + <string name="account_status_no_internet">Fara conexiune</string> + <string name="account_status_regis_fail">Inregistrare esuata</string> + <string name="account_status_regis_conflict">Nume de utilizator deja alocat</string> + <string name="account_status_regis_success">Inregistrare completa</string> + <string name="account_status_regis_not_sup">Acest server nu permite inregistrarea</string> + <string name="account_status_security_error">Eroare de securitate</string> + <string name="account_status_incompatible_server">Server incompatibil</string> + <string name="encryption_choice_unencrypted">Ne criptat</string> + <string name="encryption_choice_otr">OTR</string> + <string name="encryption_choice_pgp">OpenGPG</string> + <string name="encryption_choice_omemo">OMEMO</string> + <string name="mgmt_account_edit">Editare cont</string> + <string name="mgmt_account_delete">Sterge cont</string> + <string name="mgmt_account_disable">Dezactivare temporara</string> + <string name="mgmt_account_publish_avatar">Publica avatar</string> + <string name="mgmt_account_publish_pgp">Publica cheia publica OpenPGP</string> + <string name="mgmt_account_enable">Activeaza cont</string> + <string name="mgmt_account_are_you_sure">Esti sigur?</string> + <string name="mgmt_account_delete_confirm_text">Daca iti stergi contul intregul istoric de conversatii va fi pierdut</string> + <string name="attach_record_voice">Inregistrare voce</string> + <string name="account_settings_jabber_id">ID-ul Jabber</string> + <string name="account_settings_password">Parola</string> + <string name="account_settings_example_jabber_id">username@example.com</string> + <string name="account_settings_confirm_password">Confirma parola</string> + <string name="password">Parola</string> + <string name="confirm_password">Confirma parola</string> + <string name="passwords_do_not_match">Parolele nu sunt identice</string> + <string name="invalid_jid">Acesta nu este un ID Jabber valabil</string> + <string name="error_out_of_memory">Memorie epuizata. Imaginea este prea mare.</string> + <string name="add_phone_book_text">Vrei sa adaugi pe %s in lista de contacte?</string> + <string name="contact_status_online">conectat</string> + <string name="contact_status_free_to_chat">disponibil pentru conversatie</string> + <string name="contact_status_away">plecat</string> + <string name="contact_status_extended_away">plecat departe</string> + <string name="contact_status_do_not_disturb">nu deranja</string> + <string name="contact_status_offline">deconectat</string> + <string name="muc_details_conference">Conferinta</string> + <string name="muc_details_other_members">Alti membri</string> + <string name="server_info_show_more">Informatii server</string> + <string name="server_info_mam">XEP-0313: MAM</string> + <string name="server_info_carbon_messages">XEP-0280: Copii indigo mesaje</string> + <string name="server_info_csi">XEP-0352: Indicator stare client</string> + <string name="server_info_blocking">XEP-0191: Comanda blocare</string> + <string name="server_info_roster_version">XEP-0237: Creare de versiuni lista</string> + <string name="server_info_stream_management">XEP-0198: Management flux</string> + <string name="server_info_pep">XEP-0163: PEP (Avatare / OMEMO)</string> + <string name="server_info_http_upload">XEP-0363: Incarcare fisiere prin HTTP</string> + <string name="server_info_available">disponibil</string> + <string name="server_info_unavailable">indisponibil</string> + <string name="missing_public_keys">Cheile publice ce nu au fost anuntate</string> + <string name="last_seen_now">vazut ultima data adineauri</string> + <string name="last_seen_min">vazut ultima data acum un minut</string> + <string name="last_seen_mins">vazut ultima data acum %d minute</string> + <string name="last_seen_hour">vazut ultima data acum o ora</string> + <string name="last_seen_hours">vazut ultima data acum %d ore</string> + <string name="last_seen_day">vazut ultima data acum o zi</string> + <string name="last_seen_days">vazut ultima data acum %d zile</string> + <string name="never_seen">niciodata vazut</string> + <string name="install_openkeychain">Mesaj criptat. Te rog instaleaza OpenKeychain pentru a-l putea decripta.</string> + <string name="unknown_otr_fingerprint">Amprenta OTR necunoscuta</string> + <string name="openpgp_messages_found">A fost gasit un mesaj criptat cu OpenPGP</string> + <string name="reception_failed">Receptie esuata</string> + <string name="your_fingerprint">Amprenta ta</string> + <string name="otr_fingerprint">Amprenta OTR</string> + <string name="omemo_fingerprint">Amprenta OMEMO</string> + <string name="omemo_fingerprint_x509">Amprenta v\\OMEMO</string> + <string name="omemo_fingerprint_selected_message">Amprenta OMEMO a mesajului</string> + <string name="omemo_fingerprint_x509_selected_message">Amprenta v\\OMEMO a mesajului</string> + <string name="this_device_omemo_fingerprint">Amprenta OMEMO proprie</string> + <string name="other_devices">Alte dispozitive</string> + <string name="trust_omemo_fingerprints">Amprente OMEMO de incredere</string> + <string name="fetching_keys">Se preiau cheile...</string> + <string name="done">Gata</string> + <string name="verify">Verifica</string> + <string name="decrypt">Decripteaza</string> + <string name="conferences">Conferinte</string> + <string name="search">Cauta</string> + <string name="create_contact">Adauga contact</string> + <string name="enter_contact">Introdu contact</string> + <string name="join_conference">Alatura-te conferintei</string> + <string name="delete_contact">Sterge contact</string> + <string name="view_contact_details">Arata detalii contact</string> + <string name="block_contact">Blocheaza contact</string> + <string name="unblock_contact">Deblocheaza contact</string> + <string name="create">Creeaza</string> + <string name="select">Selecteaza</string> + <string name="contact_already_exists">Contactul exista deja</string> + <string name="join">Alatura-te</string> + <string name="conference_address">Adresa conferinta</string> + <string name="conference_address_example">room@conference.example.com</string> + <string name="save_as_bookmark">Salveaza semn de carte</string> + <string name="delete_bookmark">Sterge semn de carte</string> + <string name="bookmark_already_exists">Acest semn de carte exista</string> + <string name="you">Tu</string> + <string name="action_edit_subject">Editeaza titlul conferintei</string> + <string name="conference_not_found">Conferinta nu a fost gasita</string> + <string name="leave">Paraseste</string> + <string name="contact_added_you">Contactul a fost adaugat in lista </string> + <string name="add_back">Adauga inapoi</string> + <string name="contact_has_read_up_to_this_point">%s a citit pana in acest loc</string> + <string name="publish">Publica</string> + <string name="touch_to_choose_picture">Atinge avatarul pentru a selecta o poza din galerie</string> + <string name="publish_avatar_explanation">Tine minte: Toti cei abonati la actualizarile tale de prezenta vor putea sa vada aceasta poza</string> + <string name="publishing">Se publica...</string> + <string name="error_publish_avatar_server_reject">Acest server v-a refuzat publicarea</string> + <string name="error_publish_avatar_converting">Ceva nu a mers bine in timpul conversiei imaginii</string> + <string name="error_saving_avatar">Nu s-a putut salva avatarul pe disc</string> + <string name="or_long_press_for_default">(Sau apasa indelung pentru a reseta la implicit)</string> + <string name="error_publish_avatar_no_server_support">Acest server nu permite publicarea de avatare</string> + <string name="private_message">sopteste</string> + <string name="private_message_to">catre %s</string> + <string name="send_private_message_to">Trimite mesaj privat catre %s</string> + <string name="connect">Conectare</string> + <string name="account_already_exists">Acest cont exista deja</string> + <string name="next">Urmatoarea</string> + <string name="server_info_session_established">Sesiune curenta stabilita</string> + <string name="additional_information">Informatii aditionale</string> + <string name="skip">Sari</string> + <string name="disable_notifications">Dezactiveaza notificari</string> + <string name="disable_notifications_for_this_conversation">Dezactiveaza notificarile pentru aceasta conversatie</string> + <string name="enable">Activeaza</string> + <string name="conference_requires_password">Conferinta necesita parola</string> + <string name="enter_password">Introdu parola</string> + <string name="missing_presence_updates">Au lipsit actualizarile de prezenta de la contact</string> + <string name="request_presence_updates">Te rog mai intai cere actualizari de prezente de la contact.\n\n<small>Asta va fi folosita pentru a determina ce aplicatii client foloseste contactul tau</small></string> + <string name="request_now">Cere acum</string> + <string name="delete_fingerprint">Sterge amprenta</string> + <string name="sure_delete_fingerprint">Sigur vrei sa stergi amprenta</string> + <string name="ignore">Ignora</string> + <string name="without_mutual_presence_updates"><b>Atentie:</b> Trimitand aceasta fara actualizari de prezenta reciproce, ar putea produce probleme neprevazute.\n\n<small>Mergi la lista de contacte, la detalii, sa iti verifici abonarile la actualizarile de prezenta.</small></string> + <string name="pref_encryption_settings">Setari criptare</string> + <string name="pref_force_encryption">Forteaza criptarea conexiunii de la un capat la altul</string> + <string name="pref_force_encryption_summary">Trimite mereu mesajele criptate (exceptand conferintele)</string> + <string name="pref_dont_save_encrypted">Nu salva mesaje criptate</string> + <string name="pref_dont_save_encrypted_summary">Atentie: Asta poate duce la pierderea de mesaje</string> + <string name="pref_expert_options">Optiuni expert</string> + <string name="pref_expert_options_summary">Te rog sa fi atent cu astea</string> + <string name="title_activity_about">Despre Conversations</string> + <string name="pref_about_conversations_summary">Informatii despre versiune si licenta</string> + <string name="title_pref_quiet_hours">Ore de liniste</string> + <string name="title_pref_quiet_hours_start_time">Ora de pornire</string> + <string name="title_pref_quiet_hours_end_time">Ora de oprire</string> + <string name="title_pref_enable_quiet_hours">Activeaza orar de liniste</string> + <string name="pref_quiet_hours_summary">Notificarile vor fi reduse la tacere in timpul orelor de liniste</string> + <string name="pref_use_larger_font">Mareste marimea literelor</string> + <string name="pref_use_larger_font_summary">Foloseste marimea mai mare de text in toata aplicatia</string> + <string name="pref_use_send_button_to_indicate_status">Butonul de trimitere indica starea</string> + <string name="pref_use_indicate_received">Cere raport de primire</string> + <string name="pref_use_indicate_received_summary">Mesajele primite vor fi marcate cu un semn verde daca este suportat</string> + <string name="pref_use_send_button_to_indicate_status_summary">Coloreaza butonul de trimitere pentru a indica starea contactului</string> + <string name="pref_expert_options_other">Altele</string> + <string name="pref_conference_name">Titlu conferinta</string> + <string name="pref_conference_name_summary">Foloseste subiectul camerei in locul JID pentru a identifica conferinta</string> + <string name="toast_message_otr_fingerprint">Amprenta OTR copiata in memorie</string> + <string name="toast_message_omemo_fingerprint">Amprenta OMEMO copiata in memorie!</string> + <string name="conference_banned">Ti-a fost interzis accesul la aceasta conferinta</string> + <string name="conference_members_only">Aceasta conferinta este rezervata membrilor</string> + <string name="conference_kicked">Ai fost dat afara din conferinta</string> + <string name="using_account">folosind cont %s</string> + <string name="checking_x">Verifica %s pe gazda HTTP</string> + <string name="not_connected_try_again">Nu esti conectat. Incearca din nou mai tarziu.</string> + <string name="check_x_filesize">Verifica marimea %s</string> + <string name="message_options">Optiuni mesaje</string> + <string name="copy_text">Copiaza text</string> + <string name="copy_original_url">Copiaza URL original</string> + <string name="send_again">Trimite din nou</string> + <string name="file_url">URL fisier</string> + <string name="message_text">Mesaj text</string> + <string name="url_copied_to_clipboard">URL copiat in memorie</string> + <string name="message_copied_to_clipboard">Mesaj copiat in memorie</string> + <string name="image_transmission_failed">Transmisia imaginii a esuat</string> + <string name="scan_qr_code">Scaneaza cod QR</string> + <string name="show_qr_code">Arata codul QR</string> + <string name="show_block_list">Arata lista blocata</string> + <string name="account_details">Detalii cont</string> + <string name="verify_otr">Verifica OTR</string> + <string name="remote_fingerprint">Amprenta la distanta</string> + <string name="scan">scaneaza</string> + <string name="smp">Socialist Millionaire Protocol</string> + <string name="shared_secret_hint">Indiciu sau Intrebare</string> + <string name="shared_secret_secret">Secret impartasit</string> + <string name="confirm">Confirma</string> + <string name="in_progress">In desfasurare</string> + <string name="respond">Raspunde</string> + <string name="failed">Esuat</string> + <string name="secrets_do_not_match">Secretele nu se potrivesc</string> + <string name="try_again">Incearca din nou</string> + <string name="finish">Incheie</string> + <string name="verified">Verificat!</string> + <string name="smp_requested">Contactul a cerut verificare SMP</string> + <string name="no_otr_session_found">Nu s-a gasit nici o sesiune OTR valida</string> + <string name="conversations_foreground_service">Conversations</string> + <string name="pref_keep_foreground_service">Pastreaza serviciul activ in prim plan</string> + <string name="pref_keep_foreground_service_summary">Previne inchiderea conexiunii de catre sistemul de operare.</string> + <string name="pref_export_logs">Exporta jurnale</string> + <string name="pref_export_logs_summary">Scrie jurnal pe card SD</string> + <string name="notification_export_logs_title">Se scrie jurnal pe card SD</string> + <string name="choose_file">Alege un fisier</string> + <string name="receiving_x_file">Primesc %1$s (%2$d%% complet)</string> + <string name="download_x_file">Descarca %s</string> + <string name="file">fisier</string> + <string name="open_x_file">Deschide %s</string> + <string name="sending_file">trimit (%1$d%% complet)</string> + <string name="preparing_file">Pregatire fisier pentru transmisie</string> + <string name="x_file_offered_for_download">%s oferit spre descarcare</string> + <string name="cancel_transmission">Anuleaza transmisiunea</string> + <string name="file_transmission_failed">transmisie fisier esuata</string> + <string name="file_deleted">Fisierul a fost sters</string> + <string name="no_application_found_to_open_file">Nu s-a gasi nici o aplicatie care sa deschida fisierul</string> + <string name="could_not_verify_fingerprint">Nu s-a putut verifica amprenta</string> + <string name="manually_verify">Verifica manual</string> + <string name="are_you_sure_verify_fingerprint">Sigur vrei sa verifici amprenta OTR a persoanei de contact?</string> + <string name="pref_show_dynamic_tags">Arata etichetele dinamice</string> + <string name="pref_show_dynamic_tags_summary">Arata etichete de stare sub contacte </string> + <string name="enable_notifications">Activeaza notificari</string> + <string name="conference_with">Creeaza conferinta cu...</string> + <string name="no_conference_server_found">Serverul conferintei nu a fost gasita</string> + <string name="conference_creation_failed">Conferinta nu a putut fi creata!</string> + <string name="conference_created">Conferinta a fost creata!</string> + <string name="secret_accepted">Secret acceptat!</string> + <string name="reset">Reseteaza</string> + <string name="account_image_description">Avatar cont</string> + <string name="copy_otr_clipboard_description">Copiaza amprenta OTR in memorie</string> + <string name="copy_omemo_clipboard_description">Copiaza amprenta OMEMO in memorie</string> + <string name="regenerate_omemo_key">Genereaza din nou cheia OMEMO</string> + <string name="wipe_omemo_pep">Sterge alte dispozitive din PEP</string> + <string name="clear_other_devices">Curata lista dispozitive</string> + <string name="clear_other_devices_desc">Sigur vrei sa inlaturi toate celelalte dispozitive din mesajul de anunt OMEMO? Data viitoare cand dispozitivele se vor conecta, se vor anunta din nou, dar se poate ca ele sa nu fi primit mesajele trimise intre timp.</string> + <string name="purge_key">Sterge cheia</string> + <string name="purge_key_desc_part1">Sigur vrei sa stergi aceasta cheie?</string> + <string name="purge_key_desc_part2">Va fi considera compromisa ireversibil si nu vei mai putea crea o sesiune cu ea niciodata.</string> + <string name="error_no_keys_to_trust_server_error">Nu exista chei utilizabile pentru acest contact.\nIncercarea de a descarca o cheie de pe server a esuat. Se poate sa fie o problema cu serverul de contacte.</string> + <string name="error_no_keys_to_trust">Nu exista chei disponibile si utilizabile pentru acest contact. Daca ai sters vreuna din cheile contactului, trebuie sa generezi o alta noua.</string> + <string name="error_trustkeys_title">Eroare</string> + <string name="fetching_history_from_server">Descarc istoric de pe server</string> + <string name="no_more_history_on_server">Nu mai exista istoric pe server</string> + <string name="updating">Actualizez...</string> + <string name="password_changed">Parola schimbata</string> + <string name="could_not_change_password">Nu s-a putut schimba parola</string> + <string name="otr_session_not_started">Trimite un mesaj pentru a pornii o discutie criptata</string> + <string name="ask_question">Intreaba</string> + <string name="smp_explain_question">Tu si contactul tau aveti un secret in comun pe care nimeni altcineva nu il stie (cum ar fi o gluma sau ce ati mancat ultima data cand v-ati vazut) poti folosi acel secret ca sa va verificati amprenta fiecaruia.\n\nTu pui la dispozitie un indiciu sau o intrebare contactului iar el/ea va raspunde cu un mesaj sensibil la majuscule.</string> + <string name="smp_explain_answer">Contactul tau doreste sa iti verifice amprenta provocandu-te cu un secret partajat. Contactul tau a furnizat urmatorul indiciu sau intrebare legat de acel secret.</string> + <string name="shared_secret_hint_should_not_be_empty">Indiciul tau nu ar trebui sa fie gol</string> + <string name="shared_secret_can_not_be_empty">Secretul tau impartasit nu poate fi gol</string> + <string name="manual_verification_explanation">Compara cu grija amprenta afisata mai jos cu amprenta contactului.\nPoti folosi orice forma de comunicare de incredere precum un e-mail criptat sau o convorbire telefonica pentru a face schimb de amprente.</string> + <string name="change_password">Schimba parola</string> + <string name="current_password">Parola curenta</string> + <string name="new_password">Parola noua</string> + <string name="password_should_not_be_empty">Parola nu trebuie sa fie goala</string> + <string name="enable_all_accounts">Activeaza toate conturile</string> + <string name="disable_all_accounts">Dezactiveaza toate conturile</string> + <string name="perform_action_with">Efectuaza actiune cu</string> + <string name="no_affiliation">Fara afiliere</string> + <string name="no_role">Fara rol</string> + <string name="outcast">proscris</string> + <string name="member">Membru</string> + <string name="advanced_mode">Mod avansat</string> + <string name="grant_membership">Acorda calitatea de membru</string> + <string name="remove_membership">Abroga calitatea de membru</string> + <string name="grant_admin_privileges">Acorda privilegii de administrator</string> + <string name="remove_admin_privileges">Abroga privilegii de administrator</string> + <string name="remove_from_room">Inlatura din conferinta</string> + <string name="could_not_change_affiliation">Nu s-a putut schimba afilierea lui %s</string> + <string name="ban_from_conference">Interzice accesul la conferinta</string> + <string name="removing_from_public_conference">Incerci sa il inlaturi pe %s dintr-o conferinta publica. Singurul mod in care poti face asta este sa in blochezi pentru totdeauna.</string> + <string name="ban_now">Interzice accesul acum</string> + <string name="could_not_change_role">Nu s-a putut schimba rolul lui %s</string> + <string name="public_conference">Conferinta accesibila public</string> + <string name="private_conference">Conferinta privata, accesibila numai membrilor</string> + <string name="conference_options">Optiuni conferinta</string> + <string name="members_only">Privat, numai pentru membri</string> + <string name="non_anonymous">Ne anonim</string> + <string name="moderated">Monitorizata</string> + <string name="you_are_not_participating">Tu nu participi</string> + <string name="modified_conference_options">Optiuni conferinta modificate!</string> + <string name="could_not_modify_conference_options">Nu s-au putut modifca optiunile conferintei</string> + <string name="never">Niciodata</string> + <string name="thirty_minutes">30 minute</string> + <string name="one_hour">O ora</string> + <string name="two_hours">2 ore</string> + <string name="eight_hours">8 ore</string> + <string name="until_further_notice">Pana la noi ordine</string> + <string name="pref_input_options">Optiuni introducere</string> + <string name="pref_enter_is_send">ENTER trimite</string> + <string name="pref_enter_is_send_summary">Apasa tasta ENTER pentru a trimite mesajul</string> + <string name="pref_display_enter_key">Arata tasta ENTER</string> + <string name="pref_display_enter_key_summary">Preschimba tasta de zambilici in ENTER</string> + <string name="audio">audio</string> + <string name="video">video</string> + <string name="image">imagine</string> + <string name="pdf_document">document PDF</string> + <string name="apk">Aplicatie Android</string> + <string name="vcard">Contact</string> + <string name="received_x_file">Primit %s</string> + <string name="disable_foreground_service">Dezactiveaza serviciul in prim plan</string> + <string name="touch_to_open_conversations">Atinge pentru a deschide Conversations</string> + <string name="avatar_has_been_published">Avatarul a fost publicat!</string> + <string name="sending_x_file">Trimit %s</string> + <string name="offering_x_file">Ofer %s</string> + <string name="hide_offline">Ascunde deconectat</string> + <string name="disable_account">Dezactiveaza cont</string> + <string name="contact_is_typing">%s tasteaza...</string> + <string name="contact_has_stopped_typing">%s s-a oprit din scris</string> + <string name="pref_chat_states">Notificari cand cineva scrie</string> + <string name="pref_chat_states_summary">Anunta contactul cand scrii un nou mesaj</string> + <string name="send_location">Trimite locatia</string> + <string name="show_location">Arata locatia</string> + <string name="no_application_found_to_display_location">Nu s-a gasit nici o aplicatie care sa afiseze locatia</string> + <string name="location">Locatie</string> + <string name="received_location">Locatie primita</string> + <string name="title_undo_swipe_out_conversation">Conversatie inchisa</string> + <string name="title_undo_swipe_out_muc">A parasit conferinta</string> + <string name="pref_dont_trust_system_cas_title">Nu ai incredere in CA din sistem</string> + <string name="pref_dont_trust_system_cas_summary">Toate certificatele trebuie aprobate manual</string> + <string name="pref_remove_trusted_certificates_title">Inlatura certificatele</string> + <string name="pref_remove_trusted_certificates_summary">Sterge certificate aprobate manual</string> + <string name="toast_no_trusted_certs">Nici un certificat aprobat manual</string> + <string name="dialog_manage_certs_title">Inlatura certificatele</string> + <string name="dialog_manage_certs_positivebutton">Sterge selectia</string> + <string name="dialog_manage_certs_negativebutton">Anuleaza</string> + <plurals name="toast_delete_certificates"> + <item quantity="one">%d certificat sters</item> + <item quantity="few">%d certificate sterse</item> + <item quantity="other">%d certificate sterse</item> + </plurals> + <plurals name="select_contact"> + <item quantity="one">Selecteaza %d contact</item> + <item quantity="few">Selecteaza %d contacte</item> + <item quantity="other">Selecteaza %d contacte</item> + </plurals> + <string name="pref_quick_action_summary">Inlocuieste butonul de trimitere cu o actiune rapida</string> + <string name="pref_quick_action">Actiune rapida</string> + <string name="none">Nimic</string> + <string name="recently_used">Folosit recent</string> + <string name="choose_quick_action">Alege actiunea rapida</string> + <string name="search_for_contacts_or_groups">Cauta contacte sau grupuri</string> + <string name="send_private_message">trimite mesaj privat</string> + <string name="user_has_left_conference">%s a parasit conferinta!</string> + <string name="username">Nume utilizator</string> + <string name="username_hint">Nume utilizator</string> + <string name="invalid_username">Acesta nu este un nume de utilizator valabil</string> + <string name="download_failed_server_not_found">Descarcarea a esuat: Serverul nu a fost gasit</string> + <string name="download_failed_file_not_found">Descarcare esuata: Fisierul nu a fost gasit</string> + <string name="download_failed_could_not_connect">Descarcarea a esuat. Nu s-a putut realiza conexiunea cu gazda.</string> + <string name="pref_use_white_background">Foloseste un fundal alb</string> + <string name="pref_use_white_background_summary">Arata mesajele primite cu negru pe fond alb</string> + <string name="account_status_tor_unavailable">Reteaua Tor nu este disponibila</string> + <string name="server_info_broken">Deteriorat</string> + <string name="pref_presence_settings">Setari de prezenta</string> + <string name="pref_away_when_screen_off">Plecat cand ecranul este oprit</string> + <string name="pref_away_when_screen_off_summary">Marcheaza clientul drept plecat cand ecranul este oprit</string> + <string name="pref_xa_on_silent_mode">Indisponibil in mod silentios</string> + <string name="pref_xa_on_silent_mode_summary">Declara clientul drept indisponibil atunci cand dispozitivul este in mod silentios</string> + <string name="action_add_account_with_certificate">Adauga un cont cu certificat</string> + <string name="unable_to_parse_certificate">Nu se poate analiza certificatul</string> + <string name="authenticate_with_certificate">Lasa gol pentru a autentifica cu un certificat</string> + <string name="captcha_ocr">Text captcha de verificare</string> + <string name="captcha_required">Text captcha de verificare necesar</string> + <string name="captcha_hint">introdu textul din imagine</string> + <string name="certificate_chain_is_not_trusted">Seria de certificate nu este de incredere</string> + <string name="jid_does_not_match_certificate">ID-ul Jabber nu corespunde cu certificatul</string> + <string name="action_renew_certificate">Innoieste certificatul</string> + <string name="error_fetching_omemo_key">Eroare la preluarea cheii OMEMO!</string> + <string name="verified_omemo_key_with_certificate">Verifica cheia OMEMO cu un certificat</string> + <string name="device_does_not_support_certificates">Dispozitivul nu permite selectia unui certificat pentru client!</string> + <string name="pref_connection_options">Optiuni conexiune</string> + <string name="pref_use_tor">Conectare prin TOR</string> + <string name="pref_use_tor_summary">Trimite toate conexiunile prin reteaua Tor. Necesita OrBot</string> + <string name="account_settings_hostname">Nume gazda</string> + <string name="account_settings_port">Port</string> + <string name="hostname_or_onion">Adresa server- sau .onion</string> + <string name="not_a_valid_port">Acesta nu este un numar de port valabil</string> + <string name="not_valid_hostname">Acesta nu este un nume de gazda valabila</string> + <string name="connected_accounts">%1$d din %2$d conturi conectate</string> + <plurals name="x_messages"> + <item quantity="one">%d mesaj</item> + <item quantity="few">%d mesaje</item> + <item quantity="other">%d mesaje</item> + </plurals> + <string name="shared_file_with_x">Partajeaza fisierul cu %s...</string> + <string name="shared_image_with_x">Partajeaza imaginea cu %s.</string> + <string name="no_storage_permission">Conversations are nevoie de acces la stocarea externa</string> + <string name="sync_with_contacts">Sincronizeaza cu contactele</string> + <string name="sync_with_contacts_long">Conversations doreste sa potriveasta lista de contacte XMPP cu cea din dispozitiv pentru a putea afisa numule lor complete si avatarele.\n\nConversations doar v-a citi si potrivi local fara sa le incarce catre vreun server.\n\nUrmeaza sa fii intrebat daca doresti sa permiti accesul la contacte.</string> + <string name="certificate_information">Informatii despre certificat</string> + <string name="certificate_subject">Subiect</string> + <string name="certificate_issuer"> +Emitent</string> + <string name="certificate_cn">Nume comun</string> + <string name="certificate_o">Organizatie</string> + <string name="certificate_sha1">SHA1</string> + <string name="certicate_info_not_available">(Indisponibil)</string> + <string name="certificate_not_found">Certificat inexistent</string> + <string name="notify_on_all_messages">Notifica la toate mesajele</string> + <string name="notify_only_when_highlighted">Notifica numai la evidentiere</string> + <string name="notify_never">Notificari dezactivate</string> + <string name="notify_paused">Notificari suspendate</string> + <string name="pref_picture_compression">Comprima imaginile</string> + <string name="pref_picture_compression_summary">Redimensioneaza si comprima imaginile</string> + <string name="always">Mereu</string> + <string name="automatically">Automat</string> + <string name="battery_optimizations_enabled">Optimizare baterie activata</string> + <string name="battery_optimizations_enabled_explained">Dispozitivul dumneavoastra incearca sa optimizeze agresiv consumul bateriei pentru Conversations, asta poate duce la notificari intarziate sau chiar pierderi de mesaje.\nEste recomandat sa dezactivati aceste optimizari.</string> + <string name="battery_optimizations_enabled_dialog">Dispozitivul dumneavoastra incearca sa optimizeze agresiv consumul bateriei pentru Conversations, asta poate duce la notificari intarziate sau chiar pierderi de mesaje.\nIn continuare veti fi rugat sa dezactivati aceste optimizari.</string> + <string name="disable">Dezactivat</string> + <string name="selection_too_large">Zona selectata este prea mare</string> </resources> diff --git a/src/main/res/values-ru/strings.xml b/src/main/res/values-ru/strings.xml index 3a2515b7..68d84396 100644 --- a/src/main/res/values-ru/strings.xml +++ b/src/main/res/values-ru/strings.xml @@ -9,7 +9,7 @@ <string name="action_secure">Защищенная беседа</string> <string name="action_add_account">Добавить аккаунт</string> <string name="action_edit_contact">Редактировать контакт</string> - <string name="action_add_phone_book">Добавить в телефонную книгу</string> + <string name="action_add_phone_book">Добавить в адресную книгу</string> <string name="action_delete_contact">Удалить из списка</string> <string name="action_block_contact">Заблокировать контакт</string> <string name="action_unblock_contact">Разблокировать контакт</string> @@ -28,7 +28,8 @@ <string name="minutes_ago">%d мин. назад</string> <string name="unread_conversations">непрочитанных сообщений</string> <string name="sending">отправка…</string> - <string name="encrypted_message">Расшифровка сообщения. Пожалуйста, подождите…</string> + <string name="message_decrypting">Расшифровка сообщения. Подождите...</string> + <string name="pgp_message">OpenPGP зашифрованное сообщение</string> <string name="nick_in_use">Имя уже используется</string> <string name="admin">Администратор</string> <string name="owner">Владелец</string> @@ -74,10 +75,12 @@ <string name="clear_conversation_history">Очистить историю</string> <string name="clear_histor_msg">Вы хотите удалить все сообщения в этой беседе?\n\n<b>Предупреждение:</b> Данная операция не повлияет на сообщения, хранящиеся на других устройствах.</string> <string name="delete_messages">Удалить сообщения</string> - <string name="also_end_conversation">Завершить беседу</string> + <string name="also_end_conversation">Закончить эту беседу впоследствии</string> <string name="choose_presence">Укажите статус для контакта</string> - <string name="send_plain_text_message">Отправить незашифрованное текстовое сообщение</string> + <string name="send_unencrypted_message">Отправить незащифрованное сообщение</string> <string name="send_otr_message">Отправить OTR защифрованное сообщение</string> + <string name="send_omemo_message">Отправить OMEMO защифрованное сообщение</string> + <string name="send_omemo_x509_message">Послать v\\OMEMO зашифрованное сообщение</string> <string name="send_pgp_message">Отправить OpenPGP защифрованное сообщение</string> <string name="your_nick_has_been_changed">Ваш псевдоним был изменен</string> <string name="send_unencrypted">Отправить в незашифрованном виде</string> @@ -86,18 +89,19 @@ <string name="openkeychain_required_long">Conversations использует стороннее приложение под названием <b>OpenKeychain</b> для шифрования и расшифрования сообщений и управления открытыми ключами.\nПрограмма OpenKeychain распространяется под лицензией GPLv3 и доступна для загрузки через F-Droid или Google Play.\n\n<small>(Потребуется перезапуск Conversations после установки.)</small></string> <string name="restart">Перезапуск</string> <string name="install">Установка</string> + <string name="openkeychain_not_installed">Установите OpenKeychain пожалуйста</string> <string name="offering">предложение…</string> <string name="waiting">ожидание…</string> <string name="no_pgp_key">Нет OpenPGP ключа</string> <string name="contact_has_no_pgp_key">Conversations не может зашифровать сообщение, потому что удаленный пользователь не анонсирует свой открытый ключ.\n\n<small>Пожалуйста, попросите удаленного пользователя тоже установить OpenPGP.</small></string> <string name="no_pgp_keys">Нет OpenPGP ключей</string> <string name="contacts_have_no_pgp_keys">Conversations не может зашифровать сообщения, потому что удаленные пользователи не анонсируют свои открытые ключи.\n\n<small>Пожалуйста, попросите удаленных пользователей тоже установить OpenPGP.</small></string> - <string name="encrypted_message_received"><i>Зашифрованное сообщение получено. Нажмите здесь, чтобы расшифровать и посмотреть сообщение.</i></string> + <string name="encrypted_message_received"><i>Получено зашифрованное сообщение. Нажмите, чтобы расшифровать.</i></string> <string name="pref_general">Общие</string> <string name="pref_xmpp_resource">Название ресурса</string> <string name="pref_xmpp_resource_summary">Имя которым Conversations идентифицирует себя</string> <string name="pref_accept_files">Принимать файлы</string> - <string name="pref_accept_files_size_summary">Автоматический прием файлов…</string> + <string name="pref_accept_files_summary">Автоматический прием файлов…</string> <string name="pref_notification_settings">Настройки Уведомлений</string> <string name="pref_notifications">Уведомление</string> <string name="pref_notifications_summary">Использовать звуковое уведомление когда приходят новые сообщения</string> @@ -105,8 +109,6 @@ <string name="pref_vibrate_summary">Использовать вибрацию когда приходят новые сообщения</string> <string name="pref_sound">Звуковой сигнал</string> <string name="pref_sound_summary">Выберите звуковой сигнал для сообщений</string> - <string name="pref_conference_notifications">Уведомления конференции</string> - <string name="pref_conference_notifications_summary">Всегда сообщать при получении нового сообщения в конференции</string> <string name="pref_notification_grace_period">Отсрочка уведомлений</string> <string name="pref_notification_grace_period_summary">Не использовать уведомления, если вы прочитали сообщение на другом устройстве</string> <string name="pref_advanced_options">Дополнительные параметры</string> @@ -149,9 +151,10 @@ <string name="account_status_regis_not_sup">Сервер не поддерживает регистрацию</string> <string name="account_status_security_error">Ошибка безопасности</string> <string name="account_status_incompatible_server">Несовместимый сервер</string> - <string name="encryption_choice_none">Без шифрования</string> + <string name="encryption_choice_unencrypted">Без шифра</string> <string name="encryption_choice_otr">OTR</string> <string name="encryption_choice_pgp">OpenPGP</string> + <string name="encryption_choice_omemo">OMEMO</string> <string name="mgmt_account_edit">Редактировать аккаунт</string> <string name="mgmt_account_delete">Удалить</string> <string name="mgmt_account_disable">Отключить</string> @@ -170,7 +173,7 @@ <string name="passwords_do_not_match">Пароли не совпадают</string> <string name="invalid_jid">Недопустимый JID (Джаббер ID)</string> <string name="error_out_of_memory">Недостаточно памяти. Изображение слишком большое</string> - <string name="add_phone_book_text">Вы хотите добавить %s в свою телефонную книгу?</string> + <string name="add_phone_book_text">Вы хотите добавить %s в вашу адресную книгу?</string> <string name="contact_status_online">в сети</string> <string name="contact_status_free_to_chat">свободен для общения</string> <string name="contact_status_away">скоро буду</string> @@ -186,7 +189,8 @@ <string name="server_info_blocking">XEP-0191: Команда Блокирования</string> <string name="server_info_roster_version">XEP-0237: Управление версиями списков</string> <string name="server_info_stream_management">XEP-0198: Управление потоками</string> - <string name="server_info_pep">XEP-0163: PEP (Аватары)</string> + <string name="server_info_pep">XEP-0163: PEP (Аватары / OMEMO)</string> + <string name="server_info_http_upload">XEP-0363: Загрузка файлов по HTTP</string> <string name="server_info_available">доступен</string> <string name="server_info_unavailable">недоступен</string> <string name="missing_public_keys">Отсутствие анонсирования открытых ключей</string> @@ -204,17 +208,28 @@ <string name="reception_failed">Прием не удался</string> <string name="your_fingerprint">Контрольная сумма</string> <string name="otr_fingerprint">OTR контрольная сумма</string> + <string name="omemo_fingerprint">OMEMO отпечаток</string> + <string name="omemo_fingerprint_x509">v\\OMEMO отпечаток</string> + <string name="omemo_fingerprint_selected_message">OMEMO отпечаток сообщения</string> + <string name="omemo_fingerprint_x509_selected_message">v\\OMEMO отпечаток сообщения</string> + <string name="this_device_omemo_fingerprint">Собственный OMEMO отпечаток</string> + <string name="other_devices">Другие устройства</string> + <string name="trust_omemo_fingerprints">Доверенные отпечатки OMEMO</string> + <string name="fetching_keys">Получение ключей…</string> + <string name="done">Готово</string> <string name="verify">Подтвердить</string> <string name="decrypt">Дешифровать</string> <string name="conferences">Конференции</string> <string name="search">Поиск</string> <string name="create_contact">Создать контакт</string> + <string name="enter_contact">Добавить контакт</string> <string name="join_conference">Присоединиться к конференции</string> <string name="delete_contact">Удалить Контакт</string> <string name="view_contact_details">Посмотреть данные контакта</string> <string name="block_contact">Заблокировать контакт</string> <string name="unblock_contact">Разблокировать контакт</string> <string name="create">Создать</string> + <string name="select">Select</string> <string name="contact_already_exists">Контакт уже существует</string> <string name="join">Присоединиться</string> <string name="conference_address">Адрес конференции</string> @@ -249,7 +264,6 @@ <string name="skip">Пропустить</string> <string name="disable_notifications">Отключить уведомления</string> <string name="disable_notifications_for_this_conversation">Отключить уведомления для текущей беседы</string> - <string name="notifications_disabled">Уведомления отключены</string> <string name="enable">Включить</string> <string name="conference_requires_password">Конференция требует авторизации</string> <string name="enter_password">Введите пароль</string> @@ -284,15 +298,19 @@ <string name="pref_conference_name">Название конференции</string> <string name="pref_conference_name_summary">Использовать тему беседы заместо JID для отображения конференций</string> <string name="toast_message_otr_fingerprint">OTR-отпечаток скопирован в буфер обмена!</string> + <string name="toast_message_omemo_fingerprint">OMEMO отпечаток скопирован в буфер обмена!</string> <string name="conference_banned">Вы заблокированы в этой конференции</string> <string name="conference_members_only">Эта конференция требует членства</string> <string name="conference_kicked">Вы были удалены из конференции</string> <string name="using_account">использовать учётную запись %s</string> + <string name="checking_x">Проверка %s на сервере HTTP</string> <string name="not_connected_try_again">Вы неподключены. Попробуйте позже</string> + <string name="check_x_filesize">Проверьте размер %s</string> <string name="message_options">Опции сообщения</string> <string name="copy_text">Копировать текст</string> <string name="copy_original_url">Копировать адрес ссылки</string> <string name="send_again">Отправить ещё раз</string> + <string name="file_url">URL файла</string> <string name="message_text">Текст сообщения</string> <string name="url_copied_to_clipboard">Ссылка скопирована в буфер обмена</string> <string name="message_copied_to_clipboard">Сообщение скопировано в буфер обмена</string> @@ -304,7 +322,6 @@ <string name="verify_otr">Подтвердить OTR</string> <string name="remote_fingerprint">Удалённый отпечаток</string> <string name="scan">поиск</string> - <string name="or_touch_phones">(или сенсорные телефоны)</string> <string name="smp">Socialist Millionaire Protocol</string> <string name="shared_secret_hint">Подсказка или вопрос</string> <string name="shared_secret_secret">Общий секретный ключ</string> @@ -321,6 +338,9 @@ <string name="conversations_foreground_service">Диалоги</string> <string name="pref_keep_foreground_service">Оставить службу на переднем плане</string> <string name="pref_keep_foreground_service_summary">Не позволяет операционной системе закрыть ваше соединение</string> + <string name="pref_export_logs">Экспорт истории</string> + <string name="pref_export_logs_summary">Записать историю на SD карту</string> + <string name="notification_export_logs_title">Запись истории на SD карту</string> <string name="choose_file">Выберите файл</string> <string name="receiving_x_file">Получение %1$s (%2$d%% выполнено)</string> <string name="download_x_file">Загружено %s</string> @@ -347,6 +367,16 @@ <string name="reset">Сброс</string> <string name="account_image_description">Изображение учётной записи</string> <string name="copy_otr_clipboard_description">Скопировать OTR-отпечаток в буфер обмена</string> + <string name="copy_omemo_clipboard_description">Скопировать OMEMO-отпечаток в буфер обмена</string> + <string name="regenerate_omemo_key">Создать заново ключ OMEMO</string> + <string name="wipe_omemo_pep">Стереть другие устройства из PEP</string> + <string name="clear_other_devices">Очистить устройства</string> + <string name="clear_other_devices_desc">Вы уверены, что хотите очистить все остальные устройства из анонса ключей OMEMO? При соединении устройств в следующий раз новые ключи анонсируются автоматически, но устройства могут не получить сообщения, посланные до этого.</string> + <string name="purge_key">Очистить ключ</string> + <string name="purge_key_desc_part1">Вы уверены, что хотите удалить этот ключ?</string> + <string name="purge_key_desc_part2">Он будет необратимо считаться скомпрометированным, и вы никогда не сможете создать сессию с ним снова.</string> + <string name="error_no_keys_to_trust">Для этого контакта не существует доступных ключей. Если вы очистили все его ключи, ему необходимо создать новые.</string> + <string name="error_trustkeys_title">Ошибка</string> <string name="fetching_history_from_server">Получение истории с сервера</string> <string name="no_more_history_on_server">На сервере больше нет истории</string> <string name="updating">Обновление...</string> @@ -384,8 +414,10 @@ <string name="public_conference">Публичная конференция</string> <string name="private_conference">Приватная конференция только для членов</string> <string name="conference_options">Настройки конференции</string> - <string name="members_only">Закрытый доступ (только для участников)</string> + <string name="members_only">Частная, только для участников</string> <string name="non_anonymous">Не анонимно </string> + <string name="moderated">Модерируемая</string> + <string name="you_are_not_participating">Вы не участвуете</string> <string name="modified_conference_options">Настройки конференции изменены!</string> <string name="could_not_modify_conference_options">Не удалось изменить настройки конференции</string> <string name="never">Никогда</string> @@ -413,7 +445,7 @@ <string name="offering_x_file">Предложен %s</string> <string name="hide_offline">Скрыть пользователей вне сети</string> <string name="disable_account">Отключить учётную запись</string> - <string name="contact_is_typing">%s набирает сообщение...</string> + <string name="contact_is_typing">%s печатает…</string> <string name="contact_has_stopped_typing">%s прекратил набор</string> <string name="pref_chat_states">Оповещения о наборе</string> <string name="pref_chat_states_summary">Позволяет вашим контактам видеть когда вы пишете новое сообщение</string> @@ -424,7 +456,6 @@ <string name="received_location">Получено местоположение</string> <string name="title_undo_swipe_out_conversation">Беседа окончена</string> <string name="title_undo_swipe_out_muc">Покинул беседу</string> - <string name="pref_certificate_options">Опции сертификата</string> <string name="pref_dont_trust_system_cas_title">Не доверять системным УЦ</string> <string name="pref_dont_trust_system_cas_summary">Все сертификаты должны быть подтверждены вручную</string> <string name="pref_remove_trusted_certificates_title">Удалить сертификат</string> @@ -436,13 +467,13 @@ <plurals name="toast_delete_certificates"> <item quantity="one">Удалён %d сертификат</item> <item quantity="few">Удалено %d сертификатов</item> - <item quantity="many"></item> + <item quantity="many">Удалено %d сертификатов</item> <item quantity="other">Удалено %d сертификатов</item> </plurals> <plurals name="select_contact"> <item quantity="one">Выбран %d контакт</item> <item quantity="few">Выбрано %d контактов</item> - <item quantity="many"></item> + <item quantity="many">Выбрано %d контактов</item> <item quantity="other">Выбрано %d контактов</item> </plurals> <string name="pref_quick_action_summary">Заменить кнопку отправки кнопкой быстрого действия</string> @@ -450,4 +481,63 @@ <string name="none">Нет</string> <string name="recently_used">Последнее выбранное</string> <string name="choose_quick_action">Выбрать быстрое действие</string> + <string name="search_for_contacts_or_groups">Поиск контактов или групп</string> + <string name="send_private_message">Отправить частное сообщение</string> + <string name="user_has_left_conference">%s покинул конференцию!</string> + <string name="username">Имя пользователя</string> + <string name="username_hint">Имя пользователя</string> + <string name="invalid_username">Недопустимое имя пользователя</string> + <string name="download_failed_server_not_found">Загрузка не удалась: сервер не найден</string> + <string name="download_failed_file_not_found">Загрузка не удалась: файл не найден</string> + <string name="download_failed_could_not_connect">Загрузка не удалась: не удалось подключиться к серверу</string> + <string name="pref_use_white_background">Использовать белый фон</string> + <string name="pref_use_white_background_summary">Показывать принятые сообщения черным текстом на белом фоне</string> + <string name="account_status_tor_unavailable">Сеть Tor недоступна</string> + <string name="server_info_broken">Повреждено</string> + <string name="pref_presence_settings">Настройки присутствия</string> + <string name="pref_away_when_screen_off">Вышел когда экран выключен</string> + <string name="pref_away_when_screen_off_summary">Отмечает ваш ресурс как \"вышел\" когда экран выключен</string> + <string name="pref_xa_on_silent_mode">Не доступен в режиме без звука</string> + <string name="action_add_account_with_certificate">Добавить аккаунт с сертификатом</string> + <string name="unable_to_parse_certificate">Невозможно разобрать сертификат</string> + <string name="authenticate_with_certificate">Оставить пустым для входа с сертификатом</string> + <string name="captcha_ocr">Проверочный текст</string> + <string name="captcha_required">Необходима проверка</string> + <string name="captcha_hint">введите текст с картинки</string> + <string name="certificate_chain_is_not_trusted">Цепочка сертификата не доверена</string> + <string name="jid_does_not_match_certificate">Jabber ID не соответствует сертификату</string> + <string name="action_renew_certificate">Обновить сертификат</string> + <string name="error_fetching_omemo_key">Ошибка при получении OMEMO ключа!</string> + <string name="verified_omemo_key_with_certificate">Проверен OMEMO ключ с сертификатом!</string> + <string name="device_does_not_support_certificates">Ваше устройство не поддерживает выбор клиентских сертификатов!</string> + <string name="pref_connection_options">Настройки соединения</string> + <string name="pref_use_tor">Соединение через Tor</string> + <string name="pref_use_tor_summary">Направить все соединения через сеть TOR. Требуется Orbot</string> + <string name="account_settings_hostname">Имя сервера</string> + <string name="account_settings_port">Порт</string> + <string name="hostname_or_onion">Сервер- или .onion-Адрес</string> + <string name="not_a_valid_port">Это недопустимый номер порта</string> + <string name="not_valid_hostname">Это недопустимое имя сервера</string> + <string name="connected_accounts">%1$d из %2$d аккаунтов соединены</string> + <string name="shared_file_with_x">Общий файл с %s</string> + <string name="shared_image_with_x">Общее изображение с %s</string> + <string name="no_storage_permission">Conversations требуется доступ к внешнему хранилищу</string> + <string name="sync_with_contacts">Синхронизировать с контактами</string> + <string name="sync_with_contacts_long">Conversations ожидает совпадения вашего XMPP ростера с вашими контактами для отображения их полных имен и аватаров.\n\nConversations только считает ваши контакты и сопоставит их локально без отправки их на ваш сервер.\n\nСейчас вы получите запрос на разрешение доступа к вашим контактам.</string> + <string name="certificate_information">Информация о сертификате</string> + <string name="certificate_subject">Subject</string> + <string name="certificate_issuer">Выпустил</string> + <string name="certificate_o">Организация</string> + <string name="certificate_sha1">SHA-1</string> + <string name="certicate_info_not_available">(недоступно)</string> + <string name="certificate_not_found">Сертификатов не найдено</string> + <string name="notify_on_all_messages">Уведомлять о всех сообщениях</string> + <string name="notify_never">Уведомления запрещены</string> + <string name="notify_paused">Уведомления приостановлены</string> + <string name="pref_picture_compression">Сжать изображения</string> + <string name="always">Всегда</string> + <string name="automatically">Автоматически</string> + <string name="battery_optimizations_enabled">Оптимизации энергопотребления разрешены</string> + <string name="disable">Запретить</string> + <string name="selection_too_large">Выбранная область слишком большая</string> </resources> diff --git a/src/main/res/values-sk/strings.xml b/src/main/res/values-sk/strings.xml index 01211ad2..2990a8ed 100644 --- a/src/main/res/values-sk/strings.xml +++ b/src/main/res/values-sk/strings.xml @@ -9,7 +9,6 @@ <string name="action_secure">Zabezpečená konverzácia</string> <string name="action_add_account">Pridať účet</string> <string name="action_edit_contact">Upraviť meno</string> - <string name="action_add_phone_book">Pridať do telefónneho zoznamu</string> <string name="action_delete_contact">Vymazať zo zoznamu</string> <string name="action_block_contact">Zablokovať kontakt</string> <string name="action_unblock_contact">Odblokovať kontakt</string> @@ -27,8 +26,7 @@ <string name="minute_ago">pred 1 minútou</string> <string name="minutes_ago">pred %d minútami</string> <string name="unread_conversations">neprečítané konverzácie</string> - <string name="sending">odosielam…</string> - <string name="encrypted_message">Dešifrujem správu. Čakajte, prosím…</string> + <string name="sending">posielam...</string> <string name="nick_in_use">Prezývka už existuje</string> <string name="admin">Administrátor</string> <string name="owner">Vlastník</string> @@ -74,10 +72,11 @@ <string name="clear_conversation_history">Vymazať históriu konverzácií</string> <string name="clear_histor_msg">Chcete vymazať všetky správy v tejto konverzácii?\n\n<b>Varovanie:</b> Toto neovplyvní správy uložené v iných zariadeniach alebo serveroch.</string> <string name="delete_messages">Vymazať správy</string> - <string name="also_end_conversation">Následne ukončiť aj túto konverzáciu</string> + <string name="also_end_conversation">Neskôr ukončiť konverzáciu</string> <string name="choose_presence">Vybrať aktualizáciu stavu pre kontakt</string> - <string name="send_plain_text_message">Poslať textovú správu</string> + <string name="send_unencrypted_message">Poslať nezašifrovanú správu</string> <string name="send_otr_message">Poslať OTR šifrovanú správu</string> + <string name="send_omemo_message">Poslať OMEMO šifrovanú správu</string> <string name="send_pgp_message">Poslať OpenPGP šifrovanú správu</string> <string name="your_nick_has_been_changed">Prezývka sa zmenila</string> <string name="send_unencrypted">Poslať nešifrované</string> @@ -92,12 +91,11 @@ <string name="contact_has_no_pgp_key">Nie je možné zašifrovať správu v aplikácii Conversations, pretože druhá strana neoznamuje svoj verejný kľúč.\n\n<small>Požiadajte svoj kontakt o nastavenie OpenPGP.</small></string> <string name="no_pgp_keys">Nenašli sa žiadne OpenPGP kľúče</string> <string name="contacts_have_no_pgp_keys">Nie je možné zašifrovať správy v aplikácii Conversations, pretože kontakty neoznamujú svoj verejný kľúč.\n\n<small>Požiadajte svoje kontakty o nastavenie OpenPGP.</small></string> - <string name="encrypted_message_received"><i>Bola prijatá šifrovaná správa. Kliknite pre dešifrovanie a prečítanie.</i></string> <string name="pref_general">Všeobecné</string> <string name="pref_xmpp_resource">XMPP zdroj</string> <string name="pref_xmpp_resource_summary">Meno, ktorým sa tento klient identifikuje</string> <string name="pref_accept_files">Prijať súbory</string> - <string name="pref_accept_files_size_summary">Automaticky prijať súbory menšie ako…</string> + <string name="pref_accept_files_summary">Automaticky prijať súbory menšie ako…</string> <string name="pref_notification_settings">Nastavenia upozornení</string> <string name="pref_notifications">Upozornenia</string> <string name="pref_notifications_summary">Upozorniť pri prijatí novej správy</string> @@ -105,8 +103,6 @@ <string name="pref_vibrate_summary">Vibrovať pri prijatí novej správy</string> <string name="pref_sound">Zvuk</string> <string name="pref_sound_summary">Prehrať zvuk spolu s upozornením</string> - <string name="pref_conference_notifications">Upozornenia pri skupinovej konverzácii</string> - <string name="pref_conference_notifications_summary">Vždy upozorniť pri novej konferenčnej správe, nie len ak je zvýraznená</string> <string name="pref_notification_grace_period">Doba na prečítanie upozornenia</string> <string name="pref_notification_grace_period_summary">Neupozorňovať krátko po obdržaní kópie správy</string> <string name="pref_advanced_options">Rozšírené možnosti</string> @@ -149,9 +145,10 @@ <string name="account_status_regis_not_sup">Server nepodporuje registráciu</string> <string name="account_status_security_error">Bezpečnostná chyba</string> <string name="account_status_incompatible_server">Nekompatibilný server</string> - <string name="encryption_choice_none">Čistý text</string> + <string name="encryption_choice_unencrypted">Nezašifrovaný</string> <string name="encryption_choice_otr">OTR</string> <string name="encryption_choice_pgp">OpenPGP</string> + <string name="encryption_choice_omemo">OMEMO</string> <string name="mgmt_account_edit">Upraviť účet</string> <string name="mgmt_account_delete">Vymazať účet</string> <string name="mgmt_account_disable">Dočasne vypnúť</string> @@ -170,7 +167,6 @@ <string name="passwords_do_not_match">Heslá nezodpovedajú</string> <string name="invalid_jid">Toto nie je platné Jabber ID</string> <string name="error_out_of_memory">Nedostatok pamäti. Obrázok je príliš veľký</string> - <string name="add_phone_book_text">Chcete pridať %s do svojho telefónneho zoznamu?</string> <string name="contact_status_online">online</string> <string name="contact_status_free_to_chat">dostupný pre chat</string> <string name="contact_status_away">preč</string> @@ -186,7 +182,8 @@ <string name="server_info_blocking">XEP-0191: Blocking Command</string> <string name="server_info_roster_version">XEP-0237: Roster Versioning</string> <string name="server_info_stream_management">XEP-0198: Stream Management</string> - <string name="server_info_pep">XEP-0163: PEP (Avatars)</string> + <string name="server_info_pep">XEP-0163: PEP (Avatars / OMEMO)</string> + <string name="server_info_http_upload">XEP-0363: HTTP File Upload</string> <string name="server_info_available">dostupný</string> <string name="server_info_unavailable">nedostupný</string> <string name="missing_public_keys">Chýba oznámenie o verejnom kľúči</string> @@ -204,6 +201,12 @@ <string name="reception_failed">Príjem zlyhal</string> <string name="your_fingerprint">Váš identifikátor</string> <string name="otr_fingerprint">OTR identifikátor</string> + <string name="omemo_fingerprint">OMEMO identifikátor</string> + <string name="omemo_fingerprint_selected_message">OMEMO identifkátor správy</string> + <string name="this_device_omemo_fingerprint">Vlastný OMEMO identifikátor</string> + <string name="other_devices">Ostatné zariadenia</string> + <string name="trust_omemo_fingerprints">Dôverovať OMEMO identifikátoru</string> + <string name="done">Dokončený</string> <string name="verify">Overiť</string> <string name="decrypt">Dešifrovať</string> <string name="conferences">Skupinové konverzácie</string> @@ -249,7 +252,6 @@ <string name="skip">Preskočiť</string> <string name="disable_notifications">Vypnúť upozornenia</string> <string name="disable_notifications_for_this_conversation">Vypnúť upozornenia pre túto konverzáciu</string> - <string name="notifications_disabled">Upozornenia sú vypnuté</string> <string name="enable">Povoliť</string> <string name="conference_requires_password">Skupinová konverzácia vyžaduje heslo</string> <string name="enter_password">Vložiť heslo</string> @@ -288,6 +290,7 @@ <string name="conference_members_only">Táto konverzácia je iba pre členov</string> <string name="conference_kicked">Vyčlenili vás z tejto konverzácie</string> <string name="using_account">Používa sa účet %s</string> + <string name="checking_x">Overiť %s na HTTP host</string> <string name="not_connected_try_again">Nie ste pripojený. Skúste to neskôr</string> <string name="check_x_filesize">Overiť %s veľkosť</string> <string name="message_options">Možnosti správy</string> @@ -306,7 +309,6 @@ <string name="verify_otr">Overiť OTR</string> <string name="remote_fingerprint">Vzdialený identifikátor</string> <string name="scan">skenovať</string> - <string name="or_touch_phones">(alebo dotykové telefóny)</string> <string name="smp">Socialist Millionaire Protocol</string> <string name="shared_secret_hint">Pomôcka alebo Otázka</string> <string name="shared_secret_secret">Spoločné tajomstvo</string> @@ -349,6 +351,8 @@ <string name="reset">Vymazať</string> <string name="account_image_description">Avatar účtu</string> <string name="copy_otr_clipboard_description">Skopírovať OTR identifikátor do schránky</string> + <string name="copy_omemo_clipboard_description">Skopírovať OMEMO identifikátor do schránky</string> + <string name="error_trustkeys_title">Chyba</string> <string name="fetching_history_from_server">Načítať históriu zo serveru</string> <string name="no_more_history_on_server">Na serveri nie je žiadna ďalšia história</string> <string name="updating">Aktualizujem...</string> @@ -386,8 +390,8 @@ <string name="public_conference">Verejne prístupná skupinová konverzácia</string> <string name="private_conference">Súkromná konverzácia iba pre členov</string> <string name="conference_options">Možnosti skupinovej konverzácie</string> - <string name="members_only">Súkromný (Iba pre členov)</string> <string name="non_anonymous">Neanonymný</string> + <string name="you_are_not_participating">Nezúčastňujete sa</string> <string name="modified_conference_options">Možnosti skupinovej konverzácie nastavené!</string> <string name="could_not_modify_conference_options">Nepodarilo sa nastaviť možnosti skupinovej konverzácie</string> <string name="never">Nikdy</string> @@ -415,7 +419,6 @@ <string name="offering_x_file">Ponúkam %s</string> <string name="hide_offline">Skryť neprihlásených</string> <string name="disable_account">Vypnúť účet</string> - <string name="contact_is_typing">%s píše...</string> <string name="contact_has_stopped_typing">%s prestal písať</string> <string name="pref_chat_states">Upozornenia pri písaní</string> <string name="pref_chat_states_summary">Upozorniť kontakt, keď píšete novú správu</string> @@ -426,7 +429,6 @@ <string name="received_location">Prijatá poloha</string> <string name="title_undo_swipe_out_conversation">Konverzácia zatvorená</string> <string name="title_undo_swipe_out_muc">Opustil skupinovú konverzáciu</string> - <string name="pref_certificate_options">Možnosti certifikátu</string> <string name="pref_dont_trust_system_cas_title">Nedôverovať systému CAs</string> <string name="pref_dont_trust_system_cas_summary">Všetky certifikáty musia byť ručne schválené</string> <string name="pref_remove_trusted_certificates_title">Odstrániť certifikáty</string> @@ -450,5 +452,17 @@ <string name="none">Žiadny</string> <string name="recently_used">Naposledy použitý</string> <string name="choose_quick_action">Vybrať rýchlu voľbu</string> - <string name="file_not_found_on_remote_host">Súbor sa na vzdialenom serveri nenašiel</string> + <string name="search_for_contacts_or_groups">Hľadať kontakty alebo skupiny</string> + <string name="send_private_message">Poslať súkromnú správu</string> + <string name="user_has_left_conference">%s opustil skupinovú konverzáciu!</string> + <string name="username">Užívateľské meno</string> + <string name="username_hint">Užívateľské meno</string> + <string name="invalid_username">Toto nie je platné užívateľské meno</string> + <string name="pref_xa_on_silent_mode">Nedostupný v tichom režime</string> + <string name="action_add_account_with_certificate">Pridať účet s certifikátom</string> + <string name="unable_to_parse_certificate">Neschopný analyzovať certifikát</string> + <string name="captcha_ocr">Text CAPTCHA</string> + <string name="captcha_required">Potrebný CAPTCHA</string> + <string name="captcha_hint">vložiť text z obrázku</string> + <string name="action_renew_certificate">Obnoviť certifikát</string> </resources> diff --git a/src/main/res/values-sr/strings.xml b/src/main/res/values-sr/strings.xml index 082cfa46..43e8fc22 100644 --- a/src/main/res/values-sr/strings.xml +++ b/src/main/res/values-sr/strings.xml @@ -5,7 +5,7 @@ <string name="action_accounts">Управљај налозима</string> <string name="action_end_conversation">Окончај преписку</string> <string name="action_contact_details">Детаљи контакта</string> - <string name="action_muc_details">Детаљи конференције</string> + <string name="action_muc_details">Детаљи групног ћаскања</string> <string name="action_secure">Безбедна преписка</string> <string name="action_add_account">Додај налог</string> <string name="action_edit_contact">Уреди име</string> @@ -17,9 +17,9 @@ <string name="action_unblock_domain">Одблокирај домен</string> <string name="title_activity_manage_accounts">Управљање налозима</string> <string name="title_activity_settings">Поставке</string> - <string name="title_activity_conference_details">Детаљи конференције</string> + <string name="title_activity_conference_details">Детаљи групног ћаскања</string> <string name="title_activity_contact_details">Детаљи контакта</string> - <string name="title_activity_sharewith">Подели са преписком</string> + <string name="title_activity_sharewith">Подели у преписци</string> <string name="title_activity_start_conversation">Почни преписку</string> <string name="title_activity_choose_contact">Изабери контакт</string> <string name="title_activity_block_list">Списак блокираних</string> @@ -28,7 +28,8 @@ <string name="minutes_ago">пре %d минута</string> <string name="unread_conversations">непрочитане преписке</string> <string name="sending">шаљем…</string> - <string name="encrypted_message">Дешифрујем поруку, сачекајте…</string> + <string name="message_decrypting">Дешифрујем поруку, сачекајте…</string> + <string name="pgp_message">ОпенПГП шифрована порука</string> <string name="nick_in_use">Надимак је већ у употреби</string> <string name="admin">Администратор</string> <string name="owner">Власник</string> @@ -44,7 +45,7 @@ <string name="remove_bookmark_text">Желите ли да уклоните %s са обележивача? Преписка са овим контактом неће бити уклоњена.</string> <string name="register_account">Региструј нови налог на серверу</string> <string name="change_password_on_server">Промени лозинку на серверу</string> - <string name="share_with">Подели са…</string> + <string name="share_with">Подели помоћу…</string> <string name="start_conversation">Почни преписку</string> <string name="invite_contact">Позови контакта</string> <string name="contacts">Контакти</string> @@ -63,7 +64,7 @@ <string name="send_never">Не питај више</string> <string name="problem_connecting_to_account">Не могу да се повежем са налогом</string> <string name="problem_connecting_to_accounts">Не могу да се повежем са више налога</string> - <string name="touch_to_fix">Додирните овде да бисте управљали вашим налозима</string> + <string name="touch_to_fix">Тапните овде да бисте управљали вашим налозима</string> <string name="attach_file">Приложи фајл</string> <string name="not_in_roster">Контакт није на вашем списку контаката. Желите ли да га додате?</string> <string name="add_contact">Додај контакт</string> @@ -76,8 +77,10 @@ <string name="delete_messages">Обриши поруке</string> <string name="also_end_conversation">Окончај ову преписку након тога</string> <string name="choose_presence">Избор присутности за контакта</string> - <string name="send_plain_text_message">Пошаљи обичну текстуалну поруку</string> + <string name="send_unencrypted_message">Пошаљи нешифровану поруку</string> <string name="send_otr_message">Пошаљи ОТР шифровану поруку</string> + <string name="send_omemo_message">Пошаљи ОМЕМО шифровану поруку</string> + <string name="send_omemo_x509_message">Пошаљи v\\ОМЕМО шифровану поруку</string> <string name="send_pgp_message">Пошаљи ОпенПГП шифровану поруку</string> <string name="your_nick_has_been_changed">Ваш надимак је промењен</string> <string name="send_unencrypted">Пошаљи нешифровано</string> @@ -86,13 +89,14 @@ <string name="openkeychain_required_long">Конверзација користи апликацију <b>Отворени кључарник</b> за шифровање и дешифровање порука и управљање вашим јавним кључевима.\n\nОтворени кључарник је лиценциран под ГПЛв3 и доступан је на Ф-дроиду у Гугловој Плеј продавници.\n\n<small>(Поново покрените Конверзацију након тога.)</small></string> <string name="restart">Поново покрени</string> <string name="install">Инсталирај</string> + <string name="openkeychain_not_installed">Инсталирајте Отворени кључарник</string> <string name="offering">нудим…</string> <string name="waiting">чекам…</string> <string name="no_pgp_key">Нема ОпенПГП кључа</string> <string name="contact_has_no_pgp_key">Конверзација није могла да шифрује ваше поруке јер ваш контакт не објављује свој јавни кључ.\n\n<small>Замолите вашег контакта да постави ОпенПГП.</small></string> <string name="no_pgp_keys">Нема ОпенПГП кључева</string> <string name="contacts_have_no_pgp_keys">Конверзација није могла да шифрује ваше поруке јер ваши контакти не објављују свој јавни кључ.\n\n<small>Замолите ваше контакте да поставе ОпенПГП.</small></string> - <string name="encrypted_message_received"><i>Примљена је шифрована порука. Додирните за дешифровање и приказ.</i></string> + <string name="encrypted_message_received"><i>Примљена је шифрована порука. Тапните за дешифровање.</i></string> <string name="pref_general">Опште</string> <string name="pref_xmpp_resource">ИксМПП ресурс</string> <string name="pref_xmpp_resource_summary">Име са којим се овај клијент идентификује</string> @@ -105,10 +109,8 @@ <string name="pref_vibrate_summary">Вибрирај кад стигне нова порука</string> <string name="pref_sound">Звук</string> <string name="pref_sound_summary">Звуци обавештења</string> - <string name="pref_conference_notifications">Обавештења конференције</string> - <string name="pref_conference_notifications_summary">Увек обавести кад нова порука у конференцији стигне уместо само кад је означена</string> <string name="pref_notification_grace_period">Период одгоде обавештења</string> - <string name="pref_notification_grace_period_summary">Онемогући обавештења на кратко по примању карбон копије</string> + <string name="pref_notification_grace_period_summary">Искључи обавештења на кратко по примању карбон копије</string> <string name="pref_advanced_options">Напредне поставке</string> <string name="pref_never_send_crash">Никад не шаљи извештаје о паду</string> <string name="pref_never_send_crash_summary">Слањем контратрага помажете текући развој Конверзације</string> @@ -136,7 +138,7 @@ <string name="error_io_exception">Општа У/И грешка. Можда вам је нестало простора у складишту?</string> <string name="error_security_exception_during_image_copy">Апликација којом сте изабрали ову слику није дала довољне дозволе за читање фајла.\n\n<small>Користите други менаџер фајлова да изаберете слику</small></string> <string name="account_status_unknown">Непознато</string> - <string name="account_status_disabled">Привремено онемогућен</string> + <string name="account_status_disabled">Привремено искључен</string> <string name="account_status_online">На вези</string> <string name="account_status_connecting">Повезивање\u2026</string> <string name="account_status_offline">Ван везе</string> @@ -149,15 +151,16 @@ <string name="account_status_regis_not_sup">Сервер не подржава регистрацију</string> <string name="account_status_security_error">Безбедносна грешка</string> <string name="account_status_incompatible_server">Некомпатибилан сервер</string> - <string name="encryption_choice_none">Обичан текст</string> + <string name="encryption_choice_unencrypted">Нешифровано</string> <string name="encryption_choice_otr">ОТР</string> <string name="encryption_choice_pgp">ОпенПГП</string> + <string name="encryption_choice_omemo">ОМЕМО</string> <string name="mgmt_account_edit">Уреди налог</string> <string name="mgmt_account_delete">Обриши налог</string> - <string name="mgmt_account_disable">Привремено онемогући</string> + <string name="mgmt_account_disable">Привремено искључи</string> <string name="mgmt_account_publish_avatar">Објави аватар</string> <string name="mgmt_account_publish_pgp">Објави ОпенПГП јавни кључ</string> - <string name="mgmt_account_enable">Омогући налог</string> + <string name="mgmt_account_enable">Укључи налог</string> <string name="mgmt_account_are_you_sure">Да ли сте сигурни?</string> <string name="mgmt_account_delete_confirm_text">Ако обришете ваш налог изгубићете сав историјат преписке</string> <string name="attach_record_voice">Сними глас</string> @@ -170,14 +173,14 @@ <string name="passwords_do_not_match">Лозинке се не поклапају</string> <string name="invalid_jid">Ово није исправан Џабер ИД</string> <string name="error_out_of_memory">Нестало меморије. Слика је превелика</string> - <string name="add_phone_book_text">Желите ли да додате %s у именик вашег телефона?</string> + <string name="add_phone_book_text">Желите ли да додате %s у ваш именик?</string> <string name="contact_status_online">на вези</string> <string name="contact_status_free_to_chat">слободан за ћаскање</string> <string name="contact_status_away">одсутан</string> <string name="contact_status_extended_away">продужено одсутан</string> <string name="contact_status_do_not_disturb">не узнемиравај</string> <string name="contact_status_offline">ван везе</string> - <string name="muc_details_conference">Конференција</string> + <string name="muc_details_conference">Групно ћаскање</string> <string name="muc_details_other_members">Остали чланови</string> <string name="server_info_show_more">Подаци о серверу</string> <string name="server_info_mam">XEP-0313: МАМ</string> @@ -186,7 +189,8 @@ <string name="server_info_blocking">XEP-0191: наредба блокирања</string> <string name="server_info_roster_version">XEP-0237: верзионисање ростера</string> <string name="server_info_stream_management">XEP-0198: менаџмент тока</string> - <string name="server_info_pep">XEP-0163: ПЕП (аватари)</string> + <string name="server_info_pep">XEP-0163: PEP (аватари/ОМЕМО)</string> + <string name="server_info_http_upload">XEP-0363: ХТТП отпремање фајлова</string> <string name="server_info_available">доступан</string> <string name="server_info_unavailable">недоступан</string> <string name="missing_public_keys">Недостају објаве јавног кључа</string> @@ -204,33 +208,44 @@ <string name="reception_failed">Примање није успело</string> <string name="your_fingerprint">Ваш отисак</string> <string name="otr_fingerprint">ОТР отисак</string> + <string name="omemo_fingerprint">ОМЕМО отисак</string> + <string name="omemo_fingerprint_x509">v\\ОМЕМО отисак</string> + <string name="omemo_fingerprint_selected_message">ОМЕМО отисак поруке</string> + <string name="omemo_fingerprint_x509_selected_message">v\\ОМЕМО отисак поруке</string> + <string name="this_device_omemo_fingerprint">Сопствени ОМЕМО отисак</string> + <string name="other_devices">Остали уређаји</string> + <string name="trust_omemo_fingerprints">Поуздај се у ОМЕМО отиске</string> + <string name="fetching_keys">Добављам кључеве…</string> + <string name="done">Готово</string> <string name="verify">Овери</string> <string name="decrypt">Дешифруј</string> - <string name="conferences">Конференције</string> + <string name="conferences">Групна ћаскања</string> <string name="search">Тражи</string> <string name="create_contact">Направи контакт</string> - <string name="join_conference">Придружи се конференцији</string> + <string name="enter_contact">Унеси контакт</string> + <string name="join_conference">Придружи се групном ћаскању</string> <string name="delete_contact">Обриши контакт</string> <string name="view_contact_details">Прикажи детаље контакта</string> <string name="block_contact">Блокирај контакт</string> <string name="unblock_contact">Одблокирај контакт</string> <string name="create">Направи</string> + <string name="select">Изабери</string> <string name="contact_already_exists">Контакт већ постоји</string> <string name="join">Придружи се</string> - <string name="conference_address">Адреса конференције</string> + <string name="conference_address">Адреса групног ћаскања</string> <string name="conference_address_example">soba@konferencija.primer.com</string> <string name="save_as_bookmark">Сачувај као обележивач</string> <string name="delete_bookmark">Обриши обележивач</string> <string name="bookmark_already_exists">Овај обележивач већ постоји</string> <string name="you">Ви</string> - <string name="action_edit_subject">Уреди предмет конференције</string> - <string name="conference_not_found">Конференција није нађена</string> + <string name="action_edit_subject">Уреди предмет групног ћаскања</string> + <string name="conference_not_found">Групно ћаскање није нађено</string> <string name="leave">Напусти</string> <string name="contact_added_you">Контакт вас је додао на списак контаката</string> <string name="add_back">Додај га</string> <string name="contact_has_read_up_to_this_point">%s је прочитао довде</string> <string name="publish">Објави</string> - <string name="touch_to_choose_picture">Додирните аватар да изаберете слику из галерије</string> + <string name="touch_to_choose_picture">Тапните аватар да изаберете слику из галерије</string> <string name="publish_avatar_explanation">Имајте на уму: Свима који су претплаћени на ваше ажурирање присутности биће дозвољено да виде ову слику.</string> <string name="publishing">Објављујем…</string> <string name="error_publish_avatar_server_reject">Сервер је одбио вашу објаву</string> @@ -247,11 +262,10 @@ <string name="server_info_session_established">Текућа сесија успостављена</string> <string name="additional_information">Додатни подаци</string> <string name="skip">Прескочи</string> - <string name="disable_notifications">Онемогући обавештења</string> - <string name="disable_notifications_for_this_conversation">Онемогући обавештења за ову преписку</string> - <string name="notifications_disabled">Обавештења су онемогућена</string> - <string name="enable">Омогући</string> - <string name="conference_requires_password">Конференција захтева лозинку</string> + <string name="disable_notifications">Искључи обавештења</string> + <string name="disable_notifications_for_this_conversation">Искључи обавештења за ову преписку</string> + <string name="enable">Укључи</string> + <string name="conference_requires_password">Групно ћаскање захтева лозинку</string> <string name="enter_password">Унесите лозинку</string> <string name="missing_presence_updates">Нема ажуриране присутности од контакта</string> <string name="request_presence_updates">Најпре захтевајте ажурирање присутности од вашег контакта.\n\n<small>Ово ће омогућити да се одреди којег клијента ваш контакт користи.</small></string> @@ -262,7 +276,7 @@ <string name="without_mutual_presence_updates"><b>Упозорење:</b> Слање овога без узајамних ажурирања присутности би могло да узрокује неочекиване проблеме.\n\n<small>Идите на детаље контакта да бисте проверили претплате на присутности.</small></string> <string name="pref_encryption_settings">Поставке шифровања</string> <string name="pref_force_encryption">Присили крај-на-крај шифровање</string> - <string name="pref_force_encryption_summary">Увек шифруј поруке (осим за конференције)</string> + <string name="pref_force_encryption_summary">Увек шифруј поруке (осим за групна ћаскања)</string> <string name="pref_dont_save_encrypted">Не успремај шифроване поруке</string> <string name="pref_dont_save_encrypted_summary">Упозорење: Ово може да доведе до губитка порука</string> <string name="pref_expert_options">Опције за стручњаке</string> @@ -272,8 +286,8 @@ <string name="title_pref_quiet_hours">Тихи сати</string> <string name="title_pref_quiet_hours_start_time">Време почетка</string> <string name="title_pref_quiet_hours_end_time">Време завршетка</string> - <string name="title_pref_enable_quiet_hours">Омогући тихе сате</string> - <string name="pref_quiet_hours_summary">Обавештења ће бити утишана за време тихих сати</string> + <string name="title_pref_enable_quiet_hours">Укључи тихе сате</string> + <string name="pref_quiet_hours_summary">Обавештења ће бити ућуткана за време тихих сати</string> <string name="pref_use_larger_font">Повећај величину фонта</string> <string name="pref_use_larger_font_summary">Користи веће величине фонта за читаву апликацију</string> <string name="pref_use_send_button_to_indicate_status">Дугме слања показује стање</string> @@ -281,12 +295,13 @@ <string name="pref_use_indicate_received_summary">Означи примљене поруке зеленом квачицом, ако је подржано</string> <string name="pref_use_send_button_to_indicate_status_summary">Боја дугмета за слање показује стање контакта</string> <string name="pref_expert_options_other">Остало</string> - <string name="pref_conference_name">Име конференције</string> - <string name="pref_conference_name_summary">Предмет собе уместо ЈИД-а идентификује конференцију</string> + <string name="pref_conference_name">Назив групног ћаскања</string> + <string name="pref_conference_name_summary">Предмет собе уместо ЈИД-а идентификује групно ћаскање</string> <string name="toast_message_otr_fingerprint">ОТР отисак копиран на клипборд!</string> - <string name="conference_banned">Забрањени сте на овој конференцији</string> - <string name="conference_members_only">Ова конференција је само за чланове</string> - <string name="conference_kicked">Шутнути сте из ове конференције</string> + <string name="toast_message_omemo_fingerprint">ОМЕМО отисак копиран на клипборд!</string> + <string name="conference_banned">Забрањени сте на овом групном ћаскању</string> + <string name="conference_members_only">Ово групно ћаскање је само за чланове</string> + <string name="conference_kicked">Шутнути сте из овог групног ћаскања</string> <string name="using_account">преко налога %s</string> <string name="checking_x">Проверавам %s на ХТТП домаћину</string> <string name="not_connected_try_again">Нисте повезани. Покушајте поново касније</string> @@ -307,7 +322,6 @@ <string name="verify_otr">Овери ОТР</string> <string name="remote_fingerprint">Отисак удаљеног</string> <string name="scan">очитај</string> - <string name="or_touch_phones">(или додирните телефоне)</string> <string name="smp">Социјалистички милионер протокол</string> <string name="shared_secret_hint">Наговештај или питање</string> <string name="shared_secret_secret">Заједничка тајна</string> @@ -324,6 +338,9 @@ <string name="conversations_foreground_service">Конверзација</string> <string name="pref_keep_foreground_service">Држи сервис у првом плану</string> <string name="pref_keep_foreground_service_summary">Спречава оперативни систем да прекине вашу везу</string> + <string name="pref_export_logs">Извези записе</string> + <string name="pref_export_logs_summary">Упис записа на СД картицу</string> + <string name="notification_export_logs_title">Уписујем записе на СД картицу</string> <string name="choose_file">Изабери фајл</string> <string name="receiving_x_file">Примам %1$s (%2$d%% завршено)</string> <string name="download_x_file">Преузми %s</string> @@ -341,15 +358,26 @@ <string name="are_you_sure_verify_fingerprint">Желите ли заиста да оверите ОТР отисак вашег контакта?</string> <string name="pref_show_dynamic_tags">Прикажи динамичке ознаке</string> <string name="pref_show_dynamic_tags_summary">Приказ ознака испод контаката</string> - <string name="enable_notifications">Омогући обавештења</string> - <string name="conference_with">Направи конференцију са…</string> - <string name="no_conference_server_found">Сервер конференције није нађен</string> - <string name="conference_creation_failed">Прављење конференције није успело!</string> - <string name="conference_created">Конференција направљена!</string> + <string name="enable_notifications">Укључи обавештења</string> + <string name="conference_with">Направи групно ћаскање са…</string> + <string name="no_conference_server_found">Сервер групног ћаскања није нађен</string> + <string name="conference_creation_failed">Прављење групног ћаскања није успело!</string> + <string name="conference_created">Групно ћаскање направљено!</string> <string name="secret_accepted">Тајна прихваћена!</string> <string name="reset">Ресетуј</string> <string name="account_image_description">Аватар налога</string> <string name="copy_otr_clipboard_description">Копирај ОТР отисак на клипборд</string> + <string name="copy_omemo_clipboard_description">Копирај ОМЕМО отисак на клипборд</string> + <string name="regenerate_omemo_key">Поново генериши ОМЕМО кључ</string> + <string name="wipe_omemo_pep">Уклони остале уређаје са ПЕП-а</string> + <string name="clear_other_devices">Очисти уређаје</string> + <string name="clear_other_devices_desc">Желите ли заиста да уклоните све остале уређаје са ОМЕМО објаве? Када се ваши уређаји следећи пут повежу, објавиће се сами, али у међувремену можда неће примати поруке.</string> + <string name="purge_key">Очисти кључ</string> + <string name="purge_key_desc_part1">Желите ли заиста да очистите овај кључ?</string> + <string name="purge_key_desc_part2">Неповратно ће бити сматран компровитованим, и њиме више никад нећете моћи да успоставите сесију.</string> + <string name="error_no_keys_to_trust_server_error">Нема употребљивих кључева за овај контакт.\nДобављање нових кључева са сервера није било успешно. Можда нешто није у реду са сервером ваших контаката.</string> + <string name="error_no_keys_to_trust">Нема употребљивих кључева за овај контакт. Ако сте очистили било који од тих кључева, контакт мора да генерише нови.</string> + <string name="error_trustkeys_title">Грешка</string> <string name="fetching_history_from_server">Добављам историјат са сервера</string> <string name="no_more_history_on_server">Нема више историјата на серверу</string> <string name="updating">Ажурирам…</string> @@ -366,8 +394,8 @@ <string name="current_password">Текућа лозинка</string> <string name="new_password">Нова лозинка</string> <string name="password_should_not_be_empty">Лозинка не може бити празна</string> - <string name="enable_all_accounts">Омогући све налоге</string> - <string name="disable_all_accounts">Онемогући све налоге</string> + <string name="enable_all_accounts">Укључи све налоге</string> + <string name="disable_all_accounts">Искључи све налоге</string> <string name="perform_action_with">Изврши радњу са</string> <string name="no_affiliation">Без припадности</string> <string name="no_role">Без улоге</string> @@ -378,19 +406,21 @@ <string name="remove_membership">Опозови чланство</string> <string name="grant_admin_privileges">Одобри админ. привилегије</string> <string name="remove_admin_privileges">Одобри админ. привилегије</string> - <string name="remove_from_room">Уклони из конференције</string> + <string name="remove_from_room">Уклони из групног ћаскања</string> <string name="could_not_change_affiliation">Не могу да изменим припадност за %s</string> - <string name="ban_from_conference">Забрани са конференције</string> - <string name="removing_from_public_conference">Покушавате да уклоните %s са јавне конференције. Једини начин да то урадите је да забраните тог корисника заувек.</string> + <string name="ban_from_conference">Забрани на групном ћаскању</string> + <string name="removing_from_public_conference">Покушавате да уклоните %s са групног ћаскања. Једини начин да то урадите је да забраните тог корисника заувек.</string> <string name="ban_now">Забрани одмах</string> <string name="could_not_change_role">Не могу да изменим улогу за %s</string> - <string name="public_conference">Јавно доступна конференција</string> - <string name="private_conference">Лична, само за чланове конференција</string> - <string name="conference_options">Опције конференције</string> - <string name="members_only">Лична (само чланови)</string> + <string name="public_conference">Јавно доступно групно ћаскање</string> + <string name="private_conference">Лично, само за чланове</string> + <string name="conference_options">Опције групног ћаскања</string> + <string name="members_only">Лична, само чланови</string> <string name="non_anonymous">Неанонимна</string> - <string name="modified_conference_options">Поставке конференције измењене!</string> - <string name="could_not_modify_conference_options">Не могу да изменим поставке конференције</string> + <string name="moderated">Уређивана</string> + <string name="you_are_not_participating">Не учествујете</string> + <string name="modified_conference_options">Поставке групног ћаскања измењене!</string> + <string name="could_not_modify_conference_options">Не могу да изменим поставке групног ћаскања</string> <string name="never">никад</string> <string name="thirty_minutes">30 минута</string> <string name="one_hour">1 сат</string> @@ -409,14 +439,14 @@ <string name="apk">Апликација за Андроид</string> <string name="vcard">Контакт</string> <string name="received_x_file">Примљено %s</string> - <string name="disable_foreground_service">Онемогући сервис у првом плану</string> - <string name="touch_to_open_conversations">Додирните да отворите Конверзацију</string> + <string name="disable_foreground_service">Искључи сервис у првом плану</string> + <string name="touch_to_open_conversations">Тапните да отворите Конверзацију</string> <string name="avatar_has_been_published">Аватар је објављен!</string> <string name="sending_x_file">Шаљем %s</string> <string name="offering_x_file">Нудим %s</string> <string name="hide_offline">Сакриј неповезане</string> - <string name="disable_account">Онемогући налог</string> - <string name="contact_is_typing">%s куца...</string> + <string name="disable_account">Искључи налог</string> + <string name="contact_is_typing">%s куца…</string> <string name="contact_has_stopped_typing">%s престаде да куца</string> <string name="pref_chat_states">Обавештења о куцању</string> <string name="pref_chat_states_summary">Обзнаните контакту кад куцате нову поруку</string> @@ -426,9 +456,8 @@ <string name="location">Локација</string> <string name="received_location">Примљена локација</string> <string name="title_undo_swipe_out_conversation">Преписка затворена</string> - <string name="title_undo_swipe_out_muc">Напусти конференцију</string> - <string name="pref_certificate_options">Опције сертификата</string> - <string name="pref_dont_trust_system_cas_title">Не веруј системским сертификационим телима</string> + <string name="title_undo_swipe_out_muc">Напусти групно ћаскање</string> + <string name="pref_dont_trust_system_cas_title">Не поуздај се у системска сертификациона тела</string> <string name="pref_dont_trust_system_cas_summary">Сви сертификати морају ручно да се одобре</string> <string name="pref_remove_trusted_certificates_title">Уклони сертификате</string> <string name="pref_remove_trusted_certificates_summary">Обриши ручно одобрене сертификате</string> @@ -451,6 +480,74 @@ <string name="none">Ниједна</string> <string name="recently_used">Недавно коришћена</string> <string name="choose_quick_action">Изаберите брзу радњу</string> - <string name="file_not_found_on_remote_host">Фајл није нађен на удаљеном серверу</string> <string name="search_for_contacts_or_groups">Тражите контакте или групе</string> + <string name="send_private_message">Пошаљи личну поруку</string> + <string name="user_has_left_conference">%s напусти групно ћаскање!</string> + <string name="username">Корисничко име</string> + <string name="username_hint">Корисничко име</string> + <string name="invalid_username">Ово није исправно корисничко име</string> + <string name="download_failed_server_not_found">Преузимање није успело: сервер није нађен</string> + <string name="download_failed_file_not_found">Преузимање није успело: фајл није нађен</string> + <string name="download_failed_could_not_connect">Преузимање није успело: не могу да се повежем са домаћином</string> + <string name="pref_use_white_background">Користи белу позадину</string> + <string name="pref_use_white_background_summary">Приказ примљених порука црним текстом на белој позадини</string> + <string name="account_status_tor_unavailable">Тор мрежа недоступна</string> + <string name="server_info_broken">Оштећен</string> + <string name="pref_presence_settings">Поставке присутности</string> + <string name="pref_away_when_screen_off">Одсутан кад је екран искључен</string> + <string name="pref_away_when_screen_off_summary">Означава ваш ресурс одсутним кад је екран искључен</string> + <string name="pref_xa_on_silent_mode">Недоступан у тихом режиму</string> + <string name="pref_xa_on_silent_mode_summary">Означава ваш ресурс недоступним кад је уређај у тихом режиму</string> + <string name="action_add_account_with_certificate">Додај налог сертификатом</string> + <string name="unable_to_parse_certificate">Не могу да рашчланим сертификат</string> + <string name="authenticate_with_certificate">Оставите празно за аутентификацију сертификатом</string> + <string name="captcha_ocr">Текст стопке</string> + <string name="captcha_required">Потребна стопка</string> + <string name="captcha_hint">унесите текст са слике</string> + <string name="certificate_chain_is_not_trusted">Ланац сертификата није поуздан</string> + <string name="jid_does_not_match_certificate">Џабер ИД не одговара сертификату</string> + <string name="action_renew_certificate">Обнови сертификат</string> + <string name="error_fetching_omemo_key">Грешка добављања ОМЕМО кључа!</string> + <string name="verified_omemo_key_with_certificate">Оверен ОМЕМО кључ помоћу сертификата!</string> + <string name="device_does_not_support_certificates">Ваш уређај не подржава избор сертификата клијента!</string> + <string name="pref_connection_options">Опције повезивања</string> + <string name="pref_use_tor">Повежи се преко Тора</string> + <string name="pref_use_tor_summary">Тунеловање свих веза кроз Тор мрежу. Захтева Орбот</string> + <string name="account_settings_hostname">Име домаћина</string> + <string name="account_settings_port">Порт</string> + <string name="hostname_or_onion">Сервер или .onion адреса</string> + <string name="not_a_valid_port">Ово није исправан број порта</string> + <string name="not_valid_hostname">Ово није исправно име домаћина</string> + <string name="connected_accounts">%1$d од %2$d налога повезано</string> + <plurals name="x_messages"> + <item quantity="one">%d порука</item> + <item quantity="few">%d поруке</item> + <item quantity="other">%d порука</item> + </plurals> + <string name="shared_file_with_x">Подељен фајл са %s</string> + <string name="shared_image_with_x">Подељена слика са %s</string> + <string name="no_storage_permission">Конверзацији је потребан приступ спољашњем складишту</string> + <string name="sync_with_contacts">Синхронизуј са контактима</string> + <string name="sync_with_contacts_long">Конверзација жели да поклапи ваш ИксМПП именик са контактима на вашем уређају да би приказала њихова пуна имена и аватаре.\n\nКонверзација ће само да очита ваше контакте и упореди их локално без отпремања на сервер.\n\nСада ћете бити упитани за дозволу приступа вашим контактима.</string> + <string name="certificate_information">Подаци о сертификату</string> + <string name="certificate_subject">Предмет</string> + <string name="certificate_issuer">Издавач</string> + <string name="certificate_cn">Заједничко име</string> + <string name="certificate_o">Организација</string> + <string name="certificate_sha1">СХА1</string> + <string name="certicate_info_not_available">(није доступно)</string> + <string name="certificate_not_found">Сертификат није нађен</string> + <string name="notify_on_all_messages">Обавештења за све поруке</string> + <string name="notify_only_when_highlighted">Обавештења само за означене поруке</string> + <string name="notify_never">Обавештења искључена</string> + <string name="notify_paused">Обавештења паузирана</string> + <string name="pref_picture_compression">Компресуј слике</string> + <string name="pref_picture_compression_summary">Промена величине и компресије слике</string> + <string name="always">увек</string> + <string name="automatically">аутоматски</string> + <string name="battery_optimizations_enabled">Оптимизација батерије је укључена</string> + <string name="battery_optimizations_enabled_explained">Ваш уређај користи оптимизацију потрошње батерије за Конверзацију што може да доведе до застоја обавештења или чак губитка порука.\nПрепоручљиво је да то искључите.</string> + <string name="battery_optimizations_enabled_dialog">Ваш уређај користи оптимизацију потрошње батерије за Конверзацију што може да доведе до застоја обавештења или чак губитка порука.\n\nСада ћете бити упитани да то искључите.</string> + <string name="disable">Искључи</string> + <string name="selection_too_large">Назначена површина је превелика</string> </resources> diff --git a/src/main/res/values-sv/strings.xml b/src/main/res/values-sv/strings.xml index d635831c..25cb41b1 100644 --- a/src/main/res/values-sv/strings.xml +++ b/src/main/res/values-sv/strings.xml @@ -9,7 +9,7 @@ <string name="action_secure">Säker konversation</string> <string name="action_add_account">Lägg till konto</string> <string name="action_edit_contact">Ändra namn</string> - <string name="action_add_phone_book">Lägg till i telefonbok</string> + <string name="action_add_phone_book">Lägg till i kontakter</string> <string name="action_delete_contact">Ta bort kontakt</string> <string name="action_block_contact">Blockera kontakt</string> <string name="action_unblock_contact">Avblockera kontakt</string> @@ -28,7 +28,8 @@ <string name="minutes_ago">%d min sedan</string> <string name="unread_conversations">olästa konversationer</string> <string name="sending">skickar…</string> - <string name="encrypted_message">Avkrypterar meddelande. Vänta…</string> + <string name="message_decrypting">Avkrypterar meddelande. Vänta…</string> + <string name="pgp_message">OpenPGP-krypterat meddelande</string> <string name="nick_in_use">Nick används redan</string> <string name="admin">Admin</string> <string name="owner">Ägare</string> @@ -58,7 +59,7 @@ <string name="save">Spara</string> <string name="ok">Ok</string> <string name="crash_report_title">Conversations har kraschat</string> - <string name="crash_report_message">Genom att skicka in stack traces hjälper du utvecklarna av Conversations\n<b>Varning:</b> Detta använder ditt XMPP konto för att skicka informationen till utvecklarna.</string> + <string name="crash_report_message">Genom att skicka in stack traces hjälper du utvecklarna av Conversations\n<b>Varning:</b> Detta använder ditt XMPP-konto för att skicka informationen till utvecklarna.</string> <string name="send_now">Skicka nu</string> <string name="send_never">Fråga aldrig igen</string> <string name="problem_connecting_to_account">Kan inte ansluta till konto</string> @@ -74,10 +75,12 @@ <string name="clear_conversation_history">Rensa konversationshistorik</string> <string name="clear_histor_msg">Vill du ta bort alla meddelanden i denna konversation?\n\n<b>Varning:</b> Detta kommer inte påverka meddelanden lagrade på andra enheter eller servrar.</string> <string name="delete_messages">Ta bort meddelanden</string> - <string name="also_end_conversation">Avsluta sedan denna konversation</string> + <string name="also_end_conversation">Avsluta denna konversation efteråt</string> <string name="choose_presence">Välj tillgänglighet till kontakt</string> - <string name="send_plain_text_message">Skicka meddelande i klartext</string> + <string name="send_unencrypted_message">Skicka okrypterat meddelande</string> <string name="send_otr_message">Skicka OTR-krypterat meddelande</string> + <string name="send_omemo_message">Skicka OMEMO-krypterat meddelande</string> + <string name="send_omemo_x509_message">Skicka v\\OMEMO-krypterat meddelande</string> <string name="send_pgp_message">Skicka OpenPGP-krypterat meddelande</string> <string name="your_nick_has_been_changed">Ditt nick har ändrats</string> <string name="send_unencrypted">Skicka okrypterat</string> @@ -86,18 +89,19 @@ <string name="openkeychain_required_long">Conversations använder en tredjeparts-applikation som heter <b>OpenKeychain</b> för att kryptera och avkryptera meddelanden och hantera dina publika nycklar.\n\nOpenKeychain är licensierad under GPLv3 och tillgänglig på F-Droid och Google Play.\n\n<small>(Starta om Conversations efter installation.)</small></string> <string name="restart">Starta om</string> <string name="install">Installera</string> + <string name="openkeychain_not_installed">Installera OpenKeychain</string> <string name="offering">erbjuder…</string> <string name="waiting">väntar…</string> <string name="no_pgp_key">Ingen OpenPGP-nyckel funnen</string> - <string name="contact_has_no_pgp_key">Conversations kan inte avkryptera ditt meddelande eftersom din kontakt inte annonserar sin publika nyckel.\n\n<small>Be din kontakt att sätta upp OpenPGP.</small></string> + <string name="contact_has_no_pgp_key">Conversations kan inte kryptera ditt meddelande eftersom din kontakt inte annonserar sin publika nyckel.\n\n<small>Be din kontakt att sätta upp OpenPGP.</small></string> <string name="no_pgp_keys">Inga OpenPGP-nycklar funna</string> - <string name="contacts_have_no_pgp_keys">Conversations kan inte avkryptera ditt meddelande eftersom din kontakt inte annonserar sin publika nyckel.\n\n<small>Be din kontakt att sätta upp OpenPGP.</small></string> - <string name="encrypted_message_received"><i>Krypterat meddelande mottaget. Tryck för att se och avkryptera.</i></string> + <string name="contacts_have_no_pgp_keys">Conversations kan inte kryptera ditt meddelande eftersom din kontakt inte annonserar sin publika nyckel.\n\n<small>Be din kontakt att sätta upp OpenPGP.</small></string> + <string name="encrypted_message_received"><i>Krypterat meddelande mottaget. Tryck för att avkryptera.</i></string> <string name="pref_general">Generellt</string> - <string name="pref_xmpp_resource">XMPP resurs</string> - <string name="pref_xmpp_resource_summary">Namnet som klienten identifierar sig med</string> + <string name="pref_xmpp_resource">XMPP-resurs</string> + <string name="pref_xmpp_resource_summary">Namnet klienten identifierar sig med</string> <string name="pref_accept_files">Acceptera filer</string> - <string name="pref_accept_files_size_summary">Acceptera automatiskt filer som är mindre än…</string> + <string name="pref_accept_files_summary">Acceptera automatiskt filer som är mindre än…</string> <string name="pref_notification_settings">Notifieringsinställningar</string> <string name="pref_notifications">Notifieringar</string> <string name="pref_notifications_summary">Notifiera när meddelande tagits emot</string> @@ -105,8 +109,6 @@ <string name="pref_vibrate_summary">Vibrera när meddelande tagits emot</string> <string name="pref_sound">Ljud</string> <string name="pref_sound_summary">Spela ljud med notifiering</string> - <string name="pref_conference_notifications">Konferensnotifieringar</string> - <string name="pref_conference_notifications_summary">Notifiera alltid när nytt konferensmeddelande tagits emot i stället för endast vid highlight</string> <string name="pref_notification_grace_period">Notifieringsfrist</string> <string name="pref_notification_grace_period_summary">Inaktivera notifieringar en kort stund efter att en carbon copy tagits emot</string> <string name="pref_advanced_options">Avancerade inställningar</string> @@ -114,7 +116,7 @@ <string name="pref_never_send_crash_summary">Genom att skicka in stack traces hjälper du utvecklarna av Conversations</string> <string name="pref_confirm_messages">Bekräfta meddelanden</string> <string name="pref_confirm_messages_summary">Låter dina kontakter veta när du har tagit emot och läst ett meddelande</string> - <string name="pref_ui_options">UI inställningar</string> + <string name="pref_ui_options">UI-inställningar</string> <string name="openpgp_error">OpenKeychain rapporterade ett fel</string> <string name="error_decrypting_file">I/O-fel vid avkryptering av fil</string> <string name="accept">Acceptera</string> @@ -149,9 +151,10 @@ <string name="account_status_regis_not_sup">Servern stödjer inte registrering</string> <string name="account_status_security_error">Säkerhetsfel</string> <string name="account_status_incompatible_server">Inkompatibel server</string> - <string name="encryption_choice_none">Klartext</string> + <string name="encryption_choice_unencrypted">Okrypterat</string> <string name="encryption_choice_otr">OTR</string> <string name="encryption_choice_pgp">OpenPGP</string> + <string name="encryption_choice_omemo">OMEMO</string> <string name="mgmt_account_edit">Ändra konto</string> <string name="mgmt_account_delete">Ta bort</string> <string name="mgmt_account_disable">Inaktivera tillfälligt</string> @@ -161,16 +164,16 @@ <string name="mgmt_account_are_you_sure">Är du säker?</string> <string name="mgmt_account_delete_confirm_text">Om du tar bort kontot kommer all konversationshistorik att försvinna</string> <string name="attach_record_voice">Spela in röst</string> - <string name="account_settings_jabber_id">Jabber ID</string> + <string name="account_settings_jabber_id">Jabber-ID</string> <string name="account_settings_password">Lösenord</string> <string name="account_settings_example_jabber_id">användarnamn@exempel.se</string> <string name="account_settings_confirm_password">Bekräfta lösenord</string> <string name="password">Lösenord</string> <string name="confirm_password">Bekräfta lösenord</string> <string name="passwords_do_not_match">Lösenorden är inte lika</string> - <string name="invalid_jid">Detta är inte ett korrekt Jabber ID</string> + <string name="invalid_jid">Detta är inte ett korrekt Jabber-ID</string> <string name="error_out_of_memory">Slut på minne. Bilden är för stor</string> - <string name="add_phone_book_text">Vill du lägga till %s i din telefons kontaktlista?</string> + <string name="add_phone_book_text">Vill du lägga till %s i din enhets kontakter?</string> <string name="contact_status_online">online</string> <string name="contact_status_free_to_chat">tillgänglig</string> <string name="contact_status_away">borta</string> @@ -179,14 +182,15 @@ <string name="contact_status_offline">offline</string> <string name="muc_details_conference">Konferens</string> <string name="muc_details_other_members">Andra medlemmar</string> - <string name="server_info_show_more">Server info</string> + <string name="server_info_show_more">Server-info</string> <string name="server_info_mam">XEP-0313: Message Archive</string> - <string name="server_info_carbon_messages">Carbon Messages</string> + <string name="server_info_carbon_messages">XEP-0280: Message Carbons</string> <string name="server_info_csi">XEP-0352: Client State Indication</string> <string name="server_info_blocking">XEP-0191: Blocking Command</string> <string name="server_info_roster_version">XEP-0237: Roster Versioning</string> - <string name="server_info_stream_management">Stream Management</string> - <string name="server_info_pep">XEP-0163: PEP (Avatarbilder)</string> + <string name="server_info_stream_management">XEP-0198: Stream Management</string> + <string name="server_info_pep">XEP-0163: PEP (Avatarbilder / OMEMO)</string> + <string name="server_info_http_upload">XEP-0363: Ladda upp via HTTP</string> <string name="server_info_available">tillgänglig</string> <string name="server_info_unavailable">otillgänglig</string> <string name="missing_public_keys">Annonsering om publik nyckel saknas</string> @@ -204,17 +208,28 @@ <string name="reception_failed">Mottagning misslyckades</string> <string name="your_fingerprint">Ditt fingeravtryck</string> <string name="otr_fingerprint">OTR-fingeravtryck</string> + <string name="omemo_fingerprint">OMEMO-fingeravtryck</string> + <string name="omemo_fingerprint_x509">v\\OMEMO-fingeravtryck</string> + <string name="omemo_fingerprint_selected_message">Meddelandets OMEMO-fingeravtryck</string> + <string name="omemo_fingerprint_x509_selected_message">Meddelandets v\\OMEMO-fingeravtryck</string> + <string name="this_device_omemo_fingerprint">Eget OMEMO-fingeravtryck</string> + <string name="other_devices">Andra enheter</string> + <string name="trust_omemo_fingerprints">Lita på OMEMO-fingeravtryck</string> + <string name="fetching_keys">Hämtar nycklar...</string> + <string name="done">Klar</string> <string name="verify">Verifiera</string> <string name="decrypt">Avkryptera</string> <string name="conferences">Konferenser</string> <string name="search">Sök</string> <string name="create_contact">Skapa kontakt</string> + <string name="enter_contact">Fyll i kontakt</string> <string name="join_conference">Gå med i konferens</string> <string name="delete_contact">Ta bort kontakt</string> <string name="view_contact_details">Se kontaktdetaljer</string> <string name="block_contact">Blockera kontakt</string> <string name="unblock_contact">Avblockera kontakt</string> <string name="create">Skapa</string> + <string name="select">Välj</string> <string name="contact_already_exists">Kontakten finns redan</string> <string name="join">Gå med</string> <string name="conference_address">Konferensadress</string> @@ -249,7 +264,6 @@ <string name="skip">Hoppa över</string> <string name="disable_notifications">Inaktivera notifieringar</string> <string name="disable_notifications_for_this_conversation">Inaktivera notifieringar för denna konversation</string> - <string name="notifications_disabled">Notifieringar är inaktiverade</string> <string name="enable">Aktivera</string> <string name="conference_requires_password">Konferensen kräver lösenord</string> <string name="enter_password">Fyll i lösenord</string> @@ -284,6 +298,7 @@ <string name="pref_conference_name">Konferensnamn</string> <string name="pref_conference_name_summary">Använd konferensens ämne istället för JID för att identifiera konferenser</string> <string name="toast_message_otr_fingerprint">OTR-fingeravtryck har kopierats till urklipp!</string> + <string name="toast_message_omemo_fingerprint">OMEMO-fingeravtryck har kopierats till urklipp!</string> <string name="conference_banned">Du är bannlyst från denna konferens</string> <string name="conference_members_only">Medlemsskap krävs för denna konferens</string> <string name="conference_kicked">Du har blivit utsparkad från denna konferens</string> @@ -295,7 +310,7 @@ <string name="copy_text">Kopiera text</string> <string name="copy_original_url">Kopiera orginal-URL</string> <string name="send_again">Skicka igen</string> - <string name="file_url">Fil URL</string> + <string name="file_url">Fil-URL</string> <string name="message_text">Meddelandetext</string> <string name="url_copied_to_clipboard">URL kopierad till urklipp</string> <string name="message_copied_to_clipboard">Meddelande kopierat till urklipp</string> @@ -307,7 +322,6 @@ <string name="verify_otr">Verifiera OTR</string> <string name="remote_fingerprint">Fjärr-fingeravtryck</string> <string name="scan">skanna</string> - <string name="or_touch_phones">(eller nudda telefoner)</string> <string name="smp">Socialist Millionaire Protocol</string> <string name="shared_secret_hint">Ledtråd eller fråga</string> <string name="shared_secret_secret">Delad hemlighet</string> @@ -319,11 +333,14 @@ <string name="try_again">Försök igen</string> <string name="finish">Klar</string> <string name="verified">Verifierad</string> - <string name="smp_requested">Kontakt begärde SMP verifikation</string> + <string name="smp_requested">Kontakt begärde SMP-verifikation</string> <string name="no_otr_session_found">Ingen giltig OTR-session kunde hittas!</string> <string name="conversations_foreground_service">Conversations</string> <string name="pref_keep_foreground_service">Håll tjänst i förgrunden</string> <string name="pref_keep_foreground_service_summary">Förehindrar operativsystemet att ta ner uppkopplingen</string> + <string name="pref_export_logs">Exportera loggar</string> + <string name="pref_export_logs_summary">Skriv loggar till SD-kort</string> + <string name="notification_export_logs_title">Skriver loggar till SD-kort</string> <string name="choose_file">Välj fil</string> <string name="receiving_x_file">Tar emot %1$s (%2$d%% klart)</string> <string name="download_x_file">Ladda ner %s</string> @@ -350,6 +367,17 @@ <string name="reset">Återställ</string> <string name="account_image_description">Kontots avatarbild</string> <string name="copy_otr_clipboard_description">Kopiera OTR-fingeravtryck till urklipp</string> + <string name="copy_omemo_clipboard_description">Kopiera OMEMO-fingeravtryck till urklipp</string> + <string name="regenerate_omemo_key">Regenerera OMEMO-nyckel</string> + <string name="wipe_omemo_pep">Rensa andra enheter från PEP</string> + <string name="clear_other_devices">Rensa enheter</string> + <string name="clear_other_devices_desc">Är du säker på att du vill rensa alla andra enheter från OMEMO-annonsering? Nästa gång dina enheter ansluter kommer de att återannonsera sig, men de kanske inte tar emot enheter under tiden.</string> + <string name="purge_key">Rensa nyckel</string> + <string name="purge_key_desc_part1">Är du säker på att du vill rensa denna nyckel?</string> + <string name="purge_key_desc_part2">Den kommer att antas oåterkalleligt komprometterat och du kommer aldrig kunna bygga en session med den igen.</string> + <string name="error_no_keys_to_trust_server_error">Det finns inga användbara nycklar tillgängliga för den här kontakten.\nHämtning av nya nyckar från servern har inte lyckats. Kanske är det något fel på din kontakts server.</string> + <string name="error_no_keys_to_trust">Det finns inga användbara nycklar tillgängliga för den här kontakten. Om du har rensat någon av dennes nycklar behöver de generera nya.</string> + <string name="error_trustkeys_title">Fel</string> <string name="fetching_history_from_server">Hämtar historik från server</string> <string name="no_more_history_on_server">Ingen mer historik på server</string> <string name="updating">Uppdaterar…</string> @@ -387,8 +415,10 @@ <string name="public_conference">Publikt tillgänglig konferens</string> <string name="private_conference">Privat konferens där medlemsskap krävs</string> <string name="conference_options">Konferensalternativ</string> - <string name="members_only">Privat (endast för medlemmar)</string> + <string name="members_only">Privat, medlemsskap krävs</string> <string name="non_anonymous">Icke-anonymt</string> + <string name="moderated">Modererad</string> + <string name="you_are_not_participating">Du deltar ej</string> <string name="modified_conference_options">Ändrade konferensalternativ</string> <string name="could_not_modify_conference_options">Kunde inte ändra konferensalternativ</string> <string name="never">Aldrig</string> @@ -406,7 +436,7 @@ <string name="video">video</string> <string name="image">bild</string> <string name="pdf_document">PDF-dokument</string> - <string name="apk">Android App</string> + <string name="apk">Android-app</string> <string name="vcard">Kontakt</string> <string name="received_x_file">Tagit emot %s</string> <string name="disable_foreground_service">Deaktivera förgrundstjänst</string> @@ -427,7 +457,6 @@ <string name="received_location">Mottog position</string> <string name="title_undo_swipe_out_conversation">Konversation stängd</string> <string name="title_undo_swipe_out_muc">Lämnade konferens</string> - <string name="pref_certificate_options">Certifikatalternativ</string> <string name="pref_dont_trust_system_cas_title">Lita inte på systemets CAs</string> <string name="pref_dont_trust_system_cas_summary">Alla certifikat måste manuellt godkännas</string> <string name="pref_remove_trusted_certificates_title">Ta bort certifikat</string> @@ -449,6 +478,73 @@ <string name="none">Ingen</string> <string name="recently_used">Senast använd</string> <string name="choose_quick_action">Välj snabbfunktion</string> - <string name="file_not_found_on_remote_host">Filen hittas ej på servern</string> <string name="search_for_contacts_or_groups">Sök efter kontakter eller grupper</string> + <string name="send_private_message">Skicka privat meddelande</string> + <string name="user_has_left_conference">%s har lämnat konferensen!</string> + <string name="username">Användarnamn</string> + <string name="username_hint">Användarnamn</string> + <string name="invalid_username">Inte ett giltigt användanamn</string> + <string name="download_failed_server_not_found">Nerladdning gick fel: Server hittades inte</string> + <string name="download_failed_file_not_found">Nerladdning gick fel: Filen hittades inte</string> + <string name="download_failed_could_not_connect">Nerladdningen gick fel: Kunder inte ansluta till server</string> + <string name="pref_use_white_background">Använd vit bakgrund</string> + <string name="pref_use_white_background_summary">Visa mottagna meddelanden som svart text på vit bakgrund</string> + <string name="account_status_tor_unavailable">Tor-nätverk ej tillgängligt</string> + <string name="server_info_broken">Sönder</string> + <string name="pref_presence_settings">Tillgänglighetsinställningar</string> + <string name="pref_away_when_screen_off">Status borta när skärmen är av</string> + <string name="pref_away_when_screen_off_summary">Sätter din tillgänglighet till borta när skrämen är av</string> + <string name="pref_xa_on_silent_mode">Status ej tillgänglig i tyst läge</string> + <string name="pref_xa_on_silent_mode_summary">Sätter din tillgänglighet till ej tillgänglig när enheten är i tyst läge</string> + <string name="action_add_account_with_certificate">Lägg till konto med certifikat</string> + <string name="unable_to_parse_certificate">Kan inte läsa certifikat</string> + <string name="authenticate_with_certificate">Lämna tom för att för att logga in med certifikat</string> + <string name="captcha_ocr">CAPTCHA-text</string> + <string name="captcha_required">CAPTCHA krävs</string> + <string name="captcha_hint">skriv in texten från bilden</string> + <string name="certificate_chain_is_not_trusted">Certifikatskedjan är inte betrodd</string> + <string name="jid_does_not_match_certificate">Jabber-ID matchar inte certifikat</string> + <string name="action_renew_certificate">Förnya certifikat</string> + <string name="error_fetching_omemo_key">Misslyckades med att hämta OMEMO-nyckel!</string> + <string name="verified_omemo_key_with_certificate">Verifierade OMEMO-nyckel med certifikat!</string> + <string name="device_does_not_support_certificates">Din enhet stödjer inte val av klientcertifikat!</string> + <string name="pref_connection_options">Anslutningsalternativ</string> + <string name="pref_use_tor">Ansluten via Tor</string> + <string name="pref_use_tor_summary">Tunnla alla anslutningar genom Tor-nätverket. Kräver Orbot</string> + <string name="account_settings_hostname">Servernamn</string> + <string name="account_settings_port">Port</string> + <string name="hostname_or_onion">Server- eller .onion-adress</string> + <string name="not_a_valid_port">Inte ett giltigt portnummer</string> + <string name="not_valid_hostname">Inte ett giltigt servernamn</string> + <string name="connected_accounts">%1$d av %2$d konton anslutna</string> + <plurals name="x_messages"> + <item quantity="one">%d meddelande</item> + <item quantity="other">%d meddelanden</item> + </plurals> + <string name="shared_file_with_x">Delade fil med %s</string> + <string name="shared_image_with_x">Delade bild med %s</string> + <string name="no_storage_permission">Conversations behöver access till extern lagring</string> + <string name="sync_with_contacts">Synkronisera med kontakter</string> + <string name="sync_with_contacts_long">Conversations vill matcha din XMPP-kontaktlista med dina kontakter för att visa deras namn och profilbild.\n\nConversations läser endast dina kontakter för att matcha dem lokalt utan att ladda upp dem till din server.\n\nDu kommer nu få frågan om tillåtelse för att använda kontakterna.</string> + <string name="certificate_information">Certifikatinformation</string> + <string name="certificate_subject">Ämne</string> + <string name="certificate_issuer">Utfärdare</string> + <string name="certificate_cn">Common Name</string> + <string name="certificate_o">Organisation</string> + <string name="certificate_sha1">SHA1</string> + <string name="certicate_info_not_available">(Ej tillgänglig)</string> + <string name="certificate_not_found">Inget certifikat funnet</string> + <string name="notify_on_all_messages">Notifiera för alla meddelanden</string> + <string name="notify_only_when_highlighted">Notifiera endast vid highlight</string> + <string name="notify_never">Notifieringar deaktiverade</string> + <string name="notify_paused">Notifieringar pausade</string> + <string name="pref_picture_compression">Komprimera bilder</string> + <string name="pref_picture_compression_summary">Storleksjustera och komprimera bilder</string> + <string name="always">Alltid</string> + <string name="automatically">Automatiskt</string> + <string name="battery_optimizations_enabled">Batterioptimeringar aktiverade</string> + <string name="battery_optimizations_enabled_explained">Din enhet har kraftiga batterioptimeringar som påverkar Conversations på så sätt att inkommande meddelanden kan försenas eller kan till och med gå förlorade.\nDet är rekommenderat att deaktivera batterioptimeringarna.</string> + <string name="battery_optimizations_enabled_dialog">Din enhet har kraftiga batterioptimeringar som påverkar Conversations på så sätt att inkommande meddelanden kan försenas eller kan till och med gå förlorade.\nDu kommer nu att bli ombedd att stänga av batterioptimeringarna för Conversations.</string> + <string name="disable">Deaktivera</string> + <string name="selection_too_large">The valda området är för stort</string> </resources> diff --git a/src/main/res/values-tr-rTR/strings.xml b/src/main/res/values-tr-rTR/strings.xml new file mode 100644 index 00000000..9f3528ab --- /dev/null +++ b/src/main/res/values-tr-rTR/strings.xml @@ -0,0 +1,547 @@ +<?xml version='1.0' encoding='UTF-8'?> +<resources> + <string name="action_settings">Ayarlar</string> + <string name="action_add">Yeni sohbet</string> + <string name="action_accounts">Hesapları yönet</string> + <string name="action_end_conversation">Sohbeti sonlandır</string> + <string name="action_contact_details">Kişi bilgileri</string> + <string name="action_muc_details">Grup sohbet bilgileri</string> + <string name="action_secure">Güvenli sohbet</string> + <string name="action_add_account">Hesap ekle</string> + <string name="action_edit_contact">İsmi düzenle</string> + <string name="action_add_phone_book">Telefon rehberine ekle</string> + <string name="action_delete_contact">Kişi listesinden sil</string> + <string name="action_block_contact">Kişiyi engelle</string> + <string name="action_unblock_contact">Kişiyi engellemekten vazgeç</string> + <string name="action_block_domain">Alan adını engelle</string> + <string name="action_unblock_domain">Alan adını engellemekten vazgeç</string> + <string name="title_activity_manage_accounts">Hesapları yönet</string> + <string name="title_activity_settings">Ayarlar</string> + <string name="title_activity_conference_details">Grup sohbet bilgileri</string> + <string name="title_activity_contact_details">Kişi bilgileri</string> + <string name="title_activity_sharewith">Sohbetle paylaş</string> + <string name="title_activity_start_conversation">Sohbeti başlat</string> + <string name="title_activity_choose_contact">Kişi seç</string> + <string name="title_activity_block_list">Listeyi blokla</string> + <string name="just_now">şimdi</string> + <string name="minute_ago">1 dakika önce</string> + <string name="minutes_ago">%d dakika önc</string> + <string name="unread_conversations">okunmamış sohbetler</string> + <string name="sending">gönderiyor…</string> + <string name="message_decrypting">İleti deşifre ediliyor. Lütfen bekleyin…</string> + <string name="pgp_message">OpenPGP şifreli mesaj</string> + <string name="nick_in_use">Rumuz kullanılıyor</string> + <string name="admin">Yönetici</string> + <string name="owner">Sahip</string> + <string name="moderator">Moderatör</string> + <string name="participant">Katılımcı</string> + <string name="visitor">Ziyaretçi</string> + <string name="remove_contact_text">%s kişisini listenizden silmek istiyor musunuz? Bu kişiyle yaptığınız sohbetler silinmeyecektir.</string> + <string name="block_contact_text">%s kişisinin size ileti göndermesini engellemek istiyor musunuz?</string> + <string name="unblock_contact_text">% kişisinin size ileti göndermesine koyduğunuz engellemeyi kaldırmak ve size ileti göndermesine izin vermek istiyor musunuz?</string> + <string name="block_domain_text">%s üzerinden gelen bütün kişileri engellemek istiyor musunuz? </string> + <string name="unblock_domain_text">%s üzerinden gelen kişilerdeki engellemeyi kaldırmak istiyor musunuz?</string> + <string name="contact_blocked">Kişi engellendi</string> + <string name="remove_bookmark_text">%s yer imini silmek istiyor musunuz? Bu yer imiyle ilintili sohbet silinmeyecektir.</string> + <string name="register_account">Sunucuda yeni bir hesap oluştur</string> + <string name="change_password_on_server">Sunucudaki şifreni değiştir</string> + <string name="share_with">Paylaş...</string> + <string name="start_conversation">Sohbet başlat</string> + <string name="invite_contact">Kişi davet et</string> + <string name="contacts">Kişiler</string> + <string name="cancel">İptal et</string> + <string name="set">Ayarla</string> + <string name="add">Ekle</string> + <string name="edit">Düzenle</string> + <string name="delete">Sil</string> + <string name="block">Engelle</string> + <string name="unblock">Engellemeyi kaldır</string> + <string name="save">Kaydet</string> + <string name="ok">Tamam</string> + <string name="crash_report_title">Conversations çöktü</string> + <string name="crash_report_message">Çöküş raporu göndermeniz Conversations\n’ın geliştirilmesine katkıda bulunacaktır.<b>Uyarı:</b>Bu rapor yazılım geliştiriciye XMPP hesabınız üzerinden gönderilecektir.</string> + <string name="send_now">Şimdi gönder</string> + <string name="send_never">Bir daha sorma</string> + <string name="problem_connecting_to_account">Hesaba bağlanılamıyor</string> + <string name="problem_connecting_to_accounts">Birden fazla hesaba bağlanılamıyor</string> + <string name="touch_to_fix">Hesaplarınızı yönetmek için dokunun</string> + <string name="attach_file">Dosya ekle</string> + <string name="not_in_roster">Kişi, listenizde değil. Eklemek ister misiniz?</string> + <string name="add_contact">Kişi ekle</string> + <string name="send_failed">ulaştırılamadı</string> + <string name="send_rejected">reddedildi</string> + <string name="preparing_image">Görüntü, iletilmek üzere hazırlanıyor</string> + <string name="action_clear_history">Geçmişi sil</string> + <string name="clear_conversation_history">Sohbet geçmişini sil</string> + <string name="clear_histor_msg">Bu sohbetteki bütün iletileri silmek istiyor musunuz?\n\n<b>Uyarı:</b>Başka cihazlardaki ya da sunuculardaki iletiler bundan etkilenmeyecektir.</string> + <string name="delete_messages">İletileri sil</string> + <string name="also_end_conversation">Sonrasında bu sohbeti sonlandır</string> + <string name="choose_presence">Kaynak kişiyi seçin</string> + <string name="send_unencrypted_message">Şifrelenmemiş ileti gönder</string> + <string name="send_otr_message">OTR ile şifrelenmiş ileti gönder</string> + <string name="send_omemo_message">OMEMO ile şifrelenmiş ileti gönder</string> + <string name="send_omemo_x509_message">v\\OMEMO ile şifrelenmiş ileti gönder</string> + <string name="send_pgp_message">OpenPGP ile şifrelenmiş ileti gönder</string> + <string name="your_nick_has_been_changed">Rumuzunuz değişti</string> + <string name="send_unencrypted">Şifrelenmemiş gönder</string> + <string name="decryption_failed">Deşifre edilemedi. Uygun bir özel anahtarınız olmayabilir.</string> + <string name="openkeychain_required">OpenKeychain</string> + <string name="openkeychain_required_long">Conversations iletileri şifreleyip deşifre etmek ve ortak anahtarlarınızı yönetmek için bir üçüncü parti uygulaması olan <b>OpenKeychain’i</b> kullanmaktadır. \n\nOpenKeychain GPLv3 altında lisanslı olup F-Droid ve Google Play’den indirilebilir.\n\n<small>(Lütfen daha sonra Conversations’ı yeniden başlatın.)</small></string> + <string name="restart">Yeniden başlat</string> + <string name="install">Kur</string> + <string name="openkeychain_not_installed">Lütfen OpenKeychain’i kur</string> + <string name="offering">sunuluyor…</string> + <string name="waiting">bekliyor…</string> + <string name="no_pgp_key">Herhangi bir OpenPGP anahtarı bulunamadı</string> + <string name="contact_has_no_pgp_key">Kişi ortak anahtarını yayınlamadığı için Conversations iletilerinizi şifreleyemiyor.\n\n<small>Lütfen kişiden OpenPGP’yi ayarlamasını isteyin.</small></string> + <string name="no_pgp_keys">Herhangi bir OpenPGP anahtarı bulunamadı</string> + <string name="contacts_have_no_pgp_keys">Kişiler ortak anahtarlarını yayınlamadığı için Conversations iletilerinizi şifreleyemiyor.\n\n<small>Lütfen kişilerden OpenPGP’yi ayarlamalarını isteyin.</small></string> + <string name="encrypted_message_received"><i>Şifreli ileti alındı. Deşifre etmek için dokunun.</i></string> + <string name="pref_general">Genel</string> + <string name="pref_xmpp_resource">XMPP kaynağı</string> + <string name="pref_xmpp_resource_summary">İstemci kimliği</string> + <string name="pref_accept_files">Dosyaları kabul et</string> + <string name="pref_accept_files_summary">…‘den küçük olan dosyaları otomatik olarak kabul et</string> + <string name="pref_notification_settings">Bildirim Ayarları</string> + <string name="pref_notifications">Bildirimler</string> + <string name="pref_notifications_summary">Yeni ileti geldiğinde bildir</string> + <string name="pref_vibrate">Titreşim</string> + <string name="pref_vibrate_summary">Yeni bir ileti geldiğinde aynı zamanda titreşsin</string> + <string name="pref_sound">Ses</string> + <string name="pref_sound_summary">Bildirimle birlikte zil çalsın</string> + <string name="pref_notification_grace_period">Bildirim mühleti</string> + <string name="pref_notification_grace_period_summary">Karbon kopya alındıktan sonra kısa bir süre için bildirimleri kapa</string> + <string name="pref_advanced_options">İleri Seçenekler</string> + <string name="pref_never_send_crash">Asla çöküş raporu gönderme</string> + <string name="pref_never_send_crash_summary">Çöküş raporu göndermeniz Conversations\n’ın geliştirilmesine katkıda bulunacaktır.</string> + <string name="pref_confirm_messages">İletileri onayla</string> + <string name="pref_confirm_messages_summary">Karşı tarafa ileti alındı ve okundu raporu gönder.</string> + <string name="pref_ui_options">Arabirim Seçenekleri</string> + <string name="openpgp_error">OpenKeychain bir hata bildirdi</string> + <string name="error_decrypting_file">Dosyanın deşifresinde G/Ç hatası</string> + <string name="accept">Kabul et</string> + <string name="error">Bir hata oluştu</string> + <string name="pref_grant_presence_updates">Durum güncelleme bildirimlerine izin ver</string> + <string name="pref_grant_presence_updates_summary">Eklediğiniz kişilerin durum bildirimlerine abone olmayı isteyin ve peşinen kabul edin.</string> + <string name="subscriptions">Abonelikler</string> + <string name="your_account">Hesabınız</string> + <string name="keys">Anahtarlar</string> + <string name="send_presence_updates">Çevrimiçi durum bildirimi gönder</string> + <string name="receive_presence_updates">Çevrimiçi durum bildirimi al</string> + <string name="ask_for_presence_updates">Çevrimiçi durum bildirimi iste</string> + <string name="attach_choose_picture">Resim seç</string> + <string name="attach_take_picture">Resim çek</string> + <string name="preemptively_grant">Abonelik isteğini peşinen kabul et</string> + <string name="error_not_an_image_file">Seçtiğiniz dosya bir görüntü dosyası değil</string> + <string name="error_compressing_image">Görüntü dosyasını dönüştürürken hata oluştu</string> + <string name="error_file_not_found">Dosya bulunamadı</string> + <string name="error_io_exception">Genel G/Ç hatası. Depolama yeri kalmamış olabilir mi?</string> + <string name="error_security_exception_during_image_copy">Bu resmi seçmek için kullandığınız uygulama, dosyayı okuyabilmemiz için izin vermiyor. \n\n<small>Resim seçmek için farklı bir dosya yöneticisi kullanın.</small></string> + <string name="account_status_unknown">Bilinmeyen</string> + <string name="account_status_disabled">Geçici olarak devre dışı</string> + <string name="account_status_online">Çevrimiçi</string> + <string name="account_status_connecting">Bağlanıyor\u2026</string> + <string name="account_status_offline">Çevrimdışı</string> + <string name="account_status_unauthorized">Yetkisiz</string> + <string name="account_status_not_found">Sunucu bulunamadı</string> + <string name="account_status_no_internet">Bağlantı yok</string> + <string name="account_status_regis_fail">Hesap oluşturulamadı</string> + <string name="account_status_regis_conflict">Kullanıcı adı kullanılıyor</string> + <string name="account_status_regis_success">Hesap oluşturuldu</string> + <string name="account_status_regis_not_sup">Sunucu hesap oluşturma işlemini desteklemiyor</string> + <string name="account_status_security_error">Güvenlik hatası</string> + <string name="account_status_incompatible_server">Sunucu uyuşmazlığı</string> + <string name="encryption_choice_unencrypted">Şifrelenmemiş</string> + <string name="encryption_choice_otr">OTR</string> + <string name="encryption_choice_pgp">OpenPGP</string> + <string name="encryption_choice_omemo">OMEMO</string> + <string name="mgmt_account_edit">Hesabı düzenle</string> + <string name="mgmt_account_delete">Hesabı sil</string> + <string name="mgmt_account_disable">Geçici olarak devre dışı bırak</string> + <string name="mgmt_account_publish_avatar">Avatar yayınla</string> + <string name="mgmt_account_publish_pgp">OpenPGP genel anahtarını yayınla</string> + <string name="mgmt_account_enable">Hesabı etkinleştir</string> + <string name="mgmt_account_are_you_sure">Emin misiniz?</string> + <string name="mgmt_account_delete_confirm_text">Hesabınızı silerseniz bütün sohbet geçmişiniz silinecek</string> + <string name="attach_record_voice">Ses kaydet</string> + <string name="account_settings_jabber_id">Jabber ID</string> + <string name="account_settings_password">Parola</string> + <string name="account_settings_example_jabber_id">kullanıcıadı@ornek.com</string> + <string name="account_settings_confirm_password">Parolayı doğrula</string> + <string name="password">parola</string> + <string name="confirm_password">Parolayı doğrula</string> + <string name="passwords_do_not_match">Parolalar eşleşmiyor</string> + <string name="invalid_jid">Jabber ID geçersiz</string> + <string name="error_out_of_memory">Yetersiz bellek. Görüntü dosyası çok büyük.</string> + <string name="add_phone_book_text">%s kişisini listenize eklemek ister misiniz?</string> + <string name="contact_status_online">çevrimiçi</string> + <string name="contact_status_free_to_chat">sohbet için uygun</string> + <string name="contact_status_away">uzakta</string> + <string name="contact_status_extended_away">uzun süredir uzakta</string> + <string name="contact_status_do_not_disturb">rahatsız etmeyin</string> + <string name="contact_status_offline">çevrimdışı</string> + <string name="muc_details_conference">Grup Sohbet</string> + <string name="muc_details_other_members">Diğer Üyeler</string> + <string name="server_info_show_more">Sunucu bilgisi</string> + <string name="server_info_mam">XEP-0313: MAM</string> + <string name="server_info_carbon_messages">XEP-0280: Message Carbons</string> + <string name="server_info_csi">XEP-0352: Client State Indication</string> + <string name="server_info_blocking">XEP-0191: Blocking Command</string> + <string name="server_info_roster_version">XEP-0237: Roster Versioning</string> + <string name="server_info_stream_management">XEP-0198: Stream Management</string> + <string name="server_info_pep">XEP-0163: PEP (Avatars / OMEMO)</string> + <string name="server_info_http_upload">XEP-0363: HTTP File Upload</string> + <string name="server_info_available">mevcut</string> + <string name="server_info_unavailable">mevcut değil</string> + <string name="missing_public_keys">Kayıp genel anahtar bildirimleri</string> + <string name="last_seen_now">en son şimdi görüldü</string> + <string name="last_seen_min">en son 1 dakika önce görüldü</string> + <string name="last_seen_mins">en son %d dakika önce görüldü</string> + <string name="last_seen_hour">en son 1 saat önce görüldü</string> + <string name="last_seen_hours">en son %d saat önce görüldü</string> + <string name="last_seen_day">en son 1 gün önce görüldü</string> + <string name="last_seen_days">en son %d gün önce görüldü</string> + <string name="never_seen">hiç görülmedi</string> + <string name="install_openkeychain">Şifreli mesaj. Deşifre etmek için lütfen OpenKeychain kurun.</string> + <string name="unknown_otr_fingerprint">Bilinmeyen OTR parmak izi</string> + <string name="openpgp_messages_found">OpenPGP ile şifrelenmiş iletiler bulundu</string> + <string name="reception_failed">Alınamadı</string> + <string name="your_fingerprint">Parmak iziniz</string> + <string name="otr_fingerprint">OTR parmak izi</string> + <string name="omemo_fingerprint">OMEMO parmak izi</string> + <string name="omemo_fingerprint_x509">v\\OMEMO parmak izi</string> + <string name="omemo_fingerprint_selected_message">İletinin OMEMO parmak izi</string> + <string name="omemo_fingerprint_x509_selected_message">v\\İletinin OMEMO parmak izi</string> + <string name="this_device_omemo_fingerprint">OMEMO parmak iziniz</string> + <string name="other_devices">Diğer cihazlar</string> + <string name="trust_omemo_fingerprints">OMEMO parmak izlerine güven</string> + <string name="fetching_keys">Anahtarları alıyor…</string> + <string name="done">Tamam</string> + <string name="verify">Doğrula</string> + <string name="decrypt">Deşifre et</string> + <string name="conferences">Grup Sohbetleri</string> + <string name="search">Ara</string> + <string name="create_contact">Kişi Oluştur</string> + <string name="enter_contact">Kişi Girin</string> + <string name="join_conference">Grup Sohbete Katıl</string> + <string name="delete_contact">Kişi Sil</string> + <string name="view_contact_details">Kişi bilgilerini görüntüle</string> + <string name="block_contact">Kişiyi engelle</string> + <string name="unblock_contact">Kişiyi engellemeyi kaldır</string> + <string name="create">Oluştur</string> + <string name="select">Seç</string> + <string name="contact_already_exists">Kişi zaten mevcut</string> + <string name="join">Katıl</string> + <string name="conference_address">Grup sohbet adresi</string> + <string name="conference_address_example">oda@conference.ornek.com</string> + <string name="save_as_bookmark">Yer imi olarak kaydet</string> + <string name="delete_bookmark">Yer imini sil</string> + <string name="bookmark_already_exists">Bu yer imi zaten mevcut</string> + <string name="you">Siz</string> + <string name="action_edit_subject">Grup sohbet konusunu düzenle</string> + <string name="conference_not_found">Grup sohbet bulunamadı</string> + <string name="leave">Ayrıl</string> + <string name="contact_added_you">Kişi sizi listesine ekledi</string> + <string name="add_back">Siz de ekleyin</string> + <string name="contact_has_read_up_to_this_point">%s buraya kadar okudu</string> + <string name="publish">Yayınla</string> + <string name="touch_to_choose_picture">Galeriden resim seçmek için avatara dokun</string> + <string name="publish_avatar_explanation">Lütfen dikkat: Çevrimiçi durum bildirimi güncellemelerinize abone olan herkes bu resmi görebilir.</string> + <string name="publishing">Yayınlanıyor…</string> + <string name="error_publish_avatar_server_reject">Sunucu yayınladığınız resmi reddetti</string> + <string name="error_publish_avatar_converting">Resim dönüştürülürken hata oluştu</string> + <string name="error_saving_avatar">vatar diske kaydedilemedi</string> + <string name="or_long_press_for_default">(Veya varsayılan değerlere dönmek için uzun süre basılı tutun)</string> + <string name="error_publish_avatar_no_server_support">Sunucunuz avatar yayınlanmasını desteklemiyor</string> + <string name="private_message">fısıldandı</string> + <string name="private_message_to">%s kişisine</string> + <string name="send_private_message_to">%s kişisine özel mesaj gönder</string> + <string name="connect">Bağlan</string> + <string name="account_already_exists">Bu hesap zaten mevcut</string> + <string name="next">Sonraki</string> + <string name="server_info_session_established">Oturum sağlandı</string> + <string name="additional_information">Ek bilgi</string> + <string name="skip">Atla</string> + <string name="disable_notifications">Bildirimleri kapat</string> + <string name="disable_notifications_for_this_conversation">Bu sohbet için bildirimleri kapat</string> + <string name="enable">Etkinleştir</string> + <string name="conference_requires_password">Grup sohbet için parola gerekiyor</string> + <string name="enter_password">Parolayı gir</string> + <string name="missing_presence_updates">Kişinin çevrimiçi durum bildirimi güncellemesi kayıp</string> + <string name="request_presence_updates">Lütfen öncelikle kişiden çevrimiçi durum güncellemelerini isteyin.\n\n<small>Bu bilgi kişinin kullandığı istemcinin belirlenmesinde kullanılacaktır.</small></string> + <string name="request_now">Şimdi iste</string> + <string name="delete_fingerprint">Parmak izini sil</string> + <string name="sure_delete_fingerprint">Bu parmak izini silmek istediğinizden emin misiniz?</string> + <string name="ignore">Yok say</string> + <string name="without_mutual_presence_updates"><b>Uyarı:</b> Karşılıklı çevrimiçi durum bildirimi güncellemeleri olmaksızın bunu göndermeniz beklenmedik sorunlara sebep olabilir.\n\n\n\n<small>Çevrimiçi durum bildirimi aboneliklerinizi kontrol etmek için kişi bilgilerine gidin.</small></string> + <string name="pref_encryption_settings">Şifreleme ayarları</string> + <string name="pref_force_encryption">Uçtan uca şifrelemeye zorla</string> + <string name="pref_force_encryption_summary">Her zaman şifrelenmiş ileti gönder (Conversations hariç)</string> + <string name="pref_dont_save_encrypted">Şifrelenmiş iletileri kaydetme</string> + <string name="pref_dont_save_encrypted_summary">Uyarı: Bu, iletilerin kaybedilmesine neden olabilir</string> + <string name="pref_expert_options">Uzman seçenekleri</string> + <string name="pref_expert_options_summary">Lütfen dikkatli olun</string> + <string name="title_activity_about">Conversations hakkında</string> + <string name="pref_about_conversations_summary">Geliştirme ve lisans bilgisi</string> + <string name="title_pref_quiet_hours">Sessiz Saatler</string> + <string name="title_pref_quiet_hours_start_time">Başlangıç zamanı</string> + <string name="title_pref_quiet_hours_end_time">Bitiş zamanı</string> + <string name="title_pref_enable_quiet_hours">Sessiz saatleri etkinleştir</string> + <string name="pref_quiet_hours_summary">Bildirimler sessiz saatler boyunca sessize alınacaktır</string> + <string name="pref_use_larger_font">Fontu büyüt</string> + <string name="pref_use_larger_font_summary">Uygulamanın tamamında daha büyük font kullan+</string> + <string name="pref_use_send_button_to_indicate_status">Gönder düğmesi durum bildirsin</string> + <string name="pref_use_indicate_received">İleti alındısı iste</string> + <string name="pref_use_indicate_received_summary">Alınan mesajlar, eğer destekleniyorsa, yeşil bir tikle işaretlenecektir.</string> + <string name="pref_use_send_button_to_indicate_status_summary">Gönder butonunu kişinin durumuna göre renklendir</string> + <string name="pref_expert_options_other">Diğer</string> + <string name="pref_conference_name">Grup sohbet ismi</string> + <string name="pref_conference_name_summary">Grup sohbetleri tanımlamak için JID yerine odanın konusunu kullan</string> + <string name="toast_message_otr_fingerprint">OTR parmak izi panoya kopyalandı!</string> + <string name="toast_message_omemo_fingerprint">OMEMO parmak izi panoya kopyalandı!</string> + <string name="conference_banned">Grup sohbetinden atıldınız</string> + <string name="conference_members_only">Bu grup sohbet sadece üyelere açıktır</string> + <string name="conference_kicked">Grup sohbetinden atıldınız</string> + <string name="using_account">%s hesabını kullanarak</string> + <string name="checking_x">HTTP sunucusundaki %s \'leri kontrol ediyor</string> + <string name="not_connected_try_again">Bağlı değilsiniz. Daha sonra yeniden deneyin</string> + <string name="check_x_filesize">%s boyutunu kontrol edin</string> + <string name="message_options">İleti seçenekleri</string> + <string name="copy_text">Metni kopyala</string> + <string name="copy_original_url">Orijinal URL\'i kopyala</string> + <string name="send_again">Yeniden gönder</string> + <string name="file_url">Dosya URL</string> + <string name="message_text">İleti metni</string> + <string name="url_copied_to_clipboard">Panoya kopyalanan URL</string> + <string name="message_copied_to_clipboard">Panoya kopyalanan ileti</string> + <string name="image_transmission_failed">Resim aktarılamadı</string> + <string name="scan_qr_code">QR kodunu tara</string> + <string name="show_qr_code">QR kodunu göster</string> + <string name="show_block_list">Engellenenler listesini göster</string> + <string name="account_details">Hesap bilgileri</string> + <string name="verify_otr">OTR doğrula</string> + <string name="remote_fingerprint">Uzak parmak izi</string> + <string name="scan">tara</string> + <string name="smp">Sosyalist Milyoner Protokolü</string> + <string name="shared_secret_hint">İpucu ya da Soru</string> + <string name="shared_secret_secret">Paylaşılan Gizli Bilgi</string> + <string name="confirm">Doğrula</string> + <string name="in_progress">İşleniyor</string> + <string name="respond">Yanıt</string> + <string name="failed">Başarısız</string> + <string name="secrets_do_not_match">Gizli bilgiler eşleşmiyor</string> + <string name="try_again">Yeniden deneyin</string> + <string name="finish">Bitti</string> + <string name="verified">Doğrulandı!</string> + <string name="smp_requested">Kişi SMP doğrulaması istedi</string> + <string name="no_otr_session_found">Geçerli bir OTR oturumu bulunamadı!</string> + <string name="conversations_foreground_service">Conversations</string> + <string name="pref_keep_foreground_service">Ön planda çalışmaya devam etsin</string> + <string name="pref_keep_foreground_service_summary">İşletim sisteminin bağlantınızı koparmasına engel olur</string> + <string name="pref_export_logs">Kayıtları dışa aktar</string> + <string name="pref_export_logs_summary">Kayıtları SD karta yaz</string> + <string name="notification_export_logs_title">Kayıtları SD karta yazıyor</string> + <string name="choose_file">Dosya seç</string> + <string name="receiving_x_file">%1$s alıyor/(%2$d%% tamamlandı)</string> + <string name="download_x_file">%s indir</string> + <string name="file">dosya</string> + <string name="open_x_file">%s aç</string> + <string name="sending_file">gönderiyor (%1$d%% tamamlandı)</string> + <string name="preparing_file">Dosya, aktarma için hazırlanıyor</string> + <string name="x_file_offered_for_download">%s indirme işlemi için sunuldu</string> + <string name="cancel_transmission">Aktarmayı iptal et</string> + <string name="file_transmission_failed">dosya aktarma başarısız oldu</string> + <string name="file_deleted">Dosya silindi</string> + <string name="no_application_found_to_open_file">Dosyayı açacak bir uygulama bulunamadı</string> + <string name="could_not_verify_fingerprint">Parmak izi doğrulanamadı</string> + <string name="manually_verify">Bizzat doğrula</string> + <string name="are_you_sure_verify_fingerprint">Kişilerin OTR parmak izlerini doğrulamak istediğinizden emin misiniz?</string> + <string name="pref_show_dynamic_tags">Dinamik etiketleri göster</string> + <string name="pref_show_dynamic_tags_summary">Kişilerin görünmeyen salt okunur etiketlerini göster</string> + <string name="enable_notifications">Bildirimleri etkinleştir</string> + <string name="conference_with">… ile grup sohbet başlat</string> + <string name="no_conference_server_found">Bir grup sohbet sunucusu bulunamadı</string> + <string name="conference_creation_failed">Grup sohbet başlatılamadı!</string> + <string name="conference_created">Grup sohbet başladıldı!</string> + <string name="secret_accepted">Gizli bilgi kabul edildi!</string> + <string name="reset">Sıfırla</string> + <string name="account_image_description">Hesap avatarı</string> + <string name="copy_otr_clipboard_description">OTR parmak izini panoya kopyala</string> + <string name="copy_omemo_clipboard_description">OMEMO parmak izini panoya kopyala</string> + <string name="regenerate_omemo_key">OMEMO anahtarını yeniden oluştur</string> + <string name="wipe_omemo_pep">PEP’teki diğer cihazları sil</string> + <string name="clear_other_devices">Cihazları sil</string> + <string name="clear_other_devices_desc">OMEMO bildirimindeki diğer cihazların hepsini silmek istediğinizden emin misiniz? Cihazlarınız yeniden bağlandıklarında kendilerini yeniden bildirecekler ama bu süre zarfındaki iletileri alamayabilirler.</string> + <string name="purge_key">Anahtarı sil</string> + <string name="purge_key_desc_part1">Bu anahtarı silmek istediğinizden emin misiniz?</string> + <string name="purge_key_desc_part2">Anahtar geri dönüşü olmayacak şekilde zedelenmiş kabul edilecek bir daha onunla bir oturum başlatamayacaksınız.</string> + <string name="error_no_keys_to_trust_server_error">Bu kişi için kullanılabilr anahtar mevcut değil\nSunucudan yeni anahtarlar alınamadı. Sunucunuzla ilgili bir sorun olabilir.</string> + <string name="error_no_keys_to_trust">Bu kişi için kullanılabilecek bir anahtar bulunmuyor. Eğer anahtarlarını sildiyseniz, yeni anahtar oluşturmaları gerekiyor.</string> + <string name="error_trustkeys_title">Hata</string> + <string name="fetching_history_from_server">Sunucudan geçmiş alınıyor</string> + <string name="no_more_history_on_server">Sunucuda başka geçmiş kalmadı</string> + <string name="updating">Güncelleniyor…</string> + <string name="password_changed">Parola değişti!</string> + <string name="could_not_change_password">Parola değiştirilemedi</string> + <string name="otr_session_not_started">Şifreli bir konuşma başlatmak için ileti gönder</string> + <string name="ask_question">Soru sor</string> + <string name="smp_explain_question">Karşınızdaki kişiyle, ikinizden başka kimsenin bilmediği ortak bir sırrınız varsa (aranızdaki bir şaka ya da sadece en son buluştuğunuzda ne yediğiniz gibi) bunu birbirinizin parmak izlerini doğrulamak için kullanabilirsiniz. \n\n Karşınızdaki kişiye bir ipucu veya soru sorabilirsiniz. Cevabın harf duyarlı olması gerekmektedir.</string> + <string name="smp_explain_answer">Kişi parmak izinizi onaylamak için sadece ikinizin bildiği bir şeyi sormak istiyor. Kişi sırrınız hakkında aşağıdaki ipucu veya soruyu gönderdi.</string> + <string name="shared_secret_hint_should_not_be_empty">İpucunuz boş kalamaz</string> + <string name="shared_secret_can_not_be_empty">Sırrınız boş kalamaz</string> + <string name="manual_verification_explanation">Aşağıdaki parmak izini, karşınızdaki kişinin parmak iziyle dikkatlice karşılaştırın. \n\nŞifreli eposta veya telefon gibi güvenilir bir iletişim kanalı üzerinden bilgi alışverişi yapabilirsiniz.</string> + <string name="change_password">Parolayı değiştirin</string> + <string name="current_password">Mevcut parola</string> + <string name="new_password">Yeni parola</string> + <string name="password_should_not_be_empty">Parola boş kalamaz</string> + <string name="enable_all_accounts">Bütün hesapları etkinleştir</string> + <string name="disable_all_accounts">Bütün hesapları devre dışı bırak</string> + <string name="perform_action_with">Kullanarak tamamla</string> + <string name="no_affiliation">Ortaklık yok</string> + <string name="no_role">Rol yok</string> + <string name="outcast">Bağlantısız</string> + <string name="member">Üye</string> + <string name="advanced_mode">Gelişmiş kip</string> + <string name="grant_membership">Üyeliğe onay ver</string> + <string name="remove_membership">Üyeliği geri çevir</string> + <string name="grant_admin_privileges">Yönetici imtiyazlarını kabul et</string> + <string name="remove_admin_privileges">Yönetici imtiyazlarını geri çevir</string> + <string name="remove_from_room">Grup sohbetten at</string> + <string name="could_not_change_affiliation">%s kişisinin ortaklığı değiştirilemedi</string> + <string name="ban_from_conference">Grup sohbetten at</string> + <string name="removing_from_public_conference">%s kişisini herkese açık grup sohbetten atmaya çalışıyorsunuz. Bunu ancak bu kullanıcıyı daimi men ederek yapabilirsiniz.</string> + <string name="ban_now">Şimdi men et</string> + <string name="could_not_change_role">%s kişisinin rolü değiştirilemedi</string> + <string name="public_conference">Herkese açık grup sohbet</string> + <string name="private_conference">Özel, sadece üyelere açık grup sohbet</string> + <string name="conference_options">Grup sohbet seçenekleri</string> + <string name="members_only">Özel, sadece üyeler</string> + <string name="non_anonymous">Anonim olmayan</string> + <string name="moderated">Denetli</string> + <string name="you_are_not_participating">Katılımcı değilsiniz</string> + <string name="modified_conference_options">Değiştirilmiş grup sohbet seçenekleri!</string> + <string name="could_not_modify_conference_options">Grup sohbet seçenekleri değiştirilemedi</string> + <string name="never">Hiçbir zaman</string> + <string name="thirty_minutes">30 dakika</string> + <string name="one_hour">1 saat</string> + <string name="two_hours">2 saat</string> + <string name="eight_hours">8 saat</string> + <string name="until_further_notice">İkinci bildirime kadar</string> + <string name="pref_input_options">Girdi seçenekleri</string> + <string name="pref_enter_is_send">Enter=gönder</string> + <string name="pref_enter_is_send_summary">İleti göndermek için \"enter\" tuşunu kullanın</string> + <string name="pref_display_enter_key">\"Enter\" tuşunu göster</string> + <string name="pref_display_enter_key_summary">İfade ikonu tuşunu \"enter\" tuşu olarak değiştirin</string> + <string name="audio">ses</string> + <string name="video">video</string> + <string name="image">görüntü</string> + <string name="pdf_document">PDF belgesi</string> + <string name="apk">Android uygulaması</string> + <string name="vcard">Kişi</string> + <string name="received_x_file">%s alındı</string> + <string name="disable_foreground_service">Ön planda çalışmasını devre dışı bırak</string> + <string name="touch_to_open_conversations">Conversations’ı başlatmak için dokunun</string> + <string name="avatar_has_been_published">Avatar yayınlandı!</string> + <string name="sending_x_file">%s gönderiliyor</string> + <string name="offering_x_file">%s sunuluyor</string> + <string name="hide_offline">Çevrimdışı gizle</string> + <string name="disable_account">Hesabı devre dışı bırak</string> + <string name="contact_is_typing">%s yazıyor…</string> + <string name="contact_has_stopped_typing">%s yazmayı bıraktı</string> + <string name="pref_chat_states">Yazma bildirimleri</string> + <string name="pref_chat_states_summary">Karşınızdaki kişi sizin yeni bir ileti yazdığınızı görsün</string> + <string name="send_location">Yer bildirimi gönder</string> + <string name="show_location">Yer bildirimi göster</string> + <string name="no_application_found_to_display_location">Yer bildirimi için bir uygulama bulunamadı</string> + <string name="location">Yer</string> + <string name="received_location">Bildirilen yer</string> + <string name="title_undo_swipe_out_conversation">Sohbet sonlandı</string> + <string name="title_undo_swipe_out_muc">Grup sohbetten ayrıldı</string> + <string name="pref_dont_trust_system_cas_title">Sistem sertifikalarına güvenmeyin</string> + <string name="pref_dont_trust_system_cas_summary">Bütün sertifikalar bizzat onaylanmalıdır</string> + <string name="pref_remove_trusted_certificates_title">Sertifikaları kaldır</string> + <string name="pref_remove_trusted_certificates_summary">Bizzat onaylanmış sertifikaları sil</string> + <string name="toast_no_trusted_certs">Bizzat onaylanmış sertifika yok</string> + <string name="dialog_manage_certs_title">Sertifikaları kaldır</string> + <string name="dialog_manage_certs_positivebutton">Seçilenleri sil</string> + <string name="dialog_manage_certs_negativebutton">İptal</string> + <plurals name="toast_delete_certificates"> + <item quantity="other">%d sertifikaları silindi</item> + </plurals> + <plurals name="select_contact"> + <item quantity="other">%d kişiyi seç</item> + </plurals> + <string name="pref_quick_action_summary">Gönder düğmesini kısayol atamasıyla değiştir</string> + <string name="pref_quick_action">Kısayol</string> + <string name="none">Hiçbiri</string> + <string name="recently_used">En son kullanılanlar</string> + <string name="choose_quick_action">Kısayolu seç</string> + <string name="search_for_contacts_or_groups">Kişi veya gruplarda ara</string> + <string name="send_private_message">Özel ileti gönder</string> + <string name="user_has_left_conference">%s görüşmeden ayrıldı!</string> + <string name="username">Kullanıcı adı</string> + <string name="username_hint">Kullanıcı adı</string> + <string name="invalid_username">Kullanıcı adı geçerli değil</string> + <string name="download_failed_server_not_found">İndirme başarısız: Sunucu bulunamadı</string> + <string name="download_failed_file_not_found">İndirme başarısız: Dosya bulunamadı</string> + <string name="download_failed_could_not_connect">İndirme başarısız: Sunucuya bağlanılamadı</string> + <string name="pref_use_white_background">Beyaz arka plan kullan</string> + <string name="pref_use_white_background_summary">Alınan iletileri beyaz arka plan üzerinde siyah yazıyla göster</string> + <string name="account_status_tor_unavailable">Tor ağına erişilemiyor</string> + <string name="server_info_broken">Bozuk</string> + <string name="pref_presence_settings">Çevrimiçi durum bildirim ayarları</string> + <string name="pref_away_when_screen_off">Ekran kapandığında uzakta</string> + <string name="pref_away_when_screen_off_summary">Ekran kapandığında çevrimiçi durum bildiriminizi uzakta olarak değiştirir</string> + <string name="pref_xa_on_silent_mode">Sessiz moddayken erişilemez</string> + <string name="pref_xa_on_silent_mode_summary">Telefonunuz sessizdeyken, durum bildiriminizi müsait değil olarak değiştirir</string> + <string name="action_add_account_with_certificate">Sertifikalı hesap ekle</string> + <string name="unable_to_parse_certificate">Sertifika çözümlenemedi</string> + <string name="authenticate_with_certificate">w/ sertifikasının kimlik denetimi için boş bırak </string> + <string name="captcha_ocr">Captcha metni</string> + <string name="captcha_required">Captcha gerekli</string> + <string name="captcha_hint">resimdeki metni girin</string> + <string name="certificate_chain_is_not_trusted">Sertifika zinciri güvenilir değil</string> + <string name="jid_does_not_match_certificate">Jabber ID sertifikayla eşleşmiyor</string> + <string name="action_renew_certificate">Sertifikayı yenile</string> + <string name="error_fetching_omemo_key">OMEMO anahtarı alınırken hata oluştu!</string> + <string name="verified_omemo_key_with_certificate">Sertifikalı OMEMO anahtarı onaylandı!</string> + <string name="device_does_not_support_certificates">Cihazınız seçilen istemci sertifikalarını desteklemiyor!</string> + <string name="pref_connection_options">Bağlantı seçenekleri</string> + <string name="pref_use_tor">Tor üzerinden bağlan</string> + <string name="pref_use_tor_summary">Bütün bağlantıları Tor ağı üzerinden aktar. Orbot gerekir.</string> + <string name="account_settings_hostname">Sunucu adı</string> + <string name="account_settings_port">Port</string> + <string name="hostname_or_onion">Sunucu- veya .onion-Address</string> + <string name="not_a_valid_port">Bu port numarası geçerli değil</string> + <string name="not_valid_hostname">Bu sunucu geçerli değil</string> + <string name="connected_accounts"> %2$d hesabın %1$ kadarı bağlandı</string> + <plurals name="x_messages"> + <item quantity="other">%d ileti</item> + </plurals> + <string name="shared_file_with_x">%s ile paylaşılan dosyalar</string> + <string name="shared_image_with_x">%s ile paylaşılan resim</string> + <string name="no_storage_permission">Conversations’ın harici depolama alanına erişmesi gerek </string> + <string name="sync_with_contacts">Kişilerle senkronize et</string> + <string name="sync_with_contacts_long">Conversations XMPP listenizi telefon rehberinizle eşleştirerek kişilerin tam isimlerini ve avatarlarını göstermek istiyor. \n\n Conversations telefon rehberinizi sadece okuyacak ve onları sunucunuza yüklemeden eşleştirecek. \n\n Şimdi telefon rehberinize erişilmesine izin vermeniz istenecek.\n\n</string> + <string name="certificate_information">Sertifika Bilgisi</string> + <string name="certificate_subject">Konu</string> + <string name="certificate_issuer">Veren</string> + <string name="certificate_cn">Ortak ad</string> + <string name="certificate_o">Organizasyon</string> + <string name="certificate_sha1">SHA1</string> + <string name="certicate_info_not_available">(mevcut değil)</string> + <string name="certificate_not_found">Sertifika bulunamadı</string> + <string name="notify_on_all_messages">Tüm iletilerde uyar</string> + <string name="notify_only_when_highlighted">Sadece işaretliyse uyar</string> + <string name="notify_never">Uyarılar devre dışı</string> + <string name="notify_paused">Uyarılar geçici olarak durduruldu</string> + <string name="pref_picture_compression">Resimleri sıkıştır</string> + <string name="pref_picture_compression_summary">Resimleri yeniden boyutlandır ve sıkıştır</string> + <string name="always">Her zaman</string> + <string name="automatically">Otomatik olarak</string> + <string name="battery_optimizations_enabled">Pil optimizasyonu devrede</string> + <string name="battery_optimizations_enabled_explained">Cihazınız Conversations üzerinde yoğun pil optimizasyonu yaptığı için bildirimlerde gecikmeler olabilir hatta bazı ileti kayıpları yaşanabilir.\nBu durumla karşılaşamamak için devre dışı bırakmanız önerilir. </string> + <string name="battery_optimizations_enabled_dialog">Cihazınız Conversations üzerinde yoğun pil optimizasyonu yaptığı için bildirimlerde gecikmeler olabilir hatta bazı ileti kayıpları yaşanabilir.\n Şimdi bunları devre dışı bırakmanız istenecek.</string> + <string name="disable">Devre dışı</string> + <string name="selection_too_large">Seçilen alan çok büyük</string> +</resources> diff --git a/libs/openpgp-api-lib/res/values-fi/strings.xml b/src/main/res/values-uk/strings.xml index c757504a..c757504a 100644 --- a/libs/openpgp-api-lib/res/values-fi/strings.xml +++ b/src/main/res/values-uk/strings.xml diff --git a/src/main/res/values-v21/themes.xml b/src/main/res/values-v21/themes.xml index d1679f92..91d43e77 100644 --- a/src/main/res/values-v21/themes.xml +++ b/src/main/res/values-v21/themes.xml @@ -2,10 +2,13 @@ <resources> <style name="ConversationsTheme" parent="@android:style/Theme.Material.Light.DarkActionBar"> - <item name="android:colorPrimary">@color/green500</item> - <item name="android:colorPrimaryDark">@color/green700</item> + <item name="android:colorPrimary">@color/primary</item> + <item name="android:colorPrimaryDark">@color/primary_dark</item> <item name="android:colorAccent">@color/accent</item> + <item name="android:windowActionModeOverlay">true</item> + <item name="android:actionModeBackground">@color/accent</item> + <item name="TextSizeInfo">12sp</item> <item name="TextSizeBody">14sp</item> <item name="TextSizeHeadline">20sp</item> @@ -18,8 +21,10 @@ <item name="attr/icon_download">@drawable/ic_file_download_white_24dp</item> <item name="attr/icon_edit">@drawable/ic_edit_white_24dp</item> <item name="attr/icon_edit_dark">@drawable/ic_edit_grey600_24dp</item> + <item name="attr/icon_done">@drawable/ic_done_black_24dp</item> <item name="attr/icon_group">@drawable/ic_group_white_24dp</item> <item name="attr/icon_new">@drawable/ic_add_white_24dp</item> + <item name="attr/icon_refresh">@drawable/ic_refresh_grey600_24dp</item> <item name="attr/icon_new_attachment">@drawable/ic_attach_file_white_24dp</item> <item name="attr/icon_not_secure">@drawable/ic_lock_open_white_24dp</item> <item name="attr/icon_remove">@drawable/ic_delete_grey600_24dp</item> diff --git a/libs/openpgp-api-lib/res/values-is/strings.xml b/src/main/res/values-vi/strings.xml index c757504a..c757504a 100644 --- a/libs/openpgp-api-lib/res/values-is/strings.xml +++ b/src/main/res/values-vi/strings.xml diff --git a/src/main/res/values-zh-rCN/strings.xml b/src/main/res/values-zh-rCN/strings.xml index 32bfa38a..a133606f 100644 --- a/src/main/res/values-zh-rCN/strings.xml +++ b/src/main/res/values-zh-rCN/strings.xml @@ -9,7 +9,7 @@ <string name="action_secure">安全对话</string> <string name="action_add_account">添加账号</string> <string name="action_edit_contact">编辑姓名</string> - <string name="action_add_phone_book">添加到手机通讯录</string> + <string name="action_add_phone_book">添加到地址薄</string> <string name="action_delete_contact">从列表中删除</string> <string name="action_block_contact">屏蔽联系人</string> <string name="action_unblock_contact">解除联系人屏蔽</string> @@ -28,7 +28,8 @@ <string name="minutes_ago">%d分钟前</string> <string name="unread_conversations">未读会话</string> <string name="sending">正在发送…</string> - <string name="encrypted_message">解密信息中. 请稍候…</string> + <string name="message_decrypting">解密信息中. 请稍候…</string> + <string name="pgp_message">OpenPGP 加密的信息</string> <string name="nick_in_use">该名称已存在</string> <string name="admin">管理员</string> <string name="owner">所有者</string> @@ -74,30 +75,33 @@ <string name="clear_conversation_history">清除会话记录</string> <string name="clear_histor_msg">删除该会话中所有信息?\n\n<b>注:</b> 该操作不会影响其他设备或服务器保存的信息。</string> <string name="delete_messages">删除消息</string> - <string name="also_end_conversation">之后结束该会话</string> + <string name="also_end_conversation">结束此会话以后</string> <string name="choose_presence">添加在线用户至联系人</string> - <string name="send_plain_text_message">发送纯文本信息</string> + <string name="send_unencrypted_message">发送未加密的信息</string> <string name="send_otr_message">发送 OTR 加密信息</string> + <string name="send_omemo_message">发送 OMEMO 加密信息</string> + <string name="send_omemo_x509_message">发送 v\\OMEMO 加密信息</string> <string name="send_pgp_message">发送 OpenPGP 加密信息</string> <string name="your_nick_has_been_changed">昵称修改成功</string> <string name="send_unencrypted">不加密发送</string> <string name="decryption_failed">解密失败,可能是私钥不正确。</string> <string name="openkeychain_required">OpenKeychain</string> - <string name="openkeychain_required_long">会话运用了第三方app,名为 <b>OpenKeychain</b> 用来加密、解密信息以及管理您的密钥。\n\nOpenKeychain 遵循 GPLv3 并且可以在 F-Droid 和 Google Play 上获取。\n\n<small>(之后请重启 conversations)</small></string> + <string name="openkeychain_required_long">Conversations 使用了第三方app <b>OpenKeychain</b> 来加密、解密信息并管理您的密钥。\n\nOpenKeychain 遵循 GPLv3 并且可以在 F-Droid 和 Google Play 上获取。\n\n<small>(之后请重启 Conversations)</small></string> <string name="restart">重启</string> <string name="install">安装</string> + <string name="openkeychain_not_installed">请安装 OpenKeychain 以解密</string> <string name="offering">输入…</string> <string name="waiting">等待…</string> <string name="no_pgp_key">未发现 OpenPGP 密钥</string> - <string name="contact_has_no_pgp_key">会话加密信息失败,因为联系人未提供他/她的公钥。\n\n<small>请通知联系人设置 OpenPGP。</small></string> + <string name="contact_has_no_pgp_key">Conversations 无法加密信息,因为联系人未提供他/她的公钥。\n\n<small>请通知联系人设置 OpenPGP。</small></string> <string name="no_pgp_keys">未找到 OpenPGP 密钥</string> <string name="contacts_have_no_pgp_keys">因您的联系人未公布公钥,Conversations未能成功加密您的信息.\n\n<small>请通知联系人设置OpenPGP.</small></string> - <string name="encrypted_message_received"><i>加密信息已接收。点击解密并查看。</i></string> + <string name="encrypted_message_received"><i>接收到加密消息。轻触以解密。</i></string> <string name="pref_general">常规</string> <string name="pref_xmpp_resource">XMPP 资源</string> <string name="pref_xmpp_resource_summary">客户端标识名称</string> <string name="pref_accept_files">接收文件</string> - <string name="pref_accept_files_size_summary">自动接收小于 … 的文件</string> + <string name="pref_accept_files_summary">自动接收小于 … 的文件</string> <string name="pref_notification_settings">通知设置</string> <string name="pref_notifications">通知</string> <string name="pref_notifications_summary">收到新消息时通知</string> @@ -105,8 +109,6 @@ <string name="pref_vibrate_summary">收到新消息时震动</string> <string name="pref_sound">声音</string> <string name="pref_sound_summary">收到新消息时的铃声</string> - <string name="pref_conference_notifications">讨论组通知</string> - <string name="pref_conference_notifications_summary">当有新的消息时总是通知而不是亮屏时才通知</string> <string name="pref_notification_grace_period">通知限期</string> <string name="pref_notification_grace_period_summary">接收副本短时间内关闭通知</string> <string name="pref_advanced_options">高级选项</string> @@ -149,9 +151,10 @@ <string name="account_status_regis_not_sup">服务器不支持注册</string> <string name="account_status_security_error">安全错误</string> <string name="account_status_incompatible_server">服务器不兼容</string> - <string name="encryption_choice_none">纯文本内容</string> + <string name="encryption_choice_unencrypted">未加密</string> <string name="encryption_choice_otr">OTR</string> <string name="encryption_choice_pgp">OpenPGP</string> + <string name="encryption_choice_omemo">OMEMO</string> <string name="mgmt_account_edit">编辑账号</string> <string name="mgmt_account_delete">删除账号</string> <string name="mgmt_account_disable">暂时不可用</string> @@ -170,7 +173,7 @@ <string name="passwords_do_not_match">密码不一致</string> <string name="invalid_jid">该 Jabber ID 无效</string> <string name="error_out_of_memory">空间不足。图片过大</string> - <string name="add_phone_book_text">您将添加 %s 至手机联系人列表?</string> + <string name="add_phone_book_text">是否添加 %s 到地址薄?</string> <string name="contact_status_online">在线</string> <string name="contact_status_free_to_chat">自由畅聊</string> <string name="contact_status_away">离开</string> @@ -181,22 +184,23 @@ <string name="muc_details_other_members">其他成员</string> <string name="server_info_show_more">服务器信息</string> <string name="server_info_mam">XEP-0313: MAM</string> - <string name="server_info_carbon_messages">XEP-0280: 消息碳</string> + <string name="server_info_carbon_messages">XEP-0280: 消息复写</string> <string name="server_info_csi">XEP-0352: 客户端状态指示</string> <string name="server_info_blocking">XEP-0191: 屏蔽指令</string> - <string name="server_info_roster_version">XEP-0237: 花名册版本</string> + <string name="server_info_roster_version">XEP-0237: 花名册版本控制</string> <string name="server_info_stream_management">XEP-0198: 流管理</string> - <string name="server_info_pep">XEP-0163: PEP (头像)</string> + <string name="server_info_pep">XEP-0163: PEP (替身 / OMEMO)</string> + <string name="server_info_http_upload">XEP-0363: HTTP 文件上传</string> <string name="server_info_available">有效</string> <string name="server_info_unavailable">无效</string> <string name="missing_public_keys">缺少公钥通知</string> - <string name="last_seen_now">最近一次查看为刚刚</string> - <string name="last_seen_min"> 最近一次查看为一分钟前</string> - <string name="last_seen_mins">最近一次查看为 %d 分钟前</string> - <string name="last_seen_hour">最近一次查看为一小时前</string> - <string name="last_seen_hours">最近一次查看为 %d 小时前</string> - <string name="last_seen_day">最近一次查看为一天前</string> - <string name="last_seen_days">最近一次查看为 %d天前</string> + <string name="last_seen_now">刚刚查看过</string> + <string name="last_seen_min">1 分钟前查看过</string> + <string name="last_seen_mins">%d 分钟前查看过</string> + <string name="last_seen_hour">1 小时前查看过</string> + <string name="last_seen_hours">%d 小时前查看过</string> + <string name="last_seen_day">1 天前查看过</string> + <string name="last_seen_days">%d 天前查看过</string> <string name="never_seen">未曾查看</string> <string name="install_openkeychain">加密信息. 请安装 OpenKeychain 以解密。</string> <string name="unknown_otr_fingerprint">未知 OTR 指纹</string> @@ -204,17 +208,28 @@ <string name="reception_failed">接收失败</string> <string name="your_fingerprint">你的指纹</string> <string name="otr_fingerprint">OTR 指纹</string> + <string name="omemo_fingerprint">OMEMO 指纹</string> + <string name="omemo_fingerprint_x509">v\\OMEMO 指纹</string> + <string name="omemo_fingerprint_selected_message">消息的 OMEMO 指纹</string> + <string name="omemo_fingerprint_x509_selected_message">消息的 OMEMO 指纹</string> + <string name="this_device_omemo_fingerprint">自己的 OMEMO 指纹</string> + <string name="other_devices">其他设备</string> + <string name="trust_omemo_fingerprints">信任的 OMEMO 指纹</string> + <string name="fetching_keys">获取密钥中</string> + <string name="done">完成</string> <string name="verify">验证</string> <string name="decrypt">解密</string> <string name="conferences">讨论组</string> <string name="search">查找</string> <string name="create_contact">创建联系人</string> + <string name="enter_contact">输入联系人</string> <string name="join_conference">加入讨论组</string> <string name="delete_contact">删除联系人</string> <string name="view_contact_details">查看联系人详细信息</string> <string name="block_contact">屏蔽联系人</string> <string name="unblock_contact">解除联系人屏蔽</string> <string name="create">创建</string> + <string name="select">选择</string> <string name="contact_already_exists">联系人已存在</string> <string name="join">加入</string> <string name="conference_address">讨论组地址</string> @@ -238,7 +253,7 @@ <string name="error_saving_avatar">不能将头像保存至磁盘</string> <string name="or_long_press_for_default">(或长按按钮将返回默认头像)</string> <string name="error_publish_avatar_no_server_support">您的服务器不支持发布头像</string> - <string name="private_message">密谈</string> + <string name="private_message">私聊</string> <string name="private_message_to">至 %s</string> <string name="send_private_message_to">发送私密消息到 %s</string> <string name="connect">连接</string> @@ -246,10 +261,9 @@ <string name="next">下一步</string> <string name="server_info_session_established">当前会话已建立</string> <string name="additional_information">其他信息</string> - <string name="skip">Skip略过</string> + <string name="skip">忽略</string> <string name="disable_notifications">关闭通知</string> <string name="disable_notifications_for_this_conversation">关闭该会话消息</string> - <string name="notifications_disabled">通知已关闭</string> <string name="enable">打开通知</string> <string name="conference_requires_password">讨论组设有密码</string> <string name="enter_password">输入密码</string> @@ -265,10 +279,10 @@ <string name="pref_force_encryption_summary"> 总是发送加密信息(讨论组信息除外)</string> <string name="pref_dont_save_encrypted">不保存加密信息</string> <string name="pref_dont_save_encrypted_summary">警告:此操作将会导致信息丢失</string> - <string name="pref_expert_options">导出选项</string> + <string name="pref_expert_options">专家选项</string> <string name="pref_expert_options_summary">请谨慎使用</string> <string name="title_activity_about">关于 Conversations</string> - <string name="pref_about_conversations_summary">构建及许可证信息</string> + <string name="pref_about_conversations_summary">编译及许可证信息</string> <string name="title_pref_quiet_hours">静默时间段</string> <string name="title_pref_quiet_hours_start_time">开始时间</string> <string name="title_pref_quiet_hours_end_time">结束时间</string> @@ -284,15 +298,19 @@ <string name="pref_conference_name">讨论组名称</string> <string name="pref_conference_name_summary">用讨论组的主题来标示讨论组而不是 JID</string> <string name="toast_message_otr_fingerprint">OTR 指纹已拷贝到剪贴板!</string> + <string name="toast_message_omemo_fingerprint">OMEMO 指纹已拷贝到剪贴板!</string> <string name="conference_banned">你被此讨论组屏蔽</string> <string name="conference_members_only">此讨论组只允许成员加入</string> <string name="conference_kicked">你被从此讨论组踢出</string> <string name="using_account">用账户 %s</string> + <string name="checking_x">正在 HTTP 服务器中检查 %s</string> <string name="not_connected_try_again">你没有连接。请稍后重试</string> + <string name="check_x_filesize">检查 %s 大小</string> <string name="message_options">消息选项</string> <string name="copy_text">拷贝文本</string> <string name="copy_original_url">拷贝原始URL</string> <string name="send_again">再次发送</string> + <string name="file_url">文件 </string> <string name="message_text">消息文本</string> <string name="url_copied_to_clipboard">已经拷贝 URL 到剪贴板</string> <string name="message_copied_to_clipboard">消息已经拷贝到剪贴板</string> @@ -304,7 +322,6 @@ <string name="verify_otr">验证 OTR</string> <string name="remote_fingerprint">远程指纹</string> <string name="scan">扫描</string> - <string name="or_touch_phones">(或轻触手机)</string> <string name="smp">Socialist Millionaire Protocol</string> <string name="shared_secret_hint">提示或问题</string> <string name="shared_secret_secret">共知的秘密</string> @@ -321,7 +338,10 @@ <string name="conversations_foreground_service">Conversations</string> <string name="pref_keep_foreground_service">保持前台服务</string> <string name="pref_keep_foreground_service_summary">防止操作系统中断你的连接</string> - <string name="choose_file">关闭文件</string> + <string name="pref_export_logs">导出日志</string> + <string name="pref_export_logs_summary">将日志写入 SD 卡</string> + <string name="notification_export_logs_title">正在将日志写入 SD 卡</string> + <string name="choose_file">选择文件</string> <string name="receiving_x_file">接收中 %1$s (已完成 %2$d%%)</string> <string name="download_x_file">下载 %s</string> <string name="file">文件</string> @@ -336,7 +356,7 @@ <string name="could_not_verify_fingerprint">不能验证指纹</string> <string name="manually_verify">手工验证</string> <string name="are_you_sure_verify_fingerprint">你确认验证你的联系人的 OTR 指纹?</string> - <string name="pref_show_dynamic_tags">现实动态标签</string> + <string name="pref_show_dynamic_tags">显示动态标签</string> <string name="pref_show_dynamic_tags_summary">在联系人下方显示只读标签</string> <string name="enable_notifications">启用通知</string> <string name="conference_with">与…创建讨论组</string> @@ -347,12 +367,23 @@ <string name="reset">重置</string> <string name="account_image_description">账户头像</string> <string name="copy_otr_clipboard_description">拷贝 OTR 指纹到剪贴板</string> + <string name="copy_omemo_clipboard_description">拷贝 OMEMO 指纹到剪贴板</string> + <string name="regenerate_omemo_key">重新生成 OMEMO 密钥</string> + <string name="wipe_omemo_pep">从 PEP 中清除其他设备</string> + <string name="clear_other_devices">清除设备</string> + <string name="clear_other_devices_desc">你想清除所有其他设备的 OMEMO 通告?下次你的设备连接,将会重新收到通告,但也许将不会收到当时你发送的消息。</string> + <string name="purge_key">清除密钥</string> + <string name="purge_key_desc_part1">是否确认清除该密钥?</string> + <string name="purge_key_desc_part2">这是不可逆的损坏,你不能用此再建立一个会话了。</string> + <string name="error_no_keys_to_trust_server_error">此联系人没有可用的密钥。\n从服务器获取密钥失败。也许你的联系人所在服务器发生问题。</string> + <string name="error_no_keys_to_trust">此联系人没有可用的密钥。如果你曾经清除过他们的密钥,那么需要他们生成新的密钥。</string> + <string name="error_trustkeys_title">错误</string> <string name="fetching_history_from_server">从服务器获取历史记录</string> <string name="no_more_history_on_server">服务器上没有更多历史记录</string> <string name="updating">更新中…</string> <string name="password_changed">密码已修改!</string> <string name="could_not_change_password">不能修改密码</string> - <string name="otr_session_not_started">要启动加密聊天先发送一条消息</string> + <string name="otr_session_not_started">发送消息来开始加密聊天</string> <string name="ask_question">提出问题</string> <string name="smp_explain_question">如果你和你的联系人有一个共知的秘密(比如一个内部笑话或者仅仅只是上次见面时吃的午餐) 你可以使用这个秘密来验证彼此的指纹。\n\n你的联系人将以大小写敏感的方式给出答案,你可以给出提示或问题。</string> <string name="smp_explain_answer">你的联系人可以通过一个你们共知的秘密来验证指纹。你的联系人给出了如下的提示或问题。</string> @@ -365,7 +396,7 @@ <string name="password_should_not_be_empty">密码不能为空</string> <string name="enable_all_accounts">启用所有账户</string> <string name="disable_all_accounts">禁用所有账户</string> - <string name="perform_action_with">做一个动作和</string> + <string name="perform_action_with">选择一个操作</string> <string name="no_affiliation">没有从属关系</string> <string name="no_role">没有角色</string> <string name="outcast">抛弃</string> @@ -384,8 +415,10 @@ <string name="public_conference">公开访问的讨论组</string> <string name="private_conference">私密,只有成员可以加入的讨论组</string> <string name="conference_options">讨论组选项</string> - <string name="members_only">私密(只对成员开放)</string> + <string name="members_only">私密,只有成员可以加入</string> <string name="non_anonymous">非匿名</string> + <string name="moderated">版主</string> + <string name="you_are_not_participating">您尚未参与</string> <string name="modified_conference_options">讨论组选项已修改!</string> <string name="could_not_modify_conference_options">不能修改讨论组选项</string> <string name="never">从不</string> @@ -406,14 +439,14 @@ <string name="apk">Android App</string> <string name="vcard">联系人</string> <string name="received_x_file">已经收到 %s</string> - <string name="disable_foreground_service">禁用前端服务</string> + <string name="disable_foreground_service">禁用前台服务</string> <string name="touch_to_open_conversations">轻触打开 Conversations</string> <string name="avatar_has_been_published">头像已经发布!</string> <string name="sending_x_file">发送中 %s</string> <string name="offering_x_file">提供中 %s</string> <string name="hide_offline">隐藏离线联系人</string> <string name="disable_account">禁用账户</string> - <string name="contact_is_typing">%s 正在输入…</string> + <string name="contact_is_typing">%s 正在输入</string> <string name="contact_has_stopped_typing">%s 已停止输入</string> <string name="pref_chat_states">键盘输入通知</string> <string name="pref_chat_states_summary">让对方知道你正在输入新消息</string> @@ -424,7 +457,6 @@ <string name="received_location">位置已收到</string> <string name="title_undo_swipe_out_conversation">Conversation 已关闭</string> <string name="title_undo_swipe_out_muc">离开讨论组</string> - <string name="pref_certificate_options">证书选项</string> <string name="pref_dont_trust_system_cas_title">不相信系统 CA</string> <string name="pref_dont_trust_system_cas_summary">所有证书必须人工通过</string> <string name="pref_remove_trusted_certificates_title">移除证书</string> @@ -444,4 +476,74 @@ <string name="none">无</string> <string name="recently_used">最近使用过的</string> <string name="choose_quick_action">选择快速动作</string> + <string name="search_for_contacts_or_groups">搜索联系人或群组</string> + <string name="send_private_message">发送私密消息</string> + <string name="user_has_left_conference">%s 已离开讨论组!</string> + <string name="username">用户名</string> + <string name="username_hint">用户名</string> + <string name="invalid_username">该用户名无效</string> + <string name="download_failed_server_not_found">下载失败:未找到服务器</string> + <string name="download_failed_file_not_found">下载失败:未找到文件</string> + <string name="download_failed_could_not_connect">下载失败:无法连接到服务器</string> + <string name="pref_use_white_background">使用白色背景</string> + <string name="pref_use_white_background_summary">收到的消息将显示为白底黑字</string> + <string name="account_status_tor_unavailable">Tor network 不可用</string> + <string name="server_info_broken">损坏</string> + <string name="pref_presence_settings">上线设置</string> + <string name="pref_away_when_screen_off">关闭屏幕时离开</string> + <string name="pref_away_when_screen_off_summary">当屏幕关闭时将标记您的资源为离开状态</string> + <string name="pref_xa_on_silent_mode">静音模式时不可用</string> + <string name="pref_xa_on_silent_mode_summary">当设备进入静音模式时把资源标识改为不可用</string> + <string name="action_add_account_with_certificate">使用证书添加账户</string> + <string name="unable_to_parse_certificate">无法解析证书</string> + <string name="authenticate_with_certificate">留空以认证 w/ 证书</string> + <string name="captcha_ocr">验证码</string> + <string name="captcha_required">需要验证码</string> + <string name="captcha_hint">输入图片中的文字</string> + <string name="certificate_chain_is_not_trusted">证书链不受信任</string> + <string name="jid_does_not_match_certificate">Jabber ID 与证书不匹配</string> + <string name="action_renew_certificate">更新证书</string> + <string name="error_fetching_omemo_key">获取 OMEMO 密钥错误!</string> + <string name="verified_omemo_key_with_certificate">请用证书验证 OMEMO 密钥!</string> + <string name="device_does_not_support_certificates">您的设备不支持设备证书选择!</string> + <string name="pref_connection_options">连接选项</string> + <string name="pref_use_tor">通过 Tor 连接</string> + <string name="pref_use_tor_summary">所有连接使用 Tor 网络隧道。需要 Orbot</string> + <string name="account_settings_hostname">主机名</string> + <string name="account_settings_port">端口</string> + <string name="hostname_or_onion">服务器 - 或者 .orion 地址</string> + <string name="not_a_valid_port">该端口号无效</string> + <string name="not_valid_hostname">该主机名无效</string> + <string name="connected_accounts">%2$d 个中的 %1$d 个账户已连接</string> + <plurals name="x_messages"> + <item quantity="other">%d 条消息</item> + </plurals> + <string name="shared_file_with_x">用 %s 分享文件</string> + <string name="shared_image_with_x">用 %s 分享图片</string> + <string name="no_storage_permission">Conversations 需要访问外部存储</string> + <string name="sync_with_contacts">与联系人同步</string> + <string name="sync_with_contacts_long">Conversations 会匹配你的 XMPP 花名册与你的联系人,以显示他们的全名和头像。\n\nConversations 只会读取你的联系人并在本地匹配,不会上传到你的服务器。\n\n现在将要询问你是否给予访问你联系人的权限。</string> + <string name="certificate_information">证书详情</string> + <string name="certificate_subject">主题</string> + <string name="certificate_issuer">发行人</string> + <string name="certificate_cn">通用名称</string> + <string name="certificate_o">组织</string> + <string name="certificate_sha1">SHA1</string> + <string name="certicate_info_not_available">(不可用)</string> + <string name="certificate_not_found">未发现证书</string> + <string name="notify_on_all_messages">为所有信息显示通知</string> + <string name="notify_only_when_highlighted">仅当高亮时显示通知</string> + <string name="notify_never">禁用通知</string> + <string name="notify_paused">暂停通知</string> + <string name="pref_picture_compression">压缩图片</string> + <string name="pref_picture_compression_summary">缩小并压缩图片</string> + <string name="always">总是</string> + <string name="automatically">自动</string> + <string name="battery_optimizations_enabled">启用节电模式</string> + <string name="battery_optimizations_enabled_explained">你的设备正在为Conversations进行电池优化,这可能导致通知的延迟甚至消息的丢失。 +建议不要这样做</string> + <string name="battery_optimizations_enabled_dialog">你的设备正在为Conversations进行电池优化,这可能导致通知的延迟甚至消息的丢失。 +你将会被提示禁用该功能。</string> + <string name="disable">禁用</string> + <string name="selection_too_large">选择区域过大</string> </resources> diff --git a/src/main/res/values-zh-rTW/strings.xml b/src/main/res/values-zh-rTW/strings.xml index 95d9e4e6..3a5ce0fe 100644 --- a/src/main/res/values-zh-rTW/strings.xml +++ b/src/main/res/values-zh-rTW/strings.xml @@ -8,7 +8,6 @@ <string name="action_secure">安全對話</string> <string name="action_add_account">新增帳戶</string> <string name="action_edit_contact">編輯姓名</string> - <string name="action_add_phone_book">新增到手機通訊錄</string> <string name="action_delete_contact">從列表中刪除</string> <string name="title_activity_manage_accounts">管理帳戶</string> <string name="title_activity_conference_details">群組詳情</string> @@ -21,7 +20,6 @@ <string name="minutes_ago">%d 分鐘前</string> <string name="unread_conversations">未讀對話</string> <string name="sending">正在發送…</string> - <string name="encrypted_message">正在解密訊息中,請稍候…</string> <string name="nick_in_use">該用戶名稱已被使用</string> <string name="admin">管理員</string> <string name="owner">擁有人</string> @@ -58,9 +56,7 @@ <string name="clear_conversation_history">清除對話記錄</string> <string name="clear_histor_msg">你確定要刪除該對話中所有訊息嗎?\n\n<b>警告:</b> 這將不會影響其他設備或伺服器儲存的訊息。</string> <string name="delete_messages">刪除訊息</string> - <string name="also_end_conversation">之後結束這對話</string> <string name="choose_presence">選擇狀態訊息</string> - <string name="send_plain_text_message">發送純文字訊息</string> <string name="send_otr_message">發送 OTR 加密訊息</string> <string name="send_pgp_message">發送 OpenPGP 加密訊息</string> <string name="your_nick_has_been_changed">用戶名稱修改成功</string> @@ -76,7 +72,6 @@ <string name="contact_has_no_pgp_key">Conversations 不能將你的訊息加密,因為聯絡人沒有公佈他/她的公鑰。\n\n<small>請通知聯絡人設定 OpenPGP。</small></string> <string name="no_pgp_keys">找不到多條 OpenPGP 鑰匙</string> <string name="contacts_have_no_pgp_keys">Conversations 不能將你的訊息加密,因為多位聯絡人沒有公佈他/她的公鑰。\n\n<small>請通知聯絡人設定 OpenPGP。</small></string> - <string name="encrypted_message_received"><i>已收到加密訊息,點擊進行解密和查看。</i></string> <string name="pref_general">一般</string> <string name="pref_xmpp_resource">XMPP 資源</string> <string name="pref_xmpp_resource_summary">客戶端標示名稱</string> @@ -89,8 +84,6 @@ <string name="pref_vibrate_summary">收到新訊息時震動</string> <string name="pref_sound">聲音</string> <string name="pref_sound_summary">收到新訊息時播放鈴聲</string> - <string name="pref_conference_notifications">群組通知</string> - <string name="pref_conference_notifications_summary">當有新訊息時總是通知,而不是被標記時才通知</string> <string name="pref_notification_grace_period">通知限期</string> <string name="pref_notification_grace_period_summary">收到副本後,關閉通知一小段時間</string> <string name="pref_advanced_options">進階選項</string> @@ -131,7 +124,6 @@ <string name="account_status_regis_conflict">該用戶名稱已被使用</string> <string name="account_status_regis_success">註冊完成</string> <string name="account_status_regis_not_sup">伺服器不支持註冊</string> - <string name="encryption_choice_none">純文字內容</string> <string name="encryption_choice_otr">OTR</string> <string name="encryption_choice_pgp">OpenPGP</string> <string name="mgmt_account_edit">編輯帳戶</string> @@ -152,7 +144,6 @@ <string name="passwords_do_not_match">密碼不一致</string> <string name="invalid_jid">該 Jabber ID 無效</string> <string name="error_out_of_memory">空間不足,圖片過大</string> - <string name="add_phone_book_text">你確定要新增 %s 為聯絡人嗎?</string> <string name="contact_status_online">線上</string> <string name="contact_status_free_to_chat">目前有空</string> <string name="contact_status_away">離開</string> @@ -163,7 +154,6 @@ <string name="muc_details_other_members">其他成員</string> <string name="server_info_carbon_messages">XEP-0280: Message Carbons</string> <string name="server_info_stream_management">XEP-0198: Stream Management</string> - <string name="server_info_pep">XEP-0163: PEP (Avatars)</string> <string name="server_info_available">支援</string> <string name="server_info_unavailable">不支援</string> <string name="missing_public_keys">沒有公佈公鑰訊息。</string> @@ -224,7 +214,6 @@ <string name="skip">略過</string> <string name="disable_notifications">關閉通知</string> <string name="disable_notifications_for_this_conversation">關閉該對話消息</string> - <string name="notifications_disabled">通知已關閉</string> <string name="enable">打開通知</string> <string name="conference_requires_password">群組設有密碼</string> <string name="enter_password">輸入密碼</string> diff --git a/src/main/res/values/arrays.xml b/src/main/res/values/arrays.xml index 24f8790c..be52464a 100644 --- a/src/main/res/values/arrays.xml +++ b/src/main/res/values/arrays.xml @@ -79,4 +79,16 @@ <item>ALWAYS</item> <item>NEVER</item> </string-array> + + <string-array name="picture_compression_values"> + <item>never</item> + <item>auto</item> + <item>always</item> + </string-array> + + <string-array name="picture_compression_entries"> + <item>@string/never</item> + <item>@string/automatically</item> + <item>@string/always</item> + </string-array> </resources> diff --git a/src/main/res/values/attrs.xml b/src/main/res/values/attrs.xml index 7a195aa4..0d6d19b1 100644 --- a/src/main/res/values/attrs.xml +++ b/src/main/res/values/attrs.xml @@ -14,6 +14,7 @@ <attr name="icon_download" format="reference"/> <attr name="icon_edit" format="reference"/> <attr name="icon_edit_dark" format="reference"/> + <attr name="icon_done" format="reference"/> <attr name="icon_group" format="reference"/> <attr name="icon_new" format="reference"/> <attr name="icon_new_attachment" format="reference"/> diff --git a/src/main/res/values/colors.xml b/src/main/res/values/colors.xml index 9cef1b0a..3ecc2225 100644 --- a/src/main/res/values/colors.xml +++ b/src/main/res/values/colors.xml @@ -9,17 +9,21 @@ <color name="online">@color/green500</color> <color name="notification">@color/green500</color> - <color name="green500">#ff259b24</color> - <color name="green700">#ff0a7e07</color> - <color name="accent">#ff0091ea</color> - <color name="black87">#de000000</color> - <color name="black54">#8a000000</color> - <color name="black12">#1f000000</color> - <color name="white">#ffffffff</color> - <color name="white70">#b2ffffff</color> - <color name="grey50">#fffafafa</color> - <color name="grey200">#ffeeeeee</color> - <color name="grey800">#ff424242</color> - <color name="red500">#fff44336</color> - <color name="orange500">#ffff9800</color> + <color name="primary">@color/green500</color> + <color name="primary_dark">@color/green700</color> + <color name="accent">#ff0091ea</color> + <color name="black87">#de000000</color> + <color name="black54">#8a000000</color> + <color name="black26">#42000000</color> + <color name="black12">#1f000000</color> + <color name="white">#ffffffff</color> + <color name="white70">#b2ffffff</color> + <color name="grey50">#fffafafa</color> + <color name="grey200">#ffeeeeee</color> + <color name="grey500">#ff9e9e9e</color> + <color name="grey800">#ff424242</color> + <color name="red500">#fff44336</color> + <color name="red800">#ffc62828</color> + <color name="orange500">#ffff9800</color> + <color name="green500">#ff259b24</color> </resources>
\ No newline at end of file diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 928240ad..a7ded490 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -1,291 +1,305 @@ <?xml version="1.0" encoding="utf-8"?> <resources> - <string name="app_name" translatable="false">Conversations</string> - <string name="action_settings">Settings</string> - <string name="action_add">New conversation</string> - <string name="action_accounts">Manage accounts</string> - <string name="action_end_conversation">End this conversation</string> - <string name="action_contact_details">Contact details</string> - <string name="action_muc_details">Conference details</string> - <string name="action_secure">Secure conversation</string> - <string name="action_add_account">Add account</string> - <string name="action_edit_contact">Edit name</string> - <string name="action_add_phone_book">Add to phone book</string> - <string name="action_delete_contact">Delete from roster</string> - <string name="action_block_contact">Block contact</string> - <string name="action_unblock_contact">Unblock contact</string> - <string name="action_block_domain">Block domain</string> - <string name="action_unblock_domain">Unblock domain</string> - <string name="title_activity_manage_accounts">Manage Accounts</string> - <string name="title_activity_settings">Settings</string> - <string name="title_activity_conference_details">Conference Details</string> - <string name="title_activity_contact_details">Contact Details</string> - <string name="title_activity_sharewith">Share with Conversation</string> - <string name="title_activity_start_conversation">Start Conversation</string> - <string name="title_activity_choose_contact">Choose contact</string> - <string name="title_activity_block_list">Block list</string> - <string name="just_now">just now</string> - <string name="minute_ago">1 min ago</string> - <string name="minutes_ago">%d mins ago</string> - <string name="unread_conversations">unread Conversations</string> - <string name="sending">sending…</string> - <string name="encrypted_message">Decrypting message. Please wait…</string> - <string name="nick_in_use">Nickname is already in use</string> - <string name="admin">Admin</string> - <string name="owner">Owner</string> - <string name="moderator">Moderator</string> - <string name="participant">Participant</string> - <string name="visitor">Visitor</string> - <string name="remove_contact_text">Would you like to remove %s from your roster? The conversation associated with this contact will not be removed.</string> - <string name="block_contact_text">Would you like to block %s from sending you messages?</string> - <string name="unblock_contact_text">Would you like to unblock %s and allow them to send you messages?</string> - <string name="block_domain_text">Block all contacts from %s?</string> - <string name="unblock_domain_text">Unblock all contacts from %s?</string> - <string name="contact_blocked">Contact blocked</string> - <string name="remove_bookmark_text">Would you like to remove %s as a bookmark? The conversation associated with this bookmark will not be removed.</string> - <string name="register_account">Register new account on server</string> - <string name="change_password_on_server">Change password on server</string> - <string name="share_with">Share with…</string> - <string name="start_conversation">Start Conversation</string> - <string name="invite_contact">Invite Contact</string> - <string name="contacts">Contacts</string> - <string name="cancel">Cancel</string> - <string name="set">Set</string> - <string name="add">Add</string> - <string name="edit">Edit</string> - <string name="delete">Delete</string> - <string name="block">Block</string> - <string name="unblock">Unblock</string> - <string name="save">Save</string> - <string name="ok">OK</string> - <string name="crash_report_title">Conversations has crashed</string> - <string name="crash_report_message">By sending in stack traces you are helping the ongoing development of Conversations\n<b>Warning:</b> This will use your XMPP account to send the stack trace to the developer.</string> - <string name="send_now">Send now</string> - <string name="send_never">Never ask again</string> - <string name="problem_connecting_to_account">Unable to connect to account</string> - <string name="problem_connecting_to_accounts">Unable to connect to multiple accounts</string> - <string name="touch_to_fix">Touch here to manage your accounts</string> - <string name="attach_file">Attach file</string> - <string name="not_in_roster">The contact is not in your roster. Would you like to add it?</string> - <string name="add_contact">Add contact</string> - <string name="send_failed">delivery failed</string> - <string name="send_rejected">rejected</string> - <string name="preparing_image">Preparing image for transmission</string> - <string name="action_clear_history">Clear history</string> - <string name="clear_conversation_history">Clear Conversation History</string> - <string name="clear_histor_msg">Do you want to delete all messages within this Conversation?\n\n<b>Warning:</b> This will not influence messages stored on other devices or servers.</string> - <string name="delete_messages">Delete messages</string> - <string name="also_end_conversation">End this conversations afterwards</string> - <string name="choose_presence">Choose presence to contact</string> - <string name="send_plain_text_message">Send plain text message</string> - <string name="send_otr_message">Send OTR encrypted message</string> - <string name="send_pgp_message">Send OpenPGP encrypted message</string> - <string name="your_nick_has_been_changed">Your nickname has been changed</string> - <string name="download_image">Download Image</string> - <string name="send_unencrypted">Send unencrypted</string> - <string name="decryption_failed">Decryption failed. Maybe you don’t have the proper private key.</string> - <string name="openkeychain_required">OpenKeychain</string> - <string name="openkeychain_required_long">Conversations utilizes a third party app called <b>OpenKeychain</b> to encrypt and decrypt messages and to manage your public keys.\n\nOpenKeychain is licensed under GPLv3 and available on F-Droid and Google Play.\n\n<small>(Please restart Conversations afterwards.)</small></string> - <string name="restart">Restart</string> - <string name="install">Install</string> - <string name="offering">offering…</string> - <string name="waiting">waiting…</string> - <string name="no_pgp_key">No OpenPGP Key found</string> - <string name="contact_has_no_pgp_key">Conversations is unable to encrypt your messages because your contact is not announcing his or hers public key.\n\n<small>Please ask your contact to setup OpenPGP.</small></string> - <string name="no_pgp_keys">No OpenPGP Keys found</string> - <string name="contacts_have_no_pgp_keys">Conversations is unable to encrypt your messages because your contacts are not announcing their public key.\n\n<small>Please ask your contacts to setup OpenPGP.</small></string> - <string name="encrypted_message_received"><i>Encrypted message received. Touch to view and decrypt.</i></string> - <string name="pref_general">General</string> - <string name="pref_xmpp_resource">XMPP resource</string> - <string name="pref_xmpp_resource_summary">The name this client identifies itself with</string> - <string name="pref_accept_files">Accept files</string> - <string name="pref_accept_files_summary">Settings for accepting and automatically downloading files</string> + <string name="app_name" translatable="false">Conversations</string> + <string name="action_settings">Settings</string> + <string name="action_add">New conversation</string> + <string name="action_accounts">Manage accounts</string> + <string name="action_end_conversation">End this conversation</string> + <string name="action_contact_details">Contact details</string> + <string name="action_muc_details">Conference details</string> + <string name="action_secure">Secure conversation</string> + <string name="action_add_account">Add account</string> + <string name="action_edit_contact">Edit name</string> + <string name="action_add_phone_book">Add to address book</string> + <string name="action_delete_contact">Delete from roster</string> + <string name="action_block_contact">Block contact</string> + <string name="action_unblock_contact">Unblock contact</string> + <string name="action_block_domain">Block domain</string> + <string name="action_unblock_domain">Unblock domain</string> + <string name="title_activity_manage_accounts">Manage Accounts</string> + <string name="title_activity_settings">Settings</string> + <string name="title_activity_conference_details">Conference Details</string> + <string name="title_activity_contact_details">Contact Details</string> + <string name="title_activity_sharewith">Share with Conversation</string> + <string name="title_activity_start_conversation">Start Conversation</string> + <string name="title_activity_choose_contact">Choose contact</string> + <string name="title_activity_block_list">Block list</string> + <string name="just_now">just now</string> + <string name="minute_ago">1 min ago</string> + <string name="minutes_ago">%d mins ago</string> + <string name="unread_conversations">unread Conversations</string> + <string name="sending">sending…</string> + <string name="message_decrypting">Decrypting message. Please wait…</string> + <string name="pgp_message">OpenPGP encrypted message</string> + <string name="nick_in_use">Nickname is already in use</string> + <string name="admin">Admin</string> + <string name="owner">Owner</string> + <string name="moderator">Moderator</string> + <string name="participant">Participant</string> + <string name="visitor">Visitor</string> + <string name="remove_contact_text">Would you like to remove %s from your roster? The conversation associated with this contact will not be removed.</string> + <string name="block_contact_text">Would you like to block %s from sending you messages?</string> + <string name="unblock_contact_text">Would you like to unblock %s and allow them to send you messages?</string> + <string name="block_domain_text">Block all contacts from %s?</string> + <string name="unblock_domain_text">Unblock all contacts from %s?</string> + <string name="contact_blocked">Contact blocked</string> + <string name="remove_bookmark_text">Would you like to remove %s as a bookmark? The conversation associated with this bookmark will not be removed.</string> + <string name="register_account">Register new account on server</string> + <string name="change_password_on_server">Change password on server</string> + <string name="share_with">Share with…</string> + <string name="start_conversation">Start Conversation</string> + <string name="invite_contact">Invite Contact</string> + <string name="contacts">Contacts</string> + <string name="cancel">Cancel</string> + <string name="set">Set</string> + <string name="add">Add</string> + <string name="edit">Edit</string> + <string name="delete">Delete</string> + <string name="block">Block</string> + <string name="unblock">Unblock</string> + <string name="save">Save</string> + <string name="ok">OK</string> + <string name="crash_report_title">Conversations has crashed</string> + <string name="crash_report_message">By sending in stack traces you are helping the ongoing development of Conversations\n<b>Warning:</b> This will use your XMPP account to send the stack trace to the developer.</string> + <string name="send_now">Send now</string> + <string name="send_never">Never ask again</string> + <string name="problem_connecting_to_account">Unable to connect to account</string> + <string name="problem_connecting_to_accounts">Unable to connect to multiple accounts</string> + <string name="touch_to_fix">Touch here to manage your accounts</string> + <string name="attach_file">Attach file</string> + <string name="not_in_roster">The contact is not in your roster. Would you like to add it?</string> + <string name="add_contact">Add contact</string> + <string name="send_failed">delivery failed</string> + <string name="send_rejected">rejected</string> + <string name="preparing_image">Preparing image for transmission</string> + <string name="action_clear_history">Clear history</string> + <string name="clear_conversation_history">Clear Conversation History</string> + <string name="clear_histor_msg">Do you want to delete all messages within this Conversation?\n\n<b>Warning:</b> This will not influence messages stored on other devices or servers.</string> + <string name="delete_messages">Delete messages</string> + <string name="also_end_conversation">End this conversation afterwards</string> + <string name="choose_presence">Choose presence to contact</string> + <string name="send_unencrypted_message">Send unencrypted message</string> + <string name="send_otr_message">Send OTR encrypted message</string> + <string name="send_omemo_message">Send OMEMO encrypted message</string> + <string name="send_omemo_x509_message">Send v\\OMEMO encrypted message</string> + <string name="send_pgp_message">Send OpenPGP encrypted message</string> + <string name="your_nick_has_been_changed">Your nickname has been changed</string> + <string name="send_unencrypted">Send unencrypted</string> + <string name="decryption_failed">Decryption failed. Maybe you don’t have the proper private key.</string> + <string name="openkeychain_required">OpenKeychain</string> + <string name="openkeychain_required_long">Conversations utilizes a third party app called <b>OpenKeychain</b> to encrypt and decrypt messages and to manage your public keys.\n\nOpenKeychain is licensed under GPLv3 and available on F-Droid and Google Play.\n\n<small>(Please restart Conversations afterwards.)</small></string> + <string name="restart">Restart</string> + <string name="install">Install</string> + <string name="openkeychain_not_installed">Please install OpenKeychain</string> + <string name="offering">offering…</string> + <string name="waiting">waiting…</string> + <string name="no_pgp_key">No OpenPGP Key found</string> + <string name="contact_has_no_pgp_key">Conversations is unable to encrypt your messages because your contact is not announcing his or hers public key.\n\n<small>Please ask your contact to setup OpenPGP.</small></string> + <string name="no_pgp_keys">No OpenPGP Keys found</string> + <string name="contacts_have_no_pgp_keys">Conversations is unable to encrypt your messages because your contacts are not announcing their public key.\n\n<small>Please ask your contacts to setup OpenPGP.</small></string> + <string name="encrypted_message_received"><i>Encrypted message received. Touch to decrypt.</i></string> + <string name="pref_general">General</string> + <string name="pref_xmpp_resource">XMPP resource</string> + <string name="pref_xmpp_resource_summary">The name this client identifies itself with</string> + <string name="pref_accept_files">Accept files</string> + <string name="pref_accept_files_summary">Automatically accept files smaller than…</string> <string name="pref_accept_files_size">Size</string> <string name="pref_accept_files_size_summary">Automatically accept files smaller than…</string> <string name="pref_accept_files_download">Wi-Fi only</string> <string name="pref_accept_files_download_summary">Download and accept files automatically only when using Wi-Fi</string> <string name="pref_accept_files_download_link">Image links</string> <string name="pref_accept_files_download_link_summary">Automatically download image links</string> - <string name="pref_notification_settings">Notification Settings</string> - <string name="pref_notifications">Notifications</string> - <string name="pref_notifications_summary">Notify when a new message arrives</string> - <string name="pref_vibrate">Vibrate</string> - <string name="pref_vibrate_summary">Also vibrate when a new message arrives</string> - <string name="pref_sound">Sound</string> - <string name="pref_sound_summary">Play ringtone with notification</string> - <string name="pref_conference_notifications">Conference notifications</string> - <string name="pref_conference_notifications_summary">Always notify when a new conference message arrives instead of only when highlighted</string> - <string name="pref_notification_grace_period">Notification grace period</string> - <string name="pref_notification_grace_period_summary">Disable notifications for a short time after a carbon copy was received</string> - <string name="pref_advanced_options">Advanced Options</string> - <string name="pref_never_send_crash">Never send crash reports</string> - <string name="pref_never_send_crash_summary">By sending in stack traces you are helping the ongoing development of Conversations</string> - <string name="pref_confirm_messages">Confirm Messages</string> - <string name="pref_confirm_messages_summary">Let your contact know when you have received or read a message</string> + <string name="pref_notification_settings">Notification Settings</string> + <string name="pref_notifications">Notifications</string> + <string name="pref_notifications_summary">Notify when a new message arrives</string> + <string name="pref_vibrate">Vibrate</string> + <string name="pref_vibrate_summary">Also vibrate when a new message arrives</string> + <string name="pref_sound">Sound</string> + <string name="pref_sound_summary">Play ringtone with notification</string> + <string name="pref_notification_grace_period">Notification grace period</string> + <string name="pref_notification_grace_period_summary">Disable notifications for a short time after a carbon copy was received</string> + <string name="pref_advanced_options">Advanced Options</string> + <string name="pref_never_send_crash">Never send crash reports</string> + <string name="pref_never_send_crash_summary">By sending in stack traces you are helping the ongoing development of Conversations</string> + <string name="pref_confirm_messages">Confirm Messages</string> + <string name="pref_confirm_messages_summary">Let your contact know when you have received and read a message</string> <string name="pref_confirm_messages_none">No confirmation</string> <string name="pref_confirm_messages_received">Confirmation for received message</string> <string name="pref_confirm_messages_read_and_received">Confirmation for received and read message</string> - <string name="pref_ui_options">UI Options</string> + <string name="pref_ui_options">UI Options</string> <string name="pref_parse_emoticons">Parse Emoticons</string> <string name="pref_parse_emoticons_summary">Replace emoticons with smilies.</string> - <string name="openpgp_error">OpenKeychain reported an error</string> - <string name="error_decrypting_file">I/O Error decrypting file</string> - <string name="accept">Accept</string> - <string name="error">An error has occurred</string> - <string name="pref_grant_presence_updates">Grant presence updates</string> - <string name="pref_grant_presence_updates_summary">Preemptively grant and ask for presence subscription for contacts you created</string> - <string name="subscriptions">Subscriptions</string> - <string name="your_account">Your account</string> - <string name="keys">Keys</string> - <string name="send_presence_updates">Send presence updates</string> - <string name="receive_presence_updates">Receive presence updates</string> - <string name="ask_for_presence_updates">Ask for presence updates</string> - <string name="attach_choose_picture">Choose picture</string> - <string name="attach_take_picture">Take picture</string> - <string name="preemptively_grant">Preemptively grant subscription request</string> - <string name="error_not_an_image_file">The file you selected is not an image</string> - <string name="error_compressing_image">Error while converting the image file</string> - <string name="error_file_not_found">File not found</string> - <string name="error_io_exception">General I/O error. Maybe you ran out of storage space?</string> - <string name="error_security_exception_during_image_copy">The app you used to select this image did not provide us with enough permissions to read the file.\n\n<small>Use a different file manager to choose an image</small></string> - <string name="account_status_unknown">Unknown</string> - <string name="account_status_disabled">Temporarily disabled</string> - <string name="account_status_online">Online</string> - <string name="account_status_connecting">Connecting\u2026</string> - <string name="account_status_offline">Offline</string> - <string name="account_status_unauthorized">Unauthorized</string> - <string name="account_status_not_found">Server not found</string> - <string name="account_status_no_internet">No connectivity</string> - <string name="account_status_regis_fail">Registration failed</string> - <string name="account_status_regis_conflict">Username already in use</string> - <string name="account_status_regis_success">Registration completed</string> - <string name="account_status_regis_not_sup">Server does not support registration</string> - <string name="account_status_security_error">Security error</string> - <string name="account_status_incompatible_server">Incompatible server</string> - <string name="encryption_choice_none">Plain text</string> - <string name="encryption_choice_otr">OTR</string> - <string name="encryption_choice_pgp">OpenPGP</string> - <string name="mgmt_account_edit">Edit account</string> - <string name="mgmt_account_delete">Delete account</string> - <string name="mgmt_account_disable">Temporarily disable</string> - <string name="mgmt_account_publish_avatar">Publish avatar</string> - <string name="mgmt_account_publish_pgp">Publish OpenPGP public key</string> - <string name="mgmt_account_enable">Enable account</string> - <string name="mgmt_account_are_you_sure">Are you sure?</string> - <string name="mgmt_account_delete_confirm_text">If you delete your account your entire conversation history will be lost</string> - <string name="attach_record_voice">Record voice</string> - <string name="account_settings_jabber_id">Jabber ID</string> - <string name="account_settings_password">Password</string> - <string name="account_settings_example_jabber_id">username@example.com</string> - <string name="account_settings_confirm_password">Confirm password</string> - <string name="password">Password</string> - <string name="confirm_password">Confirm password</string> - <string name="passwords_do_not_match">Passwords do not match</string> - <string name="invalid_jid">This is not a valid Jabber ID</string> - <string name="error_out_of_memory">Out of memory. Image is too large</string> - <string name="add_phone_book_text">Do you want to add %s to your phones contact list?</string> - <string name="contact_status_online">online</string> - <string name="contact_status_free_to_chat">free to chat</string> - <string name="contact_status_away">away</string> - <string name="contact_status_extended_away">extended away</string> - <string name="contact_status_do_not_disturb">do not disturb</string> - <string name="contact_status_offline">offline</string> - <string name="muc_details_conference">Conference</string> - <string name="muc_details_other_members">Other Members</string> - <string name="server_info_show_more">Server info</string> - <string name="server_info_mam">XEP-0313: MAM</string> - <string name="server_info_carbon_messages">XEP-0280: Message Carbons</string> - <string name="server_info_csi">XEP-0352: Client State Indication</string> - <string name="server_info_blocking">XEP-0191: Blocking Command</string> - <string name="server_info_roster_version">XEP-0237: Roster Versioning</string> - <string name="server_info_stream_management">XEP-0198: Stream Management</string> - <string name="server_info_pep">XEP-0163: PEP (Avatars)</string> - <string name="server_info_available">available</string> - <string name="server_info_unavailable">unavailable</string> - <string name="missing_public_keys">Missing public key announcements</string> - <string name="last_seen_now">last seen just now</string> - <string name="last_seen_min">last seen 1 minute ago</string> - <string name="last_seen_mins">last seen %d minutes ago</string> - <string name="last_seen_hour">last seen 1 hour ago</string> - <string name="last_seen_hours">last seen %d hours ago</string> - <string name="last_seen_day">last seen 1 day ago</string> - <string name="last_seen_days">last seen %d days ago</string> - <string name="never_seen">never seen</string> - <string name="install_openkeychain">Encrypted message. Please install OpenKeychain to decrypt.</string> - <string name="unknown_otr_fingerprint">Unknown OTR fingerprint</string> - <string name="openpgp_messages_found">OpenPGP encrypted messages found</string> - <string name="reception_failed">Reception failed</string> - <string name="your_fingerprint">Your fingerprint</string> - <string name="otr_fingerprint">OTR fingerprint</string> - <string name="verify">Verify</string> - <string name="decrypt">Decrypt</string> - <string name="conferences">Conferences</string> - <string name="search">Search</string> - <string name="create_contact">Create Contact</string> - <string name="join_conference">Join Conference</string> - <string name="delete_contact">Delete Contact</string> - <string name="view_contact_details">View contact details</string> - <string name="block_contact">Block contact</string> - <string name="unblock_contact">Unblock contact</string> - <string name="create">Create</string> - <string name="contact_already_exists">The contact already exists</string> - <string name="join">Join</string> - <string name="conference_address">Conference address</string> - <string name="conference_address_example">room@conference.example.com</string> - <string name="save_as_bookmark">Save as bookmark</string> - <string name="delete_bookmark">Delete bookmark</string> - <string name="bookmark_already_exists">This bookmark already exists</string> - <string name="you">You</string> - <string name="action_edit_subject">Edit conference subject</string> - <string name="conference_not_found">Conference not found</string> - <string name="leave">Leave</string> - <string name="contact_added_you">Contact added you to contact list</string> - <string name="add_back">Add back</string> - <string name="contact_has_read_up_to_this_point">%s has read up to this point</string> - <string name="publish">Publish</string> - <string name="touch_to_choose_picture">Touch avatar to select picture from gallery</string> - <string name="publish_avatar_explanation">Please note: Everyone subscribed to your presence updates will be allowed to see this picture.</string> - <string name="publishing">Publishing…</string> - <string name="error_publish_avatar_server_reject">The server rejected your publication</string> - <string name="error_publish_avatar_converting">Something went wrong while converting your picture</string> - <string name="error_saving_avatar">Could not save avatar to disk</string> - <string name="or_long_press_for_default">(Or long press to bring back default)</string> - <string name="error_publish_avatar_no_server_support">Your server does not support the publication of avatars</string> - <string name="private_message">whispered</string> - <string name="private_message_to">to %s</string> - <string name="send_private_message_to">Send private message to %s</string> - <string name="connect">Connect</string> - <string name="account_already_exists">This account already exists</string> - <string name="next">Next</string> - <string name="server_info_session_established">Current session established</string> - <string name="additional_information">Additional Information</string> - <string name="skip">Skip</string> - <string name="disable_notifications">Disable notifications</string> - <string name="disable_notifications_for_this_conversation">Disable notifications for this conversation</string> - <string name="notifications_disabled">Notifications are disabled</string> - <string name="enable">Enable</string> - <string name="conference_requires_password">Conference requires password</string> - <string name="enter_password">Enter password</string> - <string name="missing_presence_updates">Missing presence updates from contact</string> - <string name="request_presence_updates">Please request presence updates from your contact first.\n\n<small>This will be used to determine what client(s) your contact is using.</small></string> - <string name="request_now">Request now</string> - <string name="delete_fingerprint">Delete Fingerprint</string> - <string name="sure_delete_fingerprint">Are you sure you would like to delete this fingerprint?</string> - <string name="ignore">Ignore</string> - <string name="without_mutual_presence_updates"><b>Warning:</b> Sending this without mutual presence updates could cause unexpected problems.\n\n<small>Go to contact details to verify your presence subscriptions.</small></string> - <string name="pref_encryption_settings">Encryption settings</string> - <string name="pref_force_encryption">Force end-to-end encryption</string> - <string name="pref_force_encryption_summary">Always send messages encrypted (except for conferences)</string> - <string name="pref_dont_save_encrypted">Don’t save encrypted messages</string> - <string name="pref_dont_save_encrypted_summary">Warning: This could lead to message loss</string> - <string name="pref_expert_options">Expert options</string> - <string name="pref_expert_options_summary">Please be careful with these</string> - <string name="title_activity_about">About Conversations</string> - <string name="pref_about_conversations_summary">Build and licensing information</string> - <string name="pref_about_message" translatable="false"> - Conversations+ is the improved version of Conversations. - \n\nThe extensions are designed and developed by thedevstack.de + <string name="openpgp_error">OpenKeychain reported an error</string> + <string name="error_decrypting_file">I/O Error decrypting file</string> + <string name="accept">Accept</string> + <string name="error">An error has occurred</string> + <string name="pref_grant_presence_updates">Grant presence updates</string> + <string name="pref_grant_presence_updates_summary">Preemptively grant and ask for presence subscription for contacts you created</string> + <string name="subscriptions">Subscriptions</string> + <string name="your_account">Your account</string> + <string name="keys">Keys</string> + <string name="send_presence_updates">Send presence updates</string> + <string name="receive_presence_updates">Receive presence updates</string> + <string name="ask_for_presence_updates">Ask for presence updates</string> + <string name="attach_choose_picture">Choose picture</string> + <string name="attach_take_picture">Take picture</string> + <string name="preemptively_grant">Preemptively grant subscription request</string> + <string name="error_not_an_image_file">The file you selected is not an image</string> + <string name="error_compressing_image">Error while converting the image file</string> + <string name="error_file_not_found">File not found</string> + <string name="error_io_exception">General I/O error. Maybe you ran out of storage space?</string> + <string name="error_security_exception_during_image_copy">The app you used to select this image did not provide us with enough permissions to read the file.\n\n<small>Use a different file manager to choose an image</small></string> + <string name="account_status_unknown">Unknown</string> + <string name="account_status_disabled">Temporarily disabled</string> + <string name="account_status_online">Online</string> + <string name="account_status_connecting">Connecting\u2026</string> + <string name="account_status_offline">Offline</string> + <string name="account_status_unauthorized">Unauthorized</string> + <string name="account_status_not_found">Server not found</string> + <string name="account_status_no_internet">No connectivity</string> + <string name="account_status_regis_fail">Registration failed</string> + <string name="account_status_regis_conflict">Username already in use</string> + <string name="account_status_regis_success">Registration completed</string> + <string name="account_status_regis_not_sup">Server does not support registration</string> + <string name="account_status_security_error">Security error</string> + <string name="account_status_incompatible_server">Incompatible server</string> + <string name="encryption_choice_unencrypted">Unencrypted</string> + <string name="encryption_choice_otr">OTR</string> + <string name="encryption_choice_pgp">OpenPGP</string> + <string name="encryption_choice_omemo">OMEMO</string> + <string name="mgmt_account_edit">Edit account</string> + <string name="mgmt_account_delete">Delete account</string> + <string name="mgmt_account_disable">Temporarily disable</string> + <string name="mgmt_account_publish_avatar">Publish avatar</string> + <string name="mgmt_account_publish_pgp">Publish OpenPGP public key</string> + <string name="mgmt_account_enable">Enable account</string> + <string name="mgmt_account_are_you_sure">Are you sure?</string> + <string name="mgmt_account_delete_confirm_text">If you delete your account your entire conversation history will be lost</string> + <string name="attach_record_voice">Record voice</string> + <string name="account_settings_jabber_id">Jabber ID</string> + <string name="account_settings_password">Password</string> + <string name="account_settings_example_jabber_id">username@example.com</string> + <string name="account_settings_confirm_password">Confirm password</string> + <string name="password">Password</string> + <string name="confirm_password">Confirm password</string> + <string name="passwords_do_not_match">Passwords do not match</string> + <string name="invalid_jid">This is not a valid Jabber ID</string> + <string name="error_out_of_memory">Out of memory. Image is too large</string> + <string name="add_phone_book_text">Do you want to add %s to your address book?</string> + <string name="contact_status_online">online</string> + <string name="contact_status_free_to_chat">free to chat</string> + <string name="contact_status_away">away</string> + <string name="contact_status_extended_away">extended away</string> + <string name="contact_status_do_not_disturb">do not disturb</string> + <string name="contact_status_offline">offline</string> + <string name="muc_details_conference">Conference</string> + <string name="muc_details_other_members">Other Members</string> + <string name="server_info_show_more">Server info</string> + <string name="server_info_mam">XEP-0313: MAM</string> + <string name="server_info_carbon_messages">XEP-0280: Message Carbons</string> + <string name="server_info_csi">XEP-0352: Client State Indication</string> + <string name="server_info_blocking">XEP-0191: Blocking Command</string> + <string name="server_info_roster_version">XEP-0237: Roster Versioning</string> + <string name="server_info_stream_management">XEP-0198: Stream Management</string> + <string name="server_info_pep">XEP-0163: PEP (Avatars / OMEMO)</string> + <string name="server_info_http_upload">XEP-0363: HTTP File Upload</string> + <string name="server_info_available">available</string> + <string name="server_info_unavailable">unavailable</string> + <string name="missing_public_keys">Missing public key announcements</string> + <string name="last_seen_now">last seen just now</string> + <string name="last_seen_min">last seen 1 minute ago</string> + <string name="last_seen_mins">last seen %d minutes ago</string> + <string name="last_seen_hour">last seen 1 hour ago</string> + <string name="last_seen_hours">last seen %d hours ago</string> + <string name="last_seen_day">last seen 1 day ago</string> + <string name="last_seen_days">last seen %d days ago</string> + <string name="never_seen">never seen</string> + <string name="install_openkeychain">Encrypted message. Please install OpenKeychain to decrypt.</string> + <string name="unknown_otr_fingerprint">Unknown OTR fingerprint</string> + <string name="openpgp_messages_found">OpenPGP encrypted messages found</string> + <string name="reception_failed">Reception failed</string> + <string name="your_fingerprint">Your fingerprint</string> + <string name="otr_fingerprint">OTR fingerprint</string> + <string name="omemo_fingerprint">OMEMO fingerprint</string> + <string name="omemo_fingerprint_x509">v\\OMEMO fingerprint</string> + <string name="omemo_fingerprint_selected_message">OMEMO fingerprint of message</string> + <string name="omemo_fingerprint_x509_selected_message">v\\OMEMO fingerprint of message</string> + <string name="this_device_omemo_fingerprint">Own OMEMO fingerprint</string> + <string name="other_devices">Other devices</string> + <string name="trust_omemo_fingerprints">Trust OMEMO Fingerprints</string> + <string name="fetching_keys">Fetching keys…</string> + <string name="done">Done</string> + <string name="verify">Verify</string> + <string name="decrypt">Decrypt</string> + <string name="conferences">Conferences</string> + <string name="search">Search</string> + <string name="create_contact">Create Contact</string> + <string name="enter_contact">Enter Contact</string> + <string name="join_conference">Join Conference</string> + <string name="delete_contact">Delete Contact</string> + <string name="view_contact_details">View contact details</string> + <string name="block_contact">Block contact</string> + <string name="unblock_contact">Unblock contact</string> + <string name="create">Create</string> + <string name="select">Select</string> + <string name="contact_already_exists">The contact already exists</string> + <string name="join">Join</string> + <string name="conference_address">Conference address</string> + <string name="conference_address_example">room@conference.example.com</string> + <string name="save_as_bookmark">Save as bookmark</string> + <string name="delete_bookmark">Delete bookmark</string> + <string name="bookmark_already_exists">This bookmark already exists</string> + <string name="you">You</string> + <string name="action_edit_subject">Edit conference subject</string> + <string name="conference_not_found">Conference not found</string> + <string name="conference_unknown_error">Unknown error received</string>2 + <string name="leave">Leave</string> + <string name="contact_added_you">Contact added you to contact list</string> + <string name="add_back">Add back</string> + <string name="contact_has_read_up_to_this_point">%s has read up to this point</string> + <string name="publish">Publish</string> + <string name="touch_to_choose_picture">Touch avatar to select picture from gallery</string> + <string name="publish_avatar_explanation">Please note: Everyone subscribed to your presence updates will be allowed to see this picture.</string> + <string name="publishing">Publishing…</string> + <string name="error_publish_avatar_server_reject">The server rejected your publication</string> + <string name="error_publish_avatar_converting">Something went wrong while converting your picture</string> + <string name="error_saving_avatar">Could not save avatar to disk</string> + <string name="or_long_press_for_default">(Or long press to bring back default)</string> + <string name="error_publish_avatar_no_server_support">Your server does not support the publication of avatars</string> + <string name="private_message">whispered</string> + <string name="private_message_to">to %s</string> + <string name="send_private_message_to">Send private message to %s</string> + <string name="connect">Connect</string> + <string name="account_already_exists">This account already exists</string> + <string name="next">Next</string> + <string name="server_info_session_established">Current session established</string> + <string name="additional_information">Additional Information</string> + <string name="skip">Skip</string> + <string name="disable_notifications">Disable notifications</string> + <string name="disable_notifications_for_this_conversation">Disable notifications for this conversation</string> + <string name="enable">Enable</string> + <string name="conference_requires_password">Conference requires password</string> + <string name="enter_password">Enter password</string> + <string name="missing_presence_updates">Missing presence updates from contact</string> + <string name="request_presence_updates">Please request presence updates from your contact first.\n\n<small>This will be used to determine what client(s) your contact is using.</small></string> + <string name="request_now">Request now</string> + <string name="delete_fingerprint">Delete Fingerprint</string> + <string name="sure_delete_fingerprint">Are you sure you would like to delete this fingerprint?</string> + <string name="ignore">Ignore</string> + <string name="without_mutual_presence_updates"><b>Warning:</b> Sending this without mutual presence updates could cause unexpected problems.\n\n<small>Go to contact details to verify your presence subscriptions.</small></string> + <string name="pref_encryption_settings">Encryption settings</string> + <string name="pref_force_encryption">Force end-to-end encryption</string> + <string name="pref_force_encryption_summary">Always send messages encrypted (except for conferences)</string> + <string name="pref_dont_save_encrypted">Don’t save encrypted messages</string> + <string name="pref_dont_save_encrypted_summary">Warning: This could lead to message loss</string> + <string name="pref_expert_options">Expert options</string> + <string name="pref_expert_options_summary">Please be careful with these</string> + <string name="title_activity_about">About Conversations</string> + <string name="pref_about_conversations_summary">Build and licensing information</string> + <string name="pref_about_message" translatable="false"> + Conversations • the very last word in instant messaging. + \n\nCopyright © 2014-2016 Daniel Gultsch \n\nThis 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 @@ -296,8 +310,7 @@ GNU General Public License for more details. \n\nYou should have received a copy of the GNU General Public License along with this program. If not, see https://www.gnu.org/licenses - \n\nBased on - \n\nhttps://github.com/siacs/Conversations\n(GPL, Version 3.0) + \n\nDownload the full source code at https://github.com/siacs/Conversations \n\n\nLibraries \n\nhttps://www.bouncycastle.org\n(The MIT License (MIT)) \n\nhttps://www.gnu.org/software/libidn\n(Apache License, Version 2.0) @@ -309,8 +322,11 @@ \n\nhttps://github.com/zxing/zxing\n(Apache License, Version 2.0) \n\nhttps://github.com/google/material-design-icons\n(CC BY 4.0) \n\nhttps://github.com/timroes/EnhancedListView\n(Apache License, Version 2.0) - \n\nhttps://github.com/ankushsachdeva/emojicon\n(Apache License, Version 2.0) - \n\nhttps://github.com/yukuku/ambilwarna\n(Apache License, Version 2.0) + \n\nhttps://github.com/leolin310148/ShortcutBadger\n(Apache License, Version 2.0) + \n\nhttps://github.com/kyleduo/SwitchButton\n(Apache License, Version 2.0) + \n\nhttps://github.com/WhisperSystems/libaxolotl-java\n(GPLv3) + \n\nhttps://github.com/vinc3m1/RoundedImageView\n(Apache License, Version 2.0) + \n\nhttps://github.com/jdamcd/android-crop\n(Apache License, Version 2.0) </string> <string name="title_pref_quiet_hours">Quiet Hours</string> <string name="title_pref_quiet_hours_start_time">Start time</string> @@ -326,13 +342,15 @@ <string name="pref_expert_options_other">Other</string> <string name="pref_conference_name">Conference name</string> <string name="pref_conference_name_summary">Use room’s subject instead of JID to identify conferences</string> + <string name="pref_autojoin">Automatically join conferences</string> + <string name="pref_autojoin_summary">Respect the autojoin flag in conference bookmarks</string> <string name="toast_message_otr_fingerprint">OTR fingerprint copied to clipboard!</string> + <string name="toast_message_omemo_fingerprint">OMEMO fingerprint copied to clipboard!</string> <string name="conference_banned">You are banned from this conference</string> <string name="conference_members_only">This conference is members only</string> <string name="conference_kicked">You have been kicked from this conference</string> <string name="using_account">using account %s</string> <string name="checking_x">Checking %s on HTTP host</string> - <string name="image_file_deleted">The image file has been deleted</string> <string name="not_connected_try_again">You are not connected. Try again later</string> <string name="check_x_filesize">Check %s size</string> <string name="message_options">Message options</string> @@ -351,7 +369,6 @@ <string name="verify_otr">Verify OTR</string> <string name="remote_fingerprint">Remote Fingerprint</string> <string name="scan">scan</string> - <string name="or_touch_phones">(or touch phones)</string> <string name="smp">Socialist Millionaire Protocol</string> <string name="shared_secret_hint">Hint or Question</string> <string name="shared_secret_secret">Shared Secret</string> @@ -368,6 +385,9 @@ <string name="conversations_foreground_service">Conversations</string> <string name="pref_keep_foreground_service">Keep service in foreground</string> <string name="pref_keep_foreground_service_summary">Prevents the operating system from killing your connection</string> + <string name="pref_export_logs">Export Logs</string> + <string name="pref_export_logs_summary">Write logs to SD card</string> + <string name="notification_export_logs_title">Writing logs to SD card</string> <string name="choose_file">Choose file</string> <string name="receiving_x_file">Receiving %1$s (%2$d%% completed)</string> <string name="download_x_file">Download %s</string> @@ -394,6 +414,17 @@ <string name="reset">Reset</string> <string name="account_image_description">Account avatar</string> <string name="copy_otr_clipboard_description">Copy OTR fingerprint to clipboard</string> + <string name="copy_omemo_clipboard_description">Copy OMEMO fingerprint to clipboard</string> + <string name="regenerate_omemo_key">Regenerate OMEMO key</string> + <string name="wipe_omemo_pep">Wipe other devices from PEP</string> + <string name="clear_other_devices">Clear devices</string> + <string name="clear_other_devices_desc">Are you sure you want to clear all other devices from the OMEMO announcement? The next time your devices connect, they will reannounce themselves, but they might not receive messages sent in the meantime.</string> + <string name="purge_key">Purge key</string> + <string name="purge_key_desc_part1">Are you sure you want to purge this key?</string> + <string name="purge_key_desc_part2">It will irreversibly be considered compromised, and you can never build a session with it again.</string> + <string name="error_no_keys_to_trust_server_error">There are no usable keys available for this contact.\nFetching new keys from the server has been unsuccessful. Maybe there is something wrong with your contacts server.</string> + <string name="error_no_keys_to_trust">There are no usable keys available for this contact. If you have purged any of their keys, they need to generate new ones.</string> + <string name="error_trustkeys_title">Error</string> <string name="fetching_history_from_server">Fetching history from server</string> <string name="no_more_history_on_server">No more history on server</string> <string name="updating">Updating…</string> @@ -432,8 +463,10 @@ <string name="public_conference">Publicly accessible conference</string> <string name="private_conference">Private, members only conference</string> <string name="conference_options">Conference options</string> - <string name="members_only">Private (Members only)</string> + <string name="members_only">Private, members only</string> <string name="non_anonymous">Non-anonymous</string> + <string name="moderated">Moderated</string> + <string name="you_are_not_participating">You are not participating</string> <string name="modified_conference_options">Modified conference options!</string> <string name="could_not_modify_conference_options">Could not modify conference options</string> <string name="never">Never</string> @@ -461,7 +494,7 @@ <string name="offering_x_file">Offering %s</string> <string name="hide_offline">Hide offline</string> <string name="disable_account">Disable Account</string> - <string name="contact_is_typing">%s is typing...</string> + <string name="contact_is_typing">%s is typing…</string> <string name="contact_has_stopped_typing">%s has stopped typing</string> <string name="pref_chat_states">Typing notifications</string> <string name="pref_chat_states_summary">Let your contact know when you are writing a new message</string> @@ -472,7 +505,6 @@ <string name="received_location">Received location</string> <string name="title_undo_swipe_out_conversation">Conversation closed</string> <string name="title_undo_swipe_out_muc">Left conference</string> - <string name="pref_certificate_options">Certificate options</string> <string name="pref_dont_trust_system_cas_title">Don’t trust system CAs</string> <string name="pref_dont_trust_system_cas_summary">All certificates must be manually approved</string> <string name="pref_remove_trusted_certificates_title">Remove certificates</string> @@ -494,7 +526,6 @@ <string name="none">None</string> <string name="recently_used">Most recently used</string> <string name="choose_quick_action">Choose quick action</string> - <string name="file_not_found_on_remote_host">File not found on remote server</string> <string name="search_for_contacts_or_groups">Search for contacts or groups</string> <string name="pref_led_notification_color">LED notification color</string> <string name="pref_led_notification_color_summary">Change the color of the LED notification</string> @@ -545,4 +576,77 @@ <string name="pref_file_transfer_category">File Transfer</string> <string name="cplus_not_copied_to_clipboard_empty">Nothing to copy.</string> <string name="cplus_me">Me</string> + <string name="send_private_message">Send private message</string> + <string name="user_has_left_conference">%s has left the conference!</string> + <string name="username">Username</string> + <string name="username_hint">Username</string> + <string name="invalid_username">This is not a valid username</string> + <string name="download_failed_server_not_found">Download failed: Server not found</string> + <string name="download_failed_file_not_found">Download failed: File not found</string> + <string name="download_failed_could_not_connect">Download failed: Could not connect to host</string> + <string name="pref_use_white_background">Use white background</string> + <string name="pref_use_white_background_summary">Show received messages as black text on a white background</string> + <string name="account_status_tor_unavailable">Tor network unavailable</string> + <string name="server_info_broken">Broken</string> + <string name="pref_presence_settings">Presence settings</string> + <string name="pref_away_when_screen_off">Away when screen is off</string> + <string name="pref_away_when_screen_off_summary">Marks your resource as away when the screen is turned off</string> + <string name="pref_xa_on_silent_mode">Not available in silent mode</string> + <string name="pref_xa_on_silent_mode_summary">Marks your resource as not available when device is in silent mode</string> + <string name="pref_show_connection_options">Extended connection options</string> + <string name="pref_show_connection_options_summary">Show hostname and port options when setting up an account</string> + <string name="hostname_example">xmpp.example.com</string> + <string name="action_add_account_with_certificate">Add account with certificate</string> + <string name="unable_to_parse_certificate">Unable to parse certificate</string> + <string name="authenticate_with_certificate">Leave empty to authenticate w/ certificate</string> + <string name="captcha_ocr">Captcha text</string> + <string name="captcha_required">Captcha required</string> + <string name="captcha_hint">enter the text from the image</string> + <string name="certificate_chain_is_not_trusted">Certificate chain is not trusted</string> + <string name="jid_does_not_match_certificate">Jabber ID does not match certificate</string> + <string name="action_renew_certificate">Renew certificate</string> + <string name="error_fetching_omemo_key">Error fetching OMEMO key!</string> + <string name="verified_omemo_key_with_certificate">Verified OMEMO key with certificate!</string> + <string name="device_does_not_support_certificates">Your device does not support the selection of client certificates!</string> + <string name="pref_connection_options">Connection options</string> + <string name="pref_use_tor">Connect via Tor</string> + <string name="pref_use_tor_summary">Tunnel all connections through the Tor network. Requires Orbot</string> + <string name="account_settings_hostname">Hostname</string> + <string name="account_settings_port">Port</string> + <string name="hostname_or_onion">Server- or .onion-Address</string> + <string name="not_a_valid_port">This is not a valid port number</string> + <string name="not_valid_hostname">This is not a valid hostname</string> + <string name="connected_accounts">%1$d of %2$d accounts connected</string> + <plurals name="x_messages"> + <item quantity="one">%d message</item> + <item quantity="other">%d messages</item> + </plurals> + <string name="shared_file_with_x">Shared file with %s</string> + <string name="shared_image_with_x">Shared image with %s</string> + <string name="no_storage_permission">Conversations need access to external storage</string> + <string name="sync_with_contacts">Synchronize with contacts</string> + <string name="sync_with_contacts_long">Conversations wants to match your XMPP roster with your contacts to show their full names and avatars.\n\nConversations will only read your contacts and match them locally without uploading them to your server.\n\nYou will now be asked to grant permission to access your contacts.</string> + <string name="certificate_information">Certificate Information</string> + <string name="certificate_subject">Subject</string> + <string name="certificate_issuer">Issuer</string> + <string name="certificate_cn">Common Name</string> + <string name="certificate_o">Organization</string> + <string name="certificate_sha1">SHA1</string> + <string name="certicate_info_not_available">(Not available)</string> + <string name="certificate_not_found">No certificate found</string> + <string name="notify_on_all_messages">Notify on all messages</string> + <string name="notify_only_when_highlighted">Notify only when highlighted</string> + <string name="notify_never">Notifications disabled</string> + <string name="notify_paused">Notifications paused</string> + <string name="pref_picture_compression">Compress Pictures</string> + <string name="pref_picture_compression_summary">Resize and compressed pictures</string> + <string name="always">Always</string> + <string name="automatically">Automatically</string> + <string name="battery_optimizations_enabled">Battery optimizations enabled</string> + <string name="battery_optimizations_enabled_explained">Your device is doing some heavy battery optimizations on Conversations that might lead to delayed notifications or even message loss.\nIt is recommended to disable those.</string> + <string name="battery_optimizations_enabled_dialog">Your device is doing some heavy battery optimizations on Conversations that might lead to delayed notifications or even message loss.\n\nYou will now be asked to disable those.</string> + <string name="disable">Disable</string> + <string name="selection_too_large">The selected area is too large</string> + <string name="no_accounts">(No activated accounts)</string> + <string name="this_field_is_required">This field is required</string> </resources> diff --git a/src/main/res/values/styles.xml b/src/main/res/values/styles.xml index b98a37fc..e8572d9d 100644 --- a/src/main/res/values/styles.xml +++ b/src/main/res/values/styles.xml @@ -4,8 +4,18 @@ <item name="android:layout_height">1.5dp</item> <item name="android:background">@color/black12</item> </style> - <style name="Tag"> - + <style name="MD"> + <item name="animationVelocity">6</item> + <item name="insetBottom">16dp</item> + <item name="insetTop">16dp</item> + <item name="insetLeft">16dp</item> + <item name="insetRight">16dp</item> + <item name="measureFactor">1.4</item> + <item name="offDrawable">@drawable/switch_back_off</item> + <item name="onDrawable">@drawable/switch_back_on</item> + <item name="thumbDrawable">@drawable/switch_thumb</item> + <item name="thumb_margin">-17dp</item> + <item name="android:padding">16dp</item> </style> </resources>
\ No newline at end of file diff --git a/src/main/res/values/themes.xml b/src/main/res/values/themes.xml index 5c67203b..0f8b95bb 100644 --- a/src/main/res/values/themes.xml +++ b/src/main/res/values/themes.xml @@ -18,6 +18,7 @@ <item name="attr/icon_download">@drawable/ic_action_download</item> <item name="attr/icon_edit">@drawable/ic_action_edit</item> <item name="attr/icon_edit_dark">@drawable/ic_action_edit_dark</item> + <item name="attr/icon_done">@drawable/ic_action_done</item> <item name="attr/icon_group">@drawable/ic_action_group</item> <item name="attr/icon_new">@drawable/ic_action_new</item> @@ -38,8 +39,8 @@ </style> <style name="ConversationsActionBar" parent="@android:style/Widget.Holo.Light.ActionBar.Solid.Inverse"> - <item name="android:background">@color/green500</item> - <item name="android:backgroundStacked">@color/green700</item> + <item name="android:background">@color/primary</item> + <item name="android:backgroundStacked">@color/primary_dark</item> <item name="android:displayOptions">showHome|homeAsUp|showTitle</item> <item name="android:icon">@android:color/transparent</item> </style> diff --git a/src/main/res/xml/preferences.xml b/src/main/res/xml/preferences.xml index 26ab27bd..ea685c14 100644 --- a/src/main/res/xml/preferences.xml +++ b/src/main/res/xml/preferences.xml @@ -2,12 +2,12 @@ <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" > - <PreferenceCategory android:title="@string/pref_general" > + <PreferenceCategory android:title="@string/pref_general"> <CheckBoxPreference android:defaultValue="true" android:key="grant_new_contacts" android:summary="@string/pref_grant_presence_updates_summary" - android:title="@string/pref_grant_presence_updates" /> + android:title="@string/pref_grant_presence_updates"/> <ListPreference android:defaultValue="Mobile" @@ -15,8 +15,14 @@ android:entryValues="@array/resources" android:key="resource" android:summary="@string/pref_xmpp_resource_summary" - android:title="@string/pref_xmpp_resource" /> - + android:title="@string/pref_xmpp_resource"/> + <ListPreference + android:defaultValue="auto" + android:entries="@array/picture_compression_entries" + android:entryValues="@array/picture_compression_values" + android:key="picture_compression" + android:summary="@string/pref_picture_compression_summary" + android:title="@string/pref_picture_compression"/> <ListPreference android:defaultValue="2" android:entries="@array/confirm_strings" @@ -29,7 +35,7 @@ android:defaultValue="false" android:key="chat_states" android:summary="@string/pref_chat_states_summary" - android:title="@string/pref_chat_states" /> + android:title="@string/pref_chat_states"/> <CheckBoxPreference android:defaultValue="true" android:key="parse_emoticons" @@ -92,6 +98,7 @@ android:title="@string/pref_notifications" /> <PreferenceScreen android:dependency="show_notification" + android:key="quiet_hours" android:summary="@string/pref_quiet_hours_summary" android:title="@string/title_pref_quiet_hours"> <CheckBoxPreference @@ -142,89 +149,123 @@ android:title="@string/pref_conference_notifications" /> </PreferenceScreen> </PreferenceCategory> - <PreferenceCategory android:title="@string/pref_ui_options" > + <PreferenceCategory android:title="@string/pref_ui_options"> <CheckBoxPreference android:defaultValue="true" android:key="use_subject" android:summary="@string/pref_conference_name_summary" - android:title="@string/pref_conference_name" /> + android:title="@string/pref_conference_name"/> + <CheckBoxPreference + android:defaultValue="false" + android:key="use_white_background" + android:summary="@string/pref_use_white_background_summary" + android:title="@string/pref_use_white_background"/> <CheckBoxPreference android:defaultValue="false" android:key="use_larger_font" android:summary="@string/pref_use_larger_font_summary" - android:title="@string/pref_use_larger_font" /> + android:title="@string/pref_use_larger_font"/> <CheckBoxPreference android:defaultValue="false" android:key="send_button_status" android:summary="@string/pref_use_send_button_to_indicate_status_summary" - android:title="@string/pref_use_send_button_to_indicate_status" /> + android:title="@string/pref_use_send_button_to_indicate_status"/> <ListPreference - android:key="quick_action" android:defaultValue="recent" + android:dialogTitle="@string/choose_quick_action" android:entries="@array/quick_actions" android:entryValues="@array/quick_action_values" + android:key="quick_action" android:summary="@string/pref_quick_action_summary" - android:title="@string/pref_quick_action" - android:dialogTitle="@string/choose_quick_action"/> + android:title="@string/pref_quick_action"/> <CheckBoxPreference android:defaultValue="false" android:key="show_dynamic_tags" android:summary="@string/pref_show_dynamic_tags_summary" - android:title="@string/pref_show_dynamic_tags" /> + android:title="@string/pref_show_dynamic_tags"/> </PreferenceCategory> <PreferenceCategory - android:title="@string/pref_advanced_options" - android:key="advanced"> + android:key="advanced" + android:title="@string/pref_advanced_options"> <PreferenceScreen + android:key="expert" android:summary="@string/pref_expert_options_summary" - android:title="@string/pref_expert_options" - android:key="expert"> - <PreferenceCategory android:title="@string/pref_encryption_settings" > - <CheckBoxPreference - android:defaultValue="false" - android:key="force_encryption" - android:summary="@string/pref_force_encryption_summary" - android:title="@string/pref_force_encryption" /> + android:title="@string/pref_expert_options"> + <PreferenceCategory android:title="@string/pref_encryption_settings"> <CheckBoxPreference android:defaultValue="false" android:key="dont_save_encrypted" android:summary="@string/pref_dont_save_encrypted_summary" - android:title="@string/pref_dont_save_encrypted" /> + android:title="@string/pref_dont_save_encrypted"/> + <CheckBoxPreference + android:defaultValue="false" + android:key="dont_trust_system_cas" + android:summary="@string/pref_dont_trust_system_cas_summary" + android:title="@string/pref_dont_trust_system_cas_title"/> + <Preference + android:key="remove_trusted_certificates" + android:summary="@string/pref_remove_trusted_certificates_summary" + android:title="@string/pref_remove_trusted_certificates_title"/> + </PreferenceCategory> + <PreferenceCategory + android:key="connection_options" + android:title="@string/pref_connection_options"> + <CheckBoxPreference + android:defaultValue="false" + android:key="use_tor" + android:summary="@string/pref_use_tor_summary" + android:title="@string/pref_use_tor"/> + <CheckBoxPreference + android:defaultValue="false" + android:key="show_connection_options" + android:summary="@string/pref_show_connection_options_summary" + android:title="@string/pref_show_connection_options"/> + </PreferenceCategory> + <PreferenceCategory android:title="@string/pref_input_options"> + <CheckBoxPreference + android:defaultValue="false" + android:key="enter_is_send" + android:summary="@string/pref_enter_is_send_summary" + android:title="@string/pref_enter_is_send"/> + <CheckBoxPreference + android:defaultValue="false" + android:key="display_enter_key" + android:summary="@string/pref_display_enter_key_summary" + android:title="@string/pref_display_enter_key"/> </PreferenceCategory> - <PreferenceCategory android:title="@string/pref_input_options"> - <CheckBoxPreference - android:defaultValue="false" - android:key="enter_is_send" - android:title="@string/pref_enter_is_send" - android:summary="@string/pref_enter_is_send_summary" /> - <CheckBoxPreference - android:defaultValue="false" - android:key="display_enter_key" - android:title="@string/pref_display_enter_key" - android:summary="@string/pref_display_enter_key_summary" /> - </PreferenceCategory> - <PreferenceCategory android:title="@string/pref_certificate_options"> - <CheckBoxPreference - android:defaultValue="false" - android:key="dont_trust_system_cas" - android:title="@string/pref_dont_trust_system_cas_title" - android:summary="@string/pref_dont_trust_system_cas_summary" /> - <Preference - android:key="remove_trusted_certificates" - android:title="@string/pref_remove_trusted_certificates_title" - android:summary="@string/pref_remove_trusted_certificates_summary" /> - </PreferenceCategory> - <PreferenceCategory android:title="@string/pref_expert_options_other" > + <PreferenceCategory android:title="@string/pref_presence_settings"> + <CheckBoxPreference + android:defaultValue="false" + android:key="away_when_screen_off" + android:summary="@string/pref_away_when_screen_off_summary" + android:title="@string/pref_away_when_screen_off"/> + <CheckBoxPreference + android:defaultValue="false" + android:key="xa_on_silent_mode" + android:summary="@string/pref_xa_on_silent_mode_summary" + android:title="@string/pref_xa_on_silent_mode"/> + </PreferenceCategory> + <PreferenceCategory android:title="@string/pref_expert_options_other"> + <CheckBoxPreference + android:key="autojoin" + android:defaultValue="true" + android:title="@string/pref_autojoin" + android:summary="@string/pref_autojoin_summary" + /> <CheckBoxPreference android:defaultValue="false" android:key="indicate_received" android:summary="@string/pref_use_indicate_received_summary" - android:title="@string/pref_use_indicate_received" /> + android:title="@string/pref_use_indicate_received"/> <CheckBoxPreference android:defaultValue="false" android:key="keep_foreground_service" - android:title="@string/pref_keep_foreground_service" - android:summary="@string/pref_keep_foreground_service_summary" /> + android:summary="@string/pref_keep_foreground_service_summary" + android:title="@string/pref_keep_foreground_service"/> + <eu.siacs.conversations.ui.ExportLogsPreference + android:key="export_logs" + android:summary="@string/pref_export_logs_summary" + android:title="@string/pref_export_logs"/> <de.thedevstack.conversationsplus.ui.preferences.LogInformationPreference android:summary="@string/pref_show_logcat_summary" android:title="@string/pref_show_logcat_title"/> @@ -235,9 +276,9 @@ android:defaultValue="false" android:key="never_send" android:summary="@string/pref_never_send_crash_summary" - android:title="@string/pref_never_send_crash" /> + android:title="@string/pref_never_send_crash"/> </PreferenceCategory> - <eu.siacs.conversations.ui.AboutPreference - android:summary="@string/pref_about_conversations_summary" - android:title="@string/title_activity_about" /> + <eu.siacs.conversations.ui.AboutPreference + android:summary="@string/pref_about_conversations_summary" + android:title="@string/title_activity_about"/> </PreferenceScreen> |