diff options
118 files changed, 6492 insertions, 255 deletions
diff --git a/Graphics/drawables/linked_dns.svg b/Graphics/drawables/linked_dns.svg new file mode 100644 index 000000000..c04c265c2 --- /dev/null +++ b/Graphics/drawables/linked_dns.svg @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg viewBox="0 0 24 24"> + <path fill="#000000" d="M7,9A2,2 0 0,1 5,7A2,2 0 0,1 7,5A2,2 0 0,1 9,7A2,2 0 0,1 7,9M20,3H4A1,1 0 0,0 3,4V10A1,1 0 0,0 4,11H20A1,1 0 0,0 21,10V4A1,1 0 0,0 20,3M7,19A2,2 0 0,1 5,17A2,2 0 0,1 7,15A2,2 0 0,1 9,17A2,2 0 0,1 7,19M20,13H4A1,1 0 0,0 3,14V20A1,1 0 0,0 4,21H20A1,1 0 0,0 21,20V14A1,1 0 0,0 20,13Z" /> +</svg> diff --git a/Graphics/drawables/linked_github.svg b/Graphics/drawables/linked_github.svg new file mode 100644 index 000000000..11f4076d5 --- /dev/null +++ b/Graphics/drawables/linked_github.svg @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg viewBox="0 0 24 24"> + <path fill="#000000" d="M12,2A10,10 0 0,0 2,12C2,16.42 4.87,20.17 8.84,21.5C9.34,21.58 9.5,21.27 9.5,21C9.5,20.77 9.5,20.14 9.5,19.31C6.73,19.91 6.14,17.97 6.14,17.97C5.68,16.81 5.03,16.5 5.03,16.5C4.12,15.88 5.1,15.9 5.1,15.9C6.1,15.97 6.63,16.93 6.63,16.93C7.5,18.45 8.97,18 9.54,17.76C9.63,17.11 9.89,16.67 10.17,16.42C7.95,16.17 5.62,15.31 5.62,11.5C5.62,10.39 6,9.5 6.65,8.79C6.55,8.54 6.2,7.5 6.75,6.15C6.75,6.15 7.59,5.88 9.5,7.17C10.29,6.95 11.15,6.84 12,6.84C12.85,6.84 13.71,6.95 14.5,7.17C16.41,5.88 17.25,6.15 17.25,6.15C17.8,7.5 17.45,8.54 17.35,8.79C18,9.5 18.38,10.39 18.38,11.5C18.38,15.32 16.04,16.16 13.81,16.41C14.17,16.72 14.5,17.33 14.5,18.26C14.5,19.6 14.5,20.68 14.5,21C14.5,21.27 14.66,21.59 15.17,21.5C19.14,20.16 22,16.42 22,12A10,10 0 0,0 12,2Z" /> +</svg> diff --git a/Graphics/drawables/linked_https.svg b/Graphics/drawables/linked_https.svg new file mode 100644 index 000000000..876f46276 --- /dev/null +++ b/Graphics/drawables/linked_https.svg @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg viewBox="0 0 24 24"> + <path fill="#000000" d="M16.36,14C16.44,13.34 16.5,12.68 16.5,12C16.5,11.32 16.44,10.66 16.36,10H19.74C19.9,10.64 20,11.31 20,12C20,12.69 19.9,13.36 19.74,14M14.59,19.56C15.19,18.45 15.65,17.25 15.97,16H18.92C17.96,17.65 16.43,18.93 14.59,19.56M14.34,14H9.66C9.56,13.34 9.5,12.68 9.5,12C9.5,11.32 9.56,10.65 9.66,10H14.34C14.43,10.65 14.5,11.32 14.5,12C14.5,12.68 14.43,13.34 14.34,14M12,19.96C11.17,18.76 10.5,17.43 10.09,16H13.91C13.5,17.43 12.83,18.76 12,19.96M8,8H5.08C6.03,6.34 7.57,5.06 9.4,4.44C8.8,5.55 8.35,6.75 8,8M5.08,16H8C8.35,17.25 8.8,18.45 9.4,19.56C7.57,18.93 6.03,17.65 5.08,16M4.26,14C4.1,13.36 4,12.69 4,12C4,11.31 4.1,10.64 4.26,10H7.64C7.56,10.66 7.5,11.32 7.5,12C7.5,12.68 7.56,13.34 7.64,14M12,4.03C12.83,5.23 13.5,6.57 13.91,8H10.09C10.5,6.57 11.17,5.23 12,4.03M18.92,8H15.97C15.65,6.75 15.19,5.55 14.59,4.44C16.43,5.07 17.96,6.34 18.92,8M12,2C6.47,2 2,6.5 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z" /> +</svg> diff --git a/Graphics/drawables/linked_twitter.svg b/Graphics/drawables/linked_twitter.svg new file mode 100644 index 000000000..38028f2e9 --- /dev/null +++ b/Graphics/drawables/linked_twitter.svg @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg viewBox="0 0 24 24"> + <path fill="#000000" d="M22.46,6C21.69,6.35 20.86,6.58 20,6.69C20.88,6.16 21.56,5.32 21.88,4.31C21.05,4.81 20.13,5.16 19.16,5.36C18.37,4.5 17.26,4 16,4C13.65,4 11.73,5.92 11.73,8.29C11.73,8.63 11.77,8.96 11.84,9.27C8.28,9.09 5.11,7.38 3,4.79C2.63,5.42 2.42,6.16 2.42,6.94C2.42,8.43 3.17,9.75 4.33,10.5C3.62,10.5 2.96,10.3 2.38,10C2.38,10 2.38,10 2.38,10.03C2.38,12.11 3.86,13.85 5.82,14.24C5.46,14.34 5.08,14.39 4.69,14.39C4.42,14.39 4.15,14.36 3.89,14.31C4.43,16 6,17.26 7.89,17.29C6.43,18.45 4.58,19.13 2.56,19.13C2.22,19.13 1.88,19.11 1.54,19.07C3.44,20.29 5.7,21 8.12,21C16,21 20.33,14.46 20.33,8.79C20.33,8.6 20.33,8.42 20.32,8.23C21.16,7.63 21.88,6.87 22.46,6Z" /> +</svg> diff --git a/Graphics/drawables/status_signature_verified_inner.svg b/Graphics/drawables/status_signature_verified_inner.svg new file mode 100644 index 000000000..554df0643 --- /dev/null +++ b/Graphics/drawables/status_signature_verified_inner.svg @@ -0,0 +1,77 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:sketch="http://www.bohemiancoding.com/sketch/ns" + 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="100px" + height="100px" + viewBox="0 0 100 100" + version="1.1" + id="svg2" + inkscape:version="0.48.5 r10040" + sodipodi:docname="status_signature_verified_cutout.svg"> + <metadata + id="metadata16"> + <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>signature-verified-cutout</dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1280" + inkscape:window-height="784" + id="namedview14" + showgrid="false" + inkscape:zoom="2.36" + inkscape:cx="50" + inkscape:cy="50" + inkscape:window-x="0" + inkscape:window-y="16" + inkscape:window-maximized="0" + inkscape:current-layer="signature-verified-cutout" /> + <!-- Generator: Sketch 3.0.4 (8053) - http://www.bohemiancoding.com/sketch --> + <title + id="title4">signature-verified-cutout</title> + <desc + id="desc6">Created with Sketch.</desc> + <defs + id="defs8" /> + <g + id="Page-1" + sketch:type="MSPage" + stroke-width="1" + stroke="none" + fill-rule="evenodd" + fill="none"> + <g + id="signature-verified-cutout" + sketch:type="MSArtboardGroup" + transform="translate(0.110156, 0.000000)" + fill="#000000"> + <path + d="M 46.273291,77.5085 20,57.830916 27.91844,47.63497 43.309686,59.515226 70.31112,23 80.867825,30.778219 z" + sketch:type="MSShapeGroup" + id="path12" + inkscape:connector-curvature="0" + sodipodi:nodetypes="ccccccc" /> + </g> + </g> +</svg> diff --git a/Graphics/update-drawables.sh b/Graphics/update-drawables.sh index 61dc51099..fc6cb70bf 100755 --- a/Graphics/update-drawables.sh +++ b/Graphics/update-drawables.sh @@ -22,7 +22,7 @@ SRC_DIR=./drawables/ #inkscape -w 512 -h 512 -e "$PLAY_DIR/$NAME.png" $NAME.svg -for NAME in "ic_cloud_search" "ic_action_encrypt_file" "ic_action_encrypt_text" "ic_action_verified_cutout" "ic_action_encrypt_copy" "ic_action_encrypt_save" "ic_action_encrypt_share" "status_lock_closed" "status_lock_error" "status_lock_open" "status_signature_expired_cutout" "status_signature_invalid_cutout" "status_signature_revoked_cutout" "status_signature_unknown_cutout" "status_signature_unverified_cutout" "status_signature_verified_cutout" "key_flag_authenticate" "key_flag_certify" "key_flag_encrypt" "key_flag_sign" "yubi_icon" +for NAME in "ic_cloud_search" "ic_action_encrypt_file" "ic_action_encrypt_text" "ic_action_verified_cutout" "ic_action_encrypt_copy" "ic_action_encrypt_save" "ic_action_encrypt_share" "status_lock_closed" "status_lock_error" "status_lock_open" "status_signature_expired_cutout" "status_signature_invalid_cutout" "status_signature_revoked_cutout" "status_signature_unknown_cutout" "status_signature_unverified_cutout" "status_signature_verified_cutout" "key_flag_authenticate" "key_flag_certify" "key_flag_encrypt" "key_flag_sign" "yubi_icon" "status_signature_verified_inner" do echo $NAME inkscape -w 24 -h 24 -e "$MDPI_DIR/${NAME}_24dp.png" "$SRC_DIR/$NAME.svg" @@ -32,7 +32,7 @@ inkscape -w 72 -h 72 -e "$XXDPI_DIR/${NAME}_24dp.png" "$SRC_DIR/$NAME.svg" inkscape -w 96 -h 96 -e "$XXXDPI_DIR/${NAME}_24dp.png" "$SRC_DIR/$NAME.svg" done -for NAME in "status_signature_expired_cutout" "status_signature_invalid_cutout" "status_signature_revoked_cutout" "status_signature_unknown_cutout" "status_signature_unverified_cutout" "status_signature_verified_cutout" +for NAME in "status_signature_expired_cutout" "status_signature_invalid_cutout" "status_signature_revoked_cutout" "status_signature_unknown_cutout" "status_signature_unverified_cutout" "status_signature_verified_cutout" "status_signature_verified_inner" do echo $NAME inkscape -w 96 -h 96 -e "$MDPI_DIR/${NAME}_96dp.png" "$SRC_DIR/$NAME.svg" @@ -41,7 +41,7 @@ inkscape -w 192 -h 192 -e "$XDPI_DIR/${NAME}_96dp.png" "$SRC_DIR/$NAME.svg" inkscape -w 256 -h 256 -e "$XXDPI_DIR/${NAME}_96dp.png" "$SRC_DIR/$NAME.svg" done -for NAME in "create_key_robot" +for NAME in "create_key_robot" "linked_dns" "linked_https" "linked_github" "linked_twitter" do echo $NAME inkscape -w 48 -h 48 -e "$MDPI_DIR/$NAME.png" "$SRC_DIR/$NAME.svg" diff --git a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/operations/CertifyOperationTest.java b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/operations/CertifyOperationTest.java index 130b86908..37f46604f 100644 --- a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/operations/CertifyOperationTest.java +++ b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/operations/CertifyOperationTest.java @@ -165,7 +165,7 @@ public class CertifyOperationTest { CertifyActionsParcel actions = new CertifyActionsParcel(mStaticRing1.getMasterKeyId()); actions.add(new CertifyAction(mStaticRing2.getMasterKeyId(), - mStaticRing2.getPublicKey().getUnorderedUserIds())); + mStaticRing2.getPublicKey().getUnorderedUserIds(), null)); CertifyResult result = op.certify(actions, new CryptoInputParcel(mKeyPhrase1), null); Assert.assertTrue("certification must succeed", result.success()); @@ -215,7 +215,7 @@ public class CertifyOperationTest { CertifyActionsParcel actions = new CertifyActionsParcel(mStaticRing1.getMasterKeyId()); actions.add(new CertifyAction(mStaticRing1.getMasterKeyId(), - mStaticRing2.getPublicKey().getUnorderedUserIds())); + mStaticRing2.getPublicKey().getUnorderedUserIds(), null)); CertifyResult result = op.certify(actions, new CryptoInputParcel(mKeyPhrase1), null); @@ -234,7 +234,7 @@ public class CertifyOperationTest { CertifyActionsParcel actions = new CertifyActionsParcel(mStaticRing1.getMasterKeyId()); ArrayList<String> uids = new ArrayList<String>(); uids.add("nonexistent"); - actions.add(new CertifyAction(1234L, uids)); + actions.add(new CertifyAction(1234L, uids, null)); CertifyResult result = op.certify(actions, new CryptoInputParcel(mKeyPhrase1), null); @@ -246,7 +246,7 @@ public class CertifyOperationTest { { CertifyActionsParcel actions = new CertifyActionsParcel(1234L); actions.add(new CertifyAction(mStaticRing1.getMasterKeyId(), - mStaticRing2.getPublicKey().getUnorderedUserIds())); + mStaticRing2.getPublicKey().getUnorderedUserIds(), null)); CertifyResult result = op.certify(actions, new CryptoInputParcel(mKeyPhrase1), null); diff --git a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringMergeTest.java b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringMergeTest.java index 755a0b00d..6d855ea93 100644 --- a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringMergeTest.java +++ b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringMergeTest.java @@ -300,7 +300,7 @@ public class UncachedKeyringMergeTest { ringB.getEncoded(), false, 0).getSecretKey(); secretKey.unlock(new Passphrase()); PgpCertifyOperation op = new PgpCertifyOperation(); - CertifyAction action = new CertifyAction(pubRing.getMasterKeyId(), publicRing.getPublicKey().getUnorderedUserIds()); + CertifyAction action = new CertifyAction(pubRing.getMasterKeyId(), publicRing.getPublicKey().getUnorderedUserIds(), null); // sign all user ids PgpCertifyResult result = op.certify(secretKey, publicRing, new OperationLog(), 0, action, null, new Date()); Assert.assertTrue("certification must succeed", result.success()); diff --git a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/util/KeyFormattingUtilsTest.java b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/util/KeyFormattingUtilsTest.java new file mode 100644 index 000000000..6aa4e7b8e --- /dev/null +++ b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/util/KeyFormattingUtilsTest.java @@ -0,0 +1,44 @@ +package org.sufficientlysecure.keychain.util; + + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; + + +@RunWith(RobolectricTestRunner.class) +@org.robolectric.annotation.Config(emulateSdk = 18) // Robolectric doesn't yet support 19 +public class KeyFormattingUtilsTest { + + static final byte[] fp = new byte[] { + (byte) 0xD4, (byte) 0xAB, (byte) 0x19, (byte) 0x29, (byte) 0x64, + (byte) 0xF7, (byte) 0x6A, (byte) 0x7F, (byte) 0x8F, (byte) 0x8A, + (byte) 0x9B, (byte) 0x35, (byte) 0x7B, (byte) 0xD1, (byte) 0x83, + (byte) 0x20, (byte) 0xDE, (byte) 0xAD, (byte) 0xFA, (byte) 0x11 + }; + static final long keyId = 0x7bd18320deadfa11L; + + @Test + public void testStuff() { + Assert.assertEquals(KeyFormattingUtils.convertFingerprintToKeyId(fp), keyId); + + Assert.assertEquals( + "d4ab192964f76a7f8f8a9b357bd18320deadfa11", + KeyFormattingUtils.convertFingerprintToHex(fp) + ); + + Assert.assertEquals( + "0x7bd18320deadfa11", + KeyFormattingUtils.convertKeyIdToHex(keyId) + ); + + Assert.assertEquals( + "0xdeadfa11", + KeyFormattingUtils.convertKeyIdToHexShort(keyId) + ); + + } + +} diff --git a/OpenKeychain/build.gradle b/OpenKeychain/build.gradle index a4caa1fe7..48fc38ca7 100644 --- a/OpenKeychain/build.gradle +++ b/OpenKeychain/build.gradle @@ -31,6 +31,7 @@ dependencies { compile 'com.jpardogo.materialtabstrip:library:1.0.9' compile 'com.getbase:floatingactionbutton:1.9.0' compile 'org.commonjava.googlecode.markdown4j:markdown4j:2.2-cj-1.0' + compile 'org.ocpsoft.prettytime:prettytime:3.2.7.Final' compile "com.splitwise:tokenautocomplete:1.3.3@aar" compile 'se.emilsjolander:stickylistheaders:2.6.0' compile 'org.sufficientlysecure:html-textview:1.1' @@ -39,6 +40,7 @@ dependencies { compile 'com.mikepenz.iconics:octicons-typeface:2.2.0@aar' compile 'com.mikepenz.iconics:meteocons-typeface:1.1.1@aar' compile 'com.mikepenz.iconics:community-material-typeface:1.0.0@aar' + compile 'org.thoughtcrime.ssl.pinning:AndroidPinning:1.0.0' compile 'com.nispok:snackbar:2.10.8' // libs as submodules diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml index 332930dfb..f63cf8823 100644 --- a/OpenKeychain/src/main/AndroidManifest.xml +++ b/OpenKeychain/src/main/AndroidManifest.xml @@ -103,6 +103,10 @@ android:label="@string/title_edit_key" /> <!-- NOTE: Dont use configChanges for QR Code view! We use a different layout for landscape --> <activity + android:name=".ui.linked.LinkedIdWizard" + android:configChanges="orientation|screenSize|keyboardHidden|keyboard" + android:label="@string/title_linked_create" /> + <activity android:name=".ui.QrCodeViewActivity" android:label="@string/share_qr_code_dialog_title" /> <activity diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/LinkedIdentity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/LinkedIdentity.java new file mode 100644 index 000000000..11ddb2cea --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/LinkedIdentity.java @@ -0,0 +1,32 @@ +package org.sufficientlysecure.keychain.linked; + +import java.net.URI; + +import android.content.Context; +import android.support.annotation.DrawableRes; + +public class LinkedIdentity extends UriAttribute { + + public final LinkedResource mResource; + + protected LinkedIdentity(URI uri, LinkedResource resource) { + super(uri); + if (resource == null) { + throw new AssertionError("resource must not be null in a LinkedIdentity!"); + } + mResource = resource; + } + + public @DrawableRes int getDisplayIcon() { + return mResource.getDisplayIcon(); + } + + public String getDisplayTitle(Context context) { + return mResource.getDisplayTitle(context); + } + + public String getDisplayComment(Context context) { + return mResource.getDisplayComment(context); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/LinkedResource.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/LinkedResource.java new file mode 100644 index 000000000..dffeea65e --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/LinkedResource.java @@ -0,0 +1,25 @@ +package org.sufficientlysecure.keychain.linked; + +import java.net.URI; + +import android.content.Context; +import android.content.Intent; +import android.support.annotation.DrawableRes; +import android.support.annotation.StringRes; + +public abstract class LinkedResource { + + public abstract URI toUri(); + + public abstract @DrawableRes int getDisplayIcon(); + public abstract @StringRes int getVerifiedText(boolean isSecret); + public abstract String getDisplayTitle(Context context); + public abstract String getDisplayComment(Context context); + public boolean isViewable() { + return false; + } + public Intent getViewIntent() { + return null; + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/LinkedTokenResource.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/LinkedTokenResource.java new file mode 100644 index 000000000..b5a3a6be6 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/LinkedTokenResource.java @@ -0,0 +1,301 @@ +package org.sufficientlysecure.keychain.linked; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpRequestBase; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.params.BasicHttpParams; +import org.json.JSONException; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.linked.resources.DnsResource; +import org.sufficientlysecure.keychain.linked.resources.GenericHttpsResource; +import org.sufficientlysecure.keychain.linked.resources.GithubResource; +import org.sufficientlysecure.keychain.linked.resources.TwitterResource; +import org.sufficientlysecure.keychain.operations.results.LinkedVerifyResult; +import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; +import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.util.Log; +import org.thoughtcrime.ssl.pinning.util.PinningHelper; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.MalformedURLException; +import java.net.URI; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map.Entry; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import android.content.Context; + + +public abstract class LinkedTokenResource extends LinkedResource { + + protected final URI mSubUri; + protected final Set<String> mFlags; + protected final HashMap<String,String> mParams; + + public static Pattern magicPattern = + Pattern.compile("\\[Verifying my (?:Open)?PGP key: openpgp4fpr:([a-zA-Z0-9]+)]"); + + protected LinkedTokenResource(Set<String> flags, HashMap<String, String> params, URI uri) { + mFlags = flags; + mParams = params; + mSubUri = uri; + } + + @SuppressWarnings("unused") + public URI getSubUri () { + return mSubUri; + } + + public Set<String> getFlags () { + return new HashSet<>(mFlags); + } + + public HashMap<String,String> getParams () { + return new HashMap<>(mParams); + } + + public static String generate (byte[] fingerprint) { + return String.format("[Verifying my OpenPGP key: openpgp4fpr:%s]", + KeyFormattingUtils.convertFingerprintToHex(fingerprint)); + } + + protected static LinkedTokenResource fromUri (URI uri) { + + if (!"openpgpid+token".equals(uri.getScheme()) + && !"openpgpid+cookie".equals(uri.getScheme())) { + Log.e(Constants.TAG, "unknown uri scheme in (suspected) linked id packet"); + return null; + } + + if (!uri.isOpaque()) { + Log.e(Constants.TAG, "non-opaque uri in (suspected) linked id packet"); + return null; + } + + String specific = uri.getSchemeSpecificPart(); + if (!specific.contains("@")) { + Log.e(Constants.TAG, "unknown uri scheme in linked id packet"); + return null; + } + + String[] pieces = specific.split("@", 2); + URI subUri = URI.create(pieces[1]); + + Set<String> flags = new HashSet<>(); + HashMap<String,String> params = new HashMap<>(); + if (!pieces[0].isEmpty()) { + String[] rawParams = pieces[0].split(";"); + for (String param : rawParams) { + String[] p = param.split("=", 2); + if (p.length == 1) { + flags.add(param); + } else { + params.put(p[0], p[1]); + } + } + } + + return findResourceType(flags, params, subUri); + + } + + protected static LinkedTokenResource findResourceType (Set<String> flags, + HashMap<String,String> params, + URI subUri) { + + LinkedTokenResource res; + + res = GenericHttpsResource.create(flags, params, subUri); + if (res != null) { + return res; + } + res = DnsResource.create(flags, params, subUri); + if (res != null) { + return res; + } + res = TwitterResource.create(flags, params, subUri); + if (res != null) { + return res; + } + res = GithubResource.create(flags, params, subUri); + if (res != null) { + return res; + } + + return null; + + } + + public URI toUri () { + + StringBuilder b = new StringBuilder(); + b.append("openpgpid+token:"); + + // add flags + if (mFlags != null) { + boolean first = true; + for (String flag : mFlags) { + if (!first) { + b.append(";"); + } + first = false; + b.append(flag); + } + } + + // add parameters + if (mParams != null) { + boolean first = true; + for (Entry<String, String> stringStringEntry : mParams.entrySet()) { + if (!first) { + b.append(";"); + } + first = false; + b.append(stringStringEntry.getKey()).append("=").append(stringStringEntry.getValue()); + } + } + + b.append("@"); + b.append(mSubUri); + + return URI.create(b.toString()); + + } + + public LinkedVerifyResult verify(Context context, byte[] fingerprint) { + + OperationLog log = new OperationLog(); + log.add(LogType.MSG_LV, 0); + + // Try to fetch resource. Logs for itself + String res = null; + try { + res = fetchResource(context, log, 1); + } catch (HttpStatusException e) { + // log verbose output to logcat + Log.e(Constants.TAG, "http error (" + e.getStatus() + "): " + e.getReason()); + log.add(LogType.MSG_LV_FETCH_ERROR, 2, Integer.toString(e.getStatus())); + } catch (MalformedURLException e) { + log.add(LogType.MSG_LV_FETCH_ERROR_URL, 2); + } catch (IOException e) { + Log.e(Constants.TAG, "io error", e); + log.add(LogType.MSG_LV_FETCH_ERROR_IO, 2); + } catch (JSONException e) { + Log.e(Constants.TAG, "json error", e); + log.add(LogType.MSG_LV_FETCH_ERROR_FORMAT, 2); + } + + if (res == null) { + // if this is null, an error was recorded in fetchResource above + return new LinkedVerifyResult(LinkedVerifyResult.RESULT_ERROR, log); + } + + Log.d(Constants.TAG, "Resource data: '" + res + "'"); + + return verifyString(log, 1, res, fingerprint); + + } + + protected abstract String fetchResource (Context context, OperationLog log, int indent) + throws HttpStatusException, IOException, JSONException; + + protected Matcher matchResource (OperationLog log, int indent, String res) { + return magicPattern.matcher(res); + } + + protected LinkedVerifyResult verifyString (OperationLog log, int indent, + String res, + byte[] fingerprint) { + + log.add(LogType.MSG_LV_MATCH, indent); + Matcher match = matchResource(log, indent+1, res); + if (!match.find()) { + log.add(LogType.MSG_LV_MATCH_ERROR, 2); + return new LinkedVerifyResult(LinkedVerifyResult.RESULT_ERROR, log); + } + + String candidateFp = match.group(1).toLowerCase(); + String fp = KeyFormattingUtils.convertFingerprintToHex(fingerprint); + if (!fp.equals(candidateFp)) { + log.add(LogType.MSG_LV_FP_ERROR, indent); + return new LinkedVerifyResult(LinkedVerifyResult.RESULT_ERROR, log); + } + log.add(LogType.MSG_LV_FP_OK, indent); + + return new LinkedVerifyResult(LinkedVerifyResult.RESULT_OK, log); + + } + + @SuppressWarnings("deprecation") // HttpRequestBase is deprecated + public static String getResponseBody(Context context, HttpRequestBase request) + throws IOException, HttpStatusException { + return getResponseBody(context, request, null); + } + + @SuppressWarnings("deprecation") // HttpRequestBase is deprecated + public static String getResponseBody(Context context, HttpRequestBase request, String[] pins) + throws IOException, HttpStatusException { + StringBuilder sb = new StringBuilder(); + + request.setHeader("User-Agent", "Open Keychain"); + + + HttpClient httpClient; + if (pins == null) { + httpClient = new DefaultHttpClient(new BasicHttpParams()); + } else { + httpClient = PinningHelper.getPinnedHttpClient(context, pins); + } + + HttpResponse response = httpClient.execute(request); + int statusCode = response.getStatusLine().getStatusCode(); + String reason = response.getStatusLine().getReasonPhrase(); + + if (statusCode != 200) { + throw new HttpStatusException(statusCode, reason); + } + + HttpEntity entity = response.getEntity(); + InputStream inputStream = entity.getContent(); + + BufferedReader bReader = new BufferedReader( + new InputStreamReader(inputStream, "UTF-8"), 8); + String line; + while ((line = bReader.readLine()) != null) { + sb.append(line); + } + + return sb.toString(); + } + + public static class HttpStatusException extends Throwable { + + private final int mStatusCode; + private final String mReason; + + HttpStatusException(int statusCode, String reason) { + super("http status " + statusCode + ": " + reason); + mStatusCode = statusCode; + mReason = reason; + } + + public int getStatus() { + return mStatusCode; + } + + public String getReason() { + return mReason; + } + + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/UriAttribute.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/UriAttribute.java new file mode 100644 index 000000000..9bcf84994 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/UriAttribute.java @@ -0,0 +1,79 @@ +package org.sufficientlysecure.keychain.linked; + +import org.spongycastle.util.Strings; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute; +import org.sufficientlysecure.keychain.util.Log; + +import java.io.IOException; +import java.net.URI; + +import android.content.Context; +import android.support.annotation.DrawableRes; + +/** The RawLinkedIdentity contains raw parsed data from a Linked Identity subpacket. */ +public class UriAttribute { + + public final URI mUri; + + protected UriAttribute(URI uri) { + mUri = uri; + } + + public byte[] getEncoded() { + return Strings.toUTF8ByteArray(mUri.toASCIIString()); + } + + public static UriAttribute fromAttributeData(byte[] data) throws IOException { + WrappedUserAttribute att = WrappedUserAttribute.fromData(data); + + byte[][] subpackets = att.getSubpackets(); + if (subpackets.length >= 1) { + return fromSubpacketData(subpackets[0]); + } + + throw new IOException("no subpacket data"); + } + + static UriAttribute fromSubpacketData(byte[] data) { + + try { + String uriStr = Strings.fromUTF8ByteArray(data); + URI uri = URI.create(uriStr); + + LinkedResource res = LinkedTokenResource.fromUri(uri); + if (res == null) { + return new UriAttribute(uri); + } + + return new LinkedIdentity(uri, res); + + } catch (IllegalArgumentException e) { + Log.e(Constants.TAG, "error parsing uri in (suspected) linked id packet"); + return null; + } + } + + public static UriAttribute fromResource (LinkedTokenResource res) { + return new UriAttribute(res.toUri()); + } + + + public WrappedUserAttribute toUserAttribute () { + return WrappedUserAttribute.fromSubpacket(WrappedUserAttribute.UAT_URI_ATTRIBUTE, getEncoded()); + } + + public @DrawableRes int getDisplayIcon() { + return R.drawable.ic_warning_grey_24dp; + } + + public String getDisplayTitle(Context context) { + return "unknown"; + } + + public String getDisplayComment(Context context) { + return null; + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/DnsResource.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/DnsResource.java new file mode 100644 index 000000000..86b672cc1 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/DnsResource.java @@ -0,0 +1,130 @@ +package org.sufficientlysecure.keychain.linked.resources; + +import android.content.Context; +import android.support.annotation.DrawableRes; +import android.support.annotation.StringRes; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; +import org.sufficientlysecure.keychain.linked.LinkedTokenResource; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; + +import java.net.URI; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import de.measite.minidns.Client; +import de.measite.minidns.DNSMessage; +import de.measite.minidns.Question; +import de.measite.minidns.Record; +import de.measite.minidns.Record.CLASS; +import de.measite.minidns.Record.TYPE; +import de.measite.minidns.record.TXT; + +public class DnsResource extends LinkedTokenResource { + + final static Pattern magicPattern = + Pattern.compile("openpgpid\\+token=([a-zA-Z0-9]+)(?:#|;)([a-zA-Z0-9]+)"); + + String mFqdn; + CLASS mClass; + TYPE mType; + + DnsResource(Set<String> flags, HashMap<String, String> params, URI uri, + String fqdn, CLASS clazz, TYPE type) { + super(flags, params, uri); + + mFqdn = fqdn; + mClass = clazz; + mType = type; + } + + public static String generateText(byte[] fingerprint) { + + return String.format("openpgp4fpr=%s", + KeyFormattingUtils.convertFingerprintToHex(fingerprint)); + + } + + public static DnsResource createNew (String domain) { + HashSet<String> flags = new HashSet<>(); + HashMap<String,String> params = new HashMap<>(); + URI uri = URI.create("dns:" + domain + "?TYPE=TXT"); + return create(flags, params, uri); + } + + public static DnsResource create(Set<String> flags, HashMap<String,String> params, URI uri) { + if ( ! ("dns".equals(uri.getScheme()) + && (flags == null || flags.isEmpty()) + && (params == null || params.isEmpty()))) { + return null; + } + + // + String spec = uri.getSchemeSpecificPart(); + // If there are // at the beginning, this includes an authority - we don't support those! + if (spec.startsWith("//")) { + return null; + } + + String[] pieces = spec.split("\\?", 2); + // In either case, part before a ? is the fqdn + String fqdn = pieces[0]; + // There may be a query part + /* + if (pieces.length > 1) { + // parse CLASS and TYPE query paramters + } + */ + + CLASS clazz = CLASS.IN; + TYPE type = TYPE.TXT; + + return new DnsResource(flags, params, uri, fqdn, clazz, type); + } + + @SuppressWarnings("unused") + public String getFqdn() { + return mFqdn; + } + + @Override + protected String fetchResource (Context context, OperationLog log, int indent) { + + Client c = new Client(); + DNSMessage msg = c.query(new Question(mFqdn, mType, mClass)); + Record aw = msg.getAnswers()[0]; + TXT txt = (TXT) aw.getPayload(); + return txt.getText().toLowerCase(); + + } + + @Override + protected Matcher matchResource(OperationLog log, int indent, String res) { + return magicPattern.matcher(res); + } + + @Override + public @StringRes + int getVerifiedText(boolean isSecret) { + return isSecret ? R.string.linked_verified_secret_dns : R.string.linked_verified_dns; + } + + @Override + public @DrawableRes int getDisplayIcon() { + return R.drawable.linked_dns; + } + + @Override + public String getDisplayTitle(Context context) { + return context.getString(R.string.linked_title_dns); + } + + @Override + public String getDisplayComment(Context context) { + return mFqdn; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/GenericHttpsResource.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/GenericHttpsResource.java new file mode 100644 index 000000000..82240c405 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/GenericHttpsResource.java @@ -0,0 +1,95 @@ +package org.sufficientlysecure.keychain.linked.resources; + +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.support.annotation.DrawableRes; +import android.support.annotation.StringRes; + +import org.apache.http.client.methods.HttpGet; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; +import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; +import org.sufficientlysecure.keychain.linked.LinkedTokenResource; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; + +import java.io.IOException; +import java.net.URI; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; + +public class GenericHttpsResource extends LinkedTokenResource { + + GenericHttpsResource(Set<String> flags, HashMap<String,String> params, URI uri) { + super(flags, params, uri); + } + + public static String generateText (Context context, byte[] fingerprint) { + String token = LinkedTokenResource.generate(fingerprint); + + return String.format(context.getResources().getString(R.string.linked_id_generic_text), + token, "0x" + KeyFormattingUtils.convertFingerprintToHex(fingerprint).substring(24)); + } + + @SuppressWarnings("deprecation") // HttpGet is deprecated + @Override + protected String fetchResource (Context context, OperationLog log, int indent) + throws HttpStatusException, IOException { + + log.add(LogType.MSG_LV_FETCH, indent, mSubUri.toString()); + HttpGet httpGet = new HttpGet(mSubUri); + return getResponseBody(context, httpGet); + + } + + public static GenericHttpsResource createNew (URI uri) { + HashSet<String> flags = new HashSet<>(); + flags.add("generic"); + HashMap<String,String> params = new HashMap<>(); + return create(flags, params, uri); + } + + public static GenericHttpsResource create(Set<String> flags, HashMap<String,String> params, URI uri) { + if ( ! ("https".equals(uri.getScheme()) + && flags != null && flags.size() == 1 && flags.contains("generic") + && (params == null || params.isEmpty()))) { + return null; + } + return new GenericHttpsResource(flags, params, uri); + } + + @Override + public @DrawableRes + int getDisplayIcon() { + return R.drawable.linked_https; + } + + @Override + public @StringRes + int getVerifiedText(boolean isSecret) { + return isSecret ? R.string.linked_verified_secret_https : R.string.linked_verified_https; + } + + @Override + public String getDisplayTitle(Context context) { + return context.getString(R.string.linked_title_https); + } + + @Override + public String getDisplayComment(Context context) { + return mSubUri.toString(); + } + + @Override + public boolean isViewable() { + return true; + } + + @Override + public Intent getViewIntent() { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse(mSubUri.toString())); + return intent; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/GithubResource.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/GithubResource.java new file mode 100644 index 000000000..30ce075b4 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/GithubResource.java @@ -0,0 +1,217 @@ +package org.sufficientlysecure.keychain.linked.resources; + +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.support.annotation.DrawableRes; +import android.support.annotation.StringRes; + +import org.apache.http.client.methods.HttpGet; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; +import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; +import org.sufficientlysecure.keychain.linked.LinkedTokenResource; +import org.sufficientlysecure.keychain.util.Log; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URI; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + + +public class GithubResource extends LinkedTokenResource { + + final String mHandle; + final String mGistId; + + GithubResource(Set<String> flags, HashMap<String,String> params, URI uri, + String handle, String gistId) { + super(flags, params, uri); + + mHandle = handle; + mGistId = gistId; + } + + public static String generate(Context context, byte[] fingerprint) { + String token = LinkedTokenResource.generate(fingerprint); + + return String.format(context.getResources().getString(R.string.linked_id_github_text), token); + } + + @SuppressWarnings("deprecation") // HttpGet is deprecated + @Override + protected String fetchResource (Context context, OperationLog log, int indent) + throws HttpStatusException, IOException, JSONException { + + log.add(LogType.MSG_LV_FETCH, indent, mSubUri.toString()); + indent += 1; + + HttpGet httpGet = new HttpGet("https://api.github.com/gists/" + mGistId); + String response = getResponseBody(context, httpGet); + + JSONObject obj = new JSONObject(response); + + JSONObject owner = obj.getJSONObject("owner"); + if (!mHandle.equals(owner.getString("login"))) { + log.add(LogType.MSG_LV_ERROR_GITHUB_HANDLE, indent); + return null; + } + + JSONObject files = obj.getJSONObject("files"); + Iterator<String> it = files.keys(); + if (it.hasNext()) { + // TODO can there be multiple candidates? + JSONObject file = files.getJSONObject(it.next()); + return file.getString("content"); + } + + log.add(LogType.MSG_LV_ERROR_GITHUB_NOT_FOUND, indent); + return null; + + } + + @SuppressWarnings("deprecation") + public static GithubResource searchInGithubStream( + Context context, String screenName, String needle, OperationLog log) { + + // narrow the needle down to important part + Matcher matcher = magicPattern.matcher(needle); + if (!matcher.find()) { + throw new AssertionError("Needle must contain token pattern! This is a programming error, please report."); + } + needle = matcher.group(); + + try { + + JSONArray array; { + HttpGet httpGet = + new HttpGet("https://api.github.com/users/" + screenName + "/gists"); + httpGet.setHeader("Content-Type", "application/json"); + httpGet.setHeader("User-Agent", "OpenKeychain"); + + String response = getResponseBody(context, httpGet); + array = new JSONArray(response); + } + + for (int i = 0, j = Math.min(array.length(), 5); i < j; i++) { + JSONObject obj = array.getJSONObject(i); + + JSONObject files = obj.getJSONObject("files"); + Iterator<String> it = files.keys(); + if (it.hasNext()) { + + JSONObject file = files.getJSONObject(it.next()); + String type = file.getString("type"); + if (!"text/plain".equals(type)) { + continue; + } + String id = obj.getString("id"); + HttpGet httpGet = new HttpGet("https://api.github.com/gists/" + id); + httpGet.setHeader("User-Agent", "OpenKeychain"); + + JSONObject gistObj = new JSONObject(getResponseBody(context, httpGet)); + JSONObject gistFiles = gistObj.getJSONObject("files"); + Iterator<String> gistIt = gistFiles.keys(); + if (!gistIt.hasNext()) { + continue; + } + // TODO can there be multiple candidates? + JSONObject gistFile = gistFiles.getJSONObject(gistIt.next()); + String content = gistFile.getString("content"); + if (!content.contains(needle)) { + continue; + } + + URI uri = URI.create("https://gist.github.com/" + screenName + "/" + id); + return create(uri); + } + } + + // update the results with the body of the response + log.add(LogType.MSG_LV_FETCH_ERROR_NOTHING, 2); + return null; + + } catch (HttpStatusException e) { + // log verbose output to logcat + Log.e(Constants.TAG, "http error (" + e.getStatus() + "): " + e.getReason()); + log.add(LogType.MSG_LV_FETCH_ERROR, 2, Integer.toString(e.getStatus())); + } catch (MalformedURLException e) { + log.add(LogType.MSG_LV_FETCH_ERROR_URL, 2); + } catch (IOException e) { + Log.e(Constants.TAG, "io error", e); + log.add(LogType.MSG_LV_FETCH_ERROR_IO, 2); + } catch (JSONException e) { + Log.e(Constants.TAG, "json error", e); + log.add(LogType.MSG_LV_FETCH_ERROR_FORMAT, 2); + } + + return null; + } + + public static GithubResource create(URI uri) { + return create(new HashSet<String>(), new HashMap<String,String>(), uri); + } + + public static GithubResource create(Set<String> flags, HashMap<String,String> params, URI uri) { + + // no params or flags + if (!flags.isEmpty() || !params.isEmpty()) { + return null; + } + + Pattern p = Pattern.compile("https://gist\\.github\\.com/([a-zA-Z0-9_]+)/([0-9a-f]+)"); + Matcher match = p.matcher(uri.toString()); + if (!match.matches()) { + return null; + } + String handle = match.group(1); + String gistId = match.group(2); + + return new GithubResource(flags, params, uri, handle, gistId); + + } + + + @Override + public @DrawableRes + int getDisplayIcon() { + return R.drawable.linked_github; + } + + @Override + public @StringRes + int getVerifiedText(boolean isSecret) { + return isSecret ? R.string.linked_verified_secret_github : R.string.linked_verified_github; + } + + @Override + public String getDisplayTitle(Context context) { + return context.getString(R.string.linked_title_github); + } + + @Override + public String getDisplayComment(Context context) { + return mHandle; + } + + @Override + public boolean isViewable() { + return true; + } + + @Override + public Intent getViewIntent() { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse(mSubUri.toString())); + return intent; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/TwitterResource.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/TwitterResource.java new file mode 100644 index 000000000..73e3d3643 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/TwitterResource.java @@ -0,0 +1,250 @@ +package org.sufficientlysecure.keychain.linked.resources; + +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.support.annotation.DrawableRes; +import android.support.annotation.StringRes; +import android.util.Log; + +import com.textuality.keybase.lib.JWalk; + +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.StringEntity; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; +import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; +import org.sufficientlysecure.keychain.linked.LinkedTokenResource; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URI; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class TwitterResource extends LinkedTokenResource { + + public static final String[] CERT_PINS = null; /*(new String[] { + // Symantec Class 3 Secure Server CA - G4 + "513fb9743870b73440418d30930699ff" + };*/ + + final String mHandle; + final String mTweetId; + + TwitterResource(Set<String> flags, HashMap<String,String> params, + URI uri, String handle, String tweetId) { + super(flags, params, uri); + + mHandle = handle; + mTweetId = tweetId; + } + + public static TwitterResource create(URI uri) { + return create(new HashSet<String>(), new HashMap<String,String>(), uri); + } + + public static TwitterResource create(Set<String> flags, HashMap<String,String> params, URI uri) { + + // no params or flags + if (!flags.isEmpty() || !params.isEmpty()) { + return null; + } + + Pattern p = Pattern.compile("https://twitter\\.com/([a-zA-Z0-9_]+)/status/([0-9]+)"); + Matcher match = p.matcher(uri.toString()); + if (!match.matches()) { + return null; + } + String handle = match.group(1); + String tweetId = match.group(2); + + return new TwitterResource(flags, params, uri, handle, tweetId); + + } + + @SuppressWarnings("deprecation") + @Override + protected String fetchResource(Context context, OperationLog log, int indent) + throws IOException, HttpStatusException, JSONException { + + String authToken; + try { + authToken = getAuthToken(context); + } catch (IOException | HttpStatusException | JSONException e) { + log.add(LogType.MSG_LV_ERROR_TWITTER_AUTH, indent); + return null; + } + + HttpGet httpGet = + new HttpGet("https://api.twitter.com/1.1/statuses/show.json" + + "?id=" + mTweetId + + "&include_entities=false"); + + // construct a normal HTTPS request and include an Authorization + // header with the value of Bearer <> + httpGet.setHeader("Authorization", "Bearer " + authToken); + httpGet.setHeader("Content-Type", "application/json"); + + try { + String response = getResponseBody(context, httpGet, CERT_PINS); + JSONObject obj = new JSONObject(response); + JSONObject user = obj.getJSONObject("user"); + if (!mHandle.equalsIgnoreCase(user.getString("screen_name"))) { + log.add(LogType.MSG_LV_ERROR_TWITTER_HANDLE, indent); + return null; + } + + // update the results with the body of the response + return obj.getString("text"); + } catch (JSONException e) { + log.add(LogType.MSG_LV_ERROR_TWITTER_RESPONSE, indent); + return null; + } + + } + + @Override + public @DrawableRes int getDisplayIcon() { + return R.drawable.linked_twitter; + } + + @Override + public @StringRes + int getVerifiedText(boolean isSecret) { + return isSecret ? R.string.linked_verified_secret_twitter : R.string.linked_verified_twitter; + } + + @Override + public String getDisplayTitle(Context context) { + return context.getString(R.string.linked_title_twitter); + } + + @Override + public String getDisplayComment(Context context) { + return "@" + mHandle; + } + + @Override + public boolean isViewable() { + return true; + } + + @Override + public Intent getViewIntent() { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse(mSubUri.toString())); + return intent; + } + + @SuppressWarnings("deprecation") + public static TwitterResource searchInTwitterStream( + Context context, String screenName, String needle, OperationLog log) { + + String authToken; + try { + authToken = getAuthToken(context); + } catch (IOException | HttpStatusException | JSONException e) { + log.add(LogType.MSG_LV_ERROR_TWITTER_AUTH, 1); + return null; + } + + HttpGet httpGet = + new HttpGet("https://api.twitter.com/1.1/statuses/user_timeline.json" + + "?screen_name=" + screenName + + "&count=15" + + "&include_rts=false" + + "&trim_user=true" + + "&exclude_replies=true"); + + // construct a normal HTTPS request and include an Authorization + // header with the value of Bearer <> + httpGet.setHeader("Authorization", "Bearer " + authToken); + httpGet.setHeader("Content-Type", "application/json"); + + try { + String response = getResponseBody(context, httpGet, CERT_PINS); + JSONArray array = new JSONArray(response); + + for (int i = 0; i < array.length(); i++) { + JSONObject obj = array.getJSONObject(i); + String tweet = obj.getString("text"); + if (tweet.contains(needle)) { + String id = obj.getString("id_str"); + URI uri = URI.create("https://twitter.com/" + screenName + "/status/" + id); + return create(uri); + } + } + + // update the results with the body of the response + log.add(LogType.MSG_LV_FETCH_ERROR_NOTHING, 1); + return null; + + } catch (HttpStatusException e) { + // log verbose output to logcat + Log.e(Constants.TAG, "http error (" + e.getStatus() + "): " + e.getReason()); + log.add(LogType.MSG_LV_FETCH_ERROR, 1, Integer.toString(e.getStatus())); + } catch (MalformedURLException e) { + log.add(LogType.MSG_LV_FETCH_ERROR_URL, 1); + } catch (IOException e) { + Log.e(Constants.TAG, "io error", e); + log.add(LogType.MSG_LV_FETCH_ERROR_IO, 1); + } catch (JSONException e) { + Log.e(Constants.TAG, "json error", e); + log.add(LogType.MSG_LV_FETCH_ERROR_FORMAT, 1); + } + + return null; + } + + private static String cachedAuthToken; + + @SuppressWarnings("deprecation") + private static String getAuthToken(Context context) + throws IOException, HttpStatusException, JSONException { + if (cachedAuthToken != null) { + return cachedAuthToken; + } + String base64Encoded = rot13("D293FQqanH0jH29KIaWJER5DomqSGRE2Ewc1LJACn3cbD1c" + + "Fq1bmqSAQAz5MI2cIHKOuo3cPoRAQI1OyqmIVFJS6LHMXq2g6MRLkIj") + "=="; + + // Step 2: Obtain a bearer token + HttpPost httpPost = new HttpPost("https://api.twitter.com/oauth2/token"); + httpPost.setHeader("Authorization", "Basic " + base64Encoded); + httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8"); + httpPost.setEntity(new StringEntity("grant_type=client_credentials")); + JSONObject rawAuthorization = new JSONObject(getResponseBody(context, httpPost, CERT_PINS)); + + // Applications should verify that the value associated with the + // token_type key of the returned object is bearer + if (!"bearer".equals(JWalk.getString(rawAuthorization, "token_type"))) { + throw new JSONException("Expected bearer token in response!"); + } + + cachedAuthToken = rawAuthorization.getString("access_token"); + return cachedAuthToken; + + } + + public static String rot13(String input) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < input.length(); i++) { + char c = input.charAt(i); + if (c >= 'a' && c <= 'm') c += 13; + else if (c >= 'A' && c <= 'M') c += 13; + else if (c >= 'n' && c <= 'z') c -= 13; + else if (c >= 'N' && c <= 'Z') c -= 13; + sb.append(c); + } + return sb.toString(); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/LinkedVerifyResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/LinkedVerifyResult.java new file mode 100644 index 000000000..42e9ec3f0 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/LinkedVerifyResult.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.operations.results; + +import android.os.Parcel; + +public class LinkedVerifyResult extends OperationResult { + + public LinkedVerifyResult(int result, OperationLog log) { + super(result, log); + } + + /** Construct from a parcel - trivial because we have no extra data. */ + public LinkedVerifyResult(Parcel source) { + super(source); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + } + + public static Creator<LinkedVerifyResult> CREATOR = new Creator<LinkedVerifyResult>() { + public LinkedVerifyResult createFromParcel(final Parcel source) { + return new LinkedVerifyResult(source); + } + + public LinkedVerifyResult[] newArray(final int size) { + return new LinkedVerifyResult[size]; + } + }; + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java index 094afd4a5..c93db5c39 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java @@ -730,13 +730,33 @@ public abstract class OperationResult implements Parcelable { MSG_DEL_OK (LogLevel.OK, R.plurals.msg_del_ok), MSG_DEL_FAIL (LogLevel.WARN, R.plurals.msg_del_fail), + MSG_LV (LogLevel.START, R.string.msg_lv), + MSG_LV_MATCH (LogLevel.DEBUG, R.string.msg_lv_match), + MSG_LV_MATCH_ERROR (LogLevel.ERROR, R.string.msg_lv_match_error), + MSG_LV_FP_OK (LogLevel.DEBUG, R.string.msg_lv_fp_ok), + MSG_LV_FP_ERROR (LogLevel.ERROR, R.string.msg_lv_fp_error), + + MSG_LV_ERROR_TWITTER_AUTH (LogLevel.ERROR, R.string.msg_lv_error_twitter_auth), + MSG_LV_ERROR_TWITTER_HANDLE (LogLevel.ERROR, R.string.msg_lv_error_twitter_handle), + MSG_LV_ERROR_TWITTER_RESPONSE (LogLevel.ERROR, R.string.msg_lv_error_twitter_response), + MSG_LV_ERROR_GITHUB_HANDLE (LogLevel.ERROR, R.string.msg_lv_error_github_handle), + MSG_LV_ERROR_GITHUB_NOT_FOUND (LogLevel.ERROR, R.string.msg_lv_error_github_not_found), + + MSG_LV_FETCH (LogLevel.DEBUG, R.string.msg_lv_fetch), + MSG_LV_FETCH_REDIR (LogLevel.DEBUG, R.string.msg_lv_fetch_redir), + MSG_LV_FETCH_OK (LogLevel.DEBUG, R.string.msg_lv_fetch_ok), + MSG_LV_FETCH_ERROR (LogLevel.ERROR, R.string.msg_lv_fetch_error), + MSG_LV_FETCH_ERROR_URL (LogLevel.ERROR, R.string.msg_lv_fetch_error_url), + MSG_LV_FETCH_ERROR_IO (LogLevel.ERROR, R.string.msg_lv_fetch_error_io), + MSG_LV_FETCH_ERROR_FORMAT(LogLevel.ERROR, R.string.msg_lv_fetch_error_format), + MSG_LV_FETCH_ERROR_NOTHING (LogLevel.ERROR, R.string.msg_lv_fetch_error_nothing), + //export log MSG_EXPORT_LOG(LogLevel.START,R.string.msg_export_log_start), MSG_EXPORT_LOG_EXPORT_ERROR_NO_FILE(LogLevel.ERROR,R.string.msg_export_log_error_no_file), MSG_EXPORT_LOG_EXPORT_ERROR_FOPEN(LogLevel.ERROR,R.string.msg_export_log_error_fopen), MSG_EXPORT_LOG_EXPORT_ERROR_WRITING(LogLevel.ERROR,R.string.msg_export_log_error_writing), - MSG_EXPORT_LOG_EXPORT_SUCCESS (LogLevel.OK, R.string.msg_export_log_success), - ; + MSG_EXPORT_LOG_EXPORT_SUCCESS (LogLevel.OK, R.string.msg_export_log_success); public final int mMsgId; public final LogLevel mLevel; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java index 9276cba10..0173a1d83 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java @@ -361,4 +361,5 @@ public class UncachedPublicKey { return calendar.getTime(); } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedUserAttribute.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedUserAttribute.java index 2c7f0187a..535314607 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedUserAttribute.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedUserAttribute.java @@ -1,4 +1,8 @@ /* +<<<<<<< HEAD + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> +======= +>>>>>>> development * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com> * * This program is free software: you can redistribute it and/or modify @@ -37,6 +41,7 @@ public class WrappedUserAttribute implements Serializable { public static final int UAT_NONE = 0; public static final int UAT_IMAGE = UserAttributeSubpacketTags.IMAGE_ATTRIBUTE; + public static final int UAT_URI_ATTRIBUTE = 101; private PGPUserAttributeSubpacketVector mVector; @@ -77,7 +82,7 @@ public class WrappedUserAttribute implements Serializable { public static WrappedUserAttribute fromData (byte[] data) throws IOException { UserAttributeSubpacketInputStream in = new UserAttributeSubpacketInputStream(new ByteArrayInputStream(data)); - ArrayList<UserAttributeSubpacket> list = new ArrayList<UserAttributeSubpacket>(); + ArrayList<UserAttributeSubpacket> list = new ArrayList<>(); while (in.available() > 0) { list.add(in.readPacket()); } @@ -121,6 +126,7 @@ public class WrappedUserAttribute implements Serializable { private void readObjectNoData() throws ObjectStreamException { } + @SuppressWarnings("SimplifiableIfStatement") @Override public boolean equals(Object o) { if (!WrappedUserAttribute.class.isInstance(o)) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java index d5283f01f..11d6728e2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java @@ -106,6 +106,7 @@ public class KeychainContract { public static final String PATH_PUBLIC = "public"; public static final String PATH_SECRET = "secret"; public static final String PATH_USER_IDS = "user_ids"; + public static final String PATH_LINKED_IDS = "linked_ids"; public static final String PATH_KEYS = "keys"; public static final String PATH_CERTS = "certs"; @@ -262,6 +263,11 @@ public class KeychainContract { public static Uri buildUserIdsUri(Uri uri) { return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1)).appendPath(PATH_USER_IDS).build(); } + + public static Uri buildLinkedIdsUri(Uri uri) { + return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1)).appendPath(PATH_LINKED_IDS).build(); + } + } public static class ApiApps implements ApiAppsColumns, BaseColumns { @@ -350,7 +356,14 @@ public class KeychainContract { } public static Uri buildCertsUri(Uri uri) { - return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1)).appendPath(PATH_CERTS).build(); + return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1)) + .appendPath(PATH_CERTS).build(); + } + + public static Uri buildLinkedIdCertsUri(Uri uri, int rank) { + return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1)) + .appendPath(PATH_LINKED_IDS).appendPath(Integer.toString(rank)) + .appendPath(PATH_CERTS).build(); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java index 1351e0cbc..ecb26b56a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -31,6 +31,7 @@ import android.net.Uri; import android.text.TextUtils; import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute; import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAccounts; import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAllowedKeys; import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps; @@ -62,6 +63,8 @@ public class KeychainProvider extends ContentProvider { private static final int KEY_RING_SECRET = 204; private static final int KEY_RING_CERTS = 205; private static final int KEY_RING_CERTS_SPECIFIC = 206; + private static final int KEY_RING_LINKED_IDS = 207; + private static final int KEY_RING_LINKED_ID_CERTS = 208; private static final int API_APPS = 301; private static final int API_APPS_BY_PACKAGE_NAME = 302; @@ -127,6 +130,9 @@ public class KeychainProvider extends ContentProvider { * key_rings/_/unified * key_rings/_/keys * key_rings/_/user_ids + * key_rings/_/linked_ids + * key_rings/_/linked_ids/_ + * key_rings/_/linked_ids/_/certs * key_rings/_/public * key_rings/_/secret * key_rings/_/certs @@ -143,6 +149,13 @@ public class KeychainProvider extends ContentProvider { + KeychainContract.PATH_USER_IDS, KEY_RING_USER_IDS); matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/" + + KeychainContract.PATH_LINKED_IDS, + KEY_RING_LINKED_IDS); + matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/" + + KeychainContract.PATH_LINKED_IDS + "/*/" + + KeychainContract.PATH_CERTS, + KEY_RING_LINKED_ID_CERTS); + matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/" + KeychainContract.PATH_PUBLIC, KEY_RING_PUBLIC); matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/" @@ -477,7 +490,8 @@ public class KeychainProvider extends ContentProvider { } case KEY_RINGS_USER_IDS: - case KEY_RING_USER_IDS: { + case KEY_RING_USER_IDS: + case KEY_RING_LINKED_IDS: { HashMap<String, String> projectionMap = new HashMap<>(); projectionMap.put(UserPackets._ID, Tables.USER_PACKETS + ".oid AS _id"); projectionMap.put(UserPackets.MASTER_KEY_ID, Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID); @@ -502,13 +516,15 @@ public class KeychainProvider extends ContentProvider { groupBy = Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + ", " + Tables.USER_PACKETS + "." + UserPackets.RANK; - // for now, we only respect user ids here, so TYPE must be NULL - // TODO expand with KEY_RING_USER_PACKETS query type which lifts this restriction - qb.appendWhere(Tables.USER_PACKETS + "." + UserPackets.TYPE + " IS NULL"); + if (match == KEY_RING_LINKED_IDS) { + qb.appendWhere(Tables.USER_PACKETS + "." + UserPackets.TYPE + " = " + + WrappedUserAttribute.UAT_URI_ATTRIBUTE); + } else { + qb.appendWhere(Tables.USER_PACKETS + "." + UserPackets.TYPE + " IS NULL"); + } // If we are searching for a particular keyring's ids, add where - if (match == KEY_RING_USER_IDS) { - // TODO remove with the thing above + if (match == KEY_RING_USER_IDS || match == KEY_RING_LINKED_IDS) { qb.appendWhere(" AND "); qb.appendWhere(Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + " = "); qb.appendWhereEscapeString(uri.getPathSegments().get(1)); @@ -560,7 +576,8 @@ public class KeychainProvider extends ContentProvider { } case KEY_RING_CERTS: - case KEY_RING_CERTS_SPECIFIC: { + case KEY_RING_CERTS_SPECIFIC: + case KEY_RING_LINKED_ID_CERTS: { HashMap<String, String> projectionMap = new HashMap<>(); projectionMap.put(Certs._ID, Tables.CERTS + ".oid AS " + Certs._ID); projectionMap.put(Certs.MASTER_KEY_ID, Tables.CERTS + "." + Certs.MASTER_KEY_ID); @@ -581,10 +598,6 @@ public class KeychainProvider extends ContentProvider { + " AND " + Tables.CERTS + "." + Certs.RANK + " = " + Tables.USER_PACKETS + "." + UserPackets.RANK - // for now, we only return user ids here, so TYPE must be NULL - // TODO at some point, we should lift this restriction - + " AND " - + Tables.USER_PACKETS + "." + UserPackets.TYPE + " IS NULL" + ") LEFT JOIN " + Tables.USER_PACKETS + " AS signer ON (" + Tables.CERTS + "." + Certs.KEY_ID_CERTIFIER + " = " + "signer." + UserPackets.MASTER_KEY_ID @@ -604,6 +617,17 @@ public class KeychainProvider extends ContentProvider { qb.appendWhereEscapeString(uri.getPathSegments().get(4)); } + if (match == KEY_RING_LINKED_ID_CERTS) { + qb.appendWhere(" AND " + Tables.USER_PACKETS + "." + + UserPackets.TYPE + " IS NOT NULL"); + + qb.appendWhere(" AND " + Tables.USER_PACKETS + "." + + UserPackets.RANK + " = "); + qb.appendWhereEscapeString(uri.getPathSegments().get(3)); + } else { + qb.appendWhere(" AND " + Tables.USER_PACKETS + "." + UserPackets.TYPE + " IS NULL"); + } + break; } @@ -707,7 +731,7 @@ public class KeychainProvider extends ContentProvider { )) { throw new AssertionError("Incorrect type for user packet! This is a bug!"); } - if (((Number)values.get(UserPacketsColumns.RANK)).intValue() == 0 && values.get(UserPacketsColumns.USER_ID) == null) { + if (values.get(UserPacketsColumns.RANK) == 0 && values.get(UserPacketsColumns.USER_ID) == null) { throw new AssertionError("Rank 0 user packet must be a user id!"); } db.insertOrThrow(Tables.USER_PACKETS, null, values); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java index 33e9a4345..bf56417e9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java @@ -31,10 +31,15 @@ import android.support.v4.util.LongSparseArray; import org.spongycastle.bcpg.CompressionAlgorithmTags; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; +import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute; +import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize; +import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.operations.ImportExportOperation; import org.sufficientlysecure.keychain.operations.results.ConsolidateResult; -import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult; @@ -49,7 +54,6 @@ import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.pgp.UncachedPublicKey; import org.sufficientlysecure.keychain.pgp.WrappedSignature; -import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAllowedKeys; import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps; @@ -57,15 +61,11 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; -import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; import org.sufficientlysecure.keychain.remote.AccountSettings; import org.sufficientlysecure.keychain.remote.AppSettings; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.ParcelableFileCache; -import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize; -import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.ProgressFixedScaler; import org.sufficientlysecure.keychain.util.ProgressScaler; import org.sufficientlysecure.keychain.util.Utf8Util; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java index 8721f4c0c..a7571a7ac 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java @@ -24,6 +24,7 @@ import android.os.Parcelable; import java.io.Serializable; import java.nio.ByteBuffer; import java.util.ArrayList; +import java.util.List; import java.util.Date; import java.util.Map; @@ -85,17 +86,11 @@ public class CertifyActionsParcel implements Parcelable { final public ArrayList<String> mUserIds; final public ArrayList<WrappedUserAttribute> mUserAttributes; - public CertifyAction(long masterKeyId, ArrayList<String> userIds) { + public CertifyAction(long masterKeyId, List<String> userIds, + List<WrappedUserAttribute> attributes) { mMasterKeyId = masterKeyId; - mUserIds = userIds; - mUserAttributes = null; - } - - public CertifyAction(long masterKeyId, ArrayList<String> userIds, - ArrayList<WrappedUserAttribute> attributes) { - mMasterKeyId = masterKeyId; - mUserIds = userIds; - mUserAttributes = attributes; + mUserIds = userIds == null ? null : new ArrayList<>(userIds); + mUserAttributes = attributes == null ? null : new ArrayList<>(attributes); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java index e0509ac9b..63ea6285c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java @@ -47,6 +47,14 @@ import org.sufficientlysecure.keychain.operations.results.ConsolidateResult; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.operations.results.DeleteResult; import org.sufficientlysecure.keychain.operations.results.ExportResult; +import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; +import org.sufficientlysecure.keychain.operations.results.CertifyResult; +import org.sufficientlysecure.keychain.util.FileHelper; +import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize; +import org.sufficientlysecure.keychain.util.Preferences; +import org.sufficientlysecure.keychain.keyimport.HkpKeyserver; +import org.sufficientlysecure.keychain.keyimport.Keyserver; +import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java index 59623a610..51b6e824d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java @@ -88,7 +88,9 @@ public class CertifyKeyFragment extends CryptoOperationFragment }; private static final int INDEX_MASTER_KEY_ID = 1; private static final int INDEX_USER_ID = 2; + @SuppressWarnings("unused") private static final int INDEX_IS_PRIMARY = 3; + @SuppressWarnings("unused") private static final int INDEX_IS_REVOKED = 4; private MultiUserIdsAdapter mUserIdsAdapter; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java index c5f9821d3..e9bc42a4d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java @@ -331,6 +331,16 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements onVerifyLoaded(true); + } else if (isYours) { + + mSignatureText.setText(R.string.decrypt_result_signature_secret); + KeyFormattingUtils.setStatusImage(getActivity(), mSignatureIcon, mSignatureText, State.VERIFIED); + + setSignatureLayoutVisibility(View.VISIBLE); + setShowAction(signatureKeyId); + + onVerifyLoaded(true); + } else if (isVerified) { mSignatureText.setText(R.string.decrypt_result_signature_certified); KeyFormattingUtils.setStatusImage(getActivity(), mSignatureIcon, mSignatureText, State.VERIFIED); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java index 390efddce..897de8490 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java @@ -276,7 +276,7 @@ public class EditKeyFragment extends CryptoOperationFragment implements case LOADER_ID_USER_IDS: { Uri baseUri = UserPackets.buildUserIdsUri(mDataUri); return new CursorLoader(getActivity(), baseUri, - UserIdsAdapter.USER_IDS_PROJECTION, null, null, null); + UserIdsAdapter.USER_PACKETS_PROJECTION, null, null, null); } case LOADER_ID_SUBKEYS: { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java index 1355bd3e6..d8c3e0350 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java @@ -66,12 +66,12 @@ import org.sufficientlysecure.keychain.provider.KeychainDatabase; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.CloudImportService; import org.sufficientlysecure.keychain.service.KeychainIntentService; +import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter; +import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment; import org.sufficientlysecure.keychain.service.ServiceProgressHandler; import org.sufficientlysecure.keychain.service.PassphraseCacheService; -import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; -import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.ExportHelper; import org.sufficientlysecure.keychain.util.FabContainer; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java index 4e926c0fe..c6431bfaf 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java @@ -66,6 +66,7 @@ import org.sufficientlysecure.keychain.util.Preferences; * This activity encapsulates a DialogFragment to emulate a dialog. */ public class PassphraseDialogActivity extends FragmentActivity { + public static final String RESULT_CRYPTO_INPUT = "result_data"; public static final String EXTRA_REQUIRED_INPUT = "required_input"; @@ -261,6 +262,9 @@ public class PassphraseDialogActivity extends FragmentActivity { case DIVERT_TO_CARD: message = getString(R.string.yubikey_pin_for, userId); break; + // special case: empty passphrase just returns the empty passphrase + case PASSPHRASE_EMPTY: + finishCaching(new Passphrase("")); default: throw new AssertionError("Unhandled SecretKeyType (should not happen)"); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java index 5466c0b9a..9a48201b6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -66,6 +66,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.KeychainIntentService; +import org.sufficientlysecure.keychain.ui.linked.LinkedIdWizard; import org.sufficientlysecure.keychain.service.ServiceProgressHandler; import org.sufficientlysecure.keychain.service.ServiceProgressHandler.MessageStatus; import org.sufficientlysecure.keychain.service.PassphraseCacheService; @@ -291,24 +292,33 @@ public class ViewKeyActivity extends BaseNfcActivity implements setContentView(R.layout.view_key_activity); } - private void startFragment(Bundle savedInstanceState, Uri dataUri) { - // However, if we're being restored from a previous state, - // then we don't need to do anything and should return or else - // we could end up with overlapping fragments. + private void startFragment(Bundle savedInstanceState, final Uri dataUri) { + // If we're being restored from a previous state, then we don't + // need to do anything and should return or else we could end + // up with overlapping fragments. if (savedInstanceState != null) { return; } - // Create an instance of the fragment - ViewKeyFragment frag = ViewKeyFragment.newInstance(dataUri); + new Handler().post(new Runnable() { + @Override + public void run() { + + FragmentManager manager = getSupportFragmentManager(); + if (manager.getBackStackEntryCount() == 0) { + // Create an instance of the fragment + final ViewKeyFragment frag = ViewKeyFragment.newInstance(dataUri); + manager.beginTransaction() + .replace(R.id.view_key_fragment, frag) + .commit(); + manager.popBackStack(); + } /* else { + // not sure yet if we actually want this! + manager.popBackStack(); + } */ - // Add the fragment to the 'fragment_container' FrameLayout - // NOTE: We use commitAllowingStateLoss() to prevent weird crashes! - getSupportFragmentManager().beginTransaction() - .replace(R.id.view_key_fragment, frag) - .commitAllowingStateLoss(); - // do it immediately! - getSupportFragmentManager().executePendingTransactions(); + } + }); } @Override @@ -370,6 +380,12 @@ public class ViewKeyActivity extends BaseNfcActivity implements } return true; } + case R.id.menu_key_view_add_linked_identity: { + Intent intent = new Intent(this, LinkedIdWizard.class); + intent.setData(mDataUri); + startActivity(intent); + return true; + } case R.id.menu_key_view_edit: { editKey(mDataUri); return true; @@ -386,6 +402,10 @@ public class ViewKeyActivity extends BaseNfcActivity implements public boolean onPrepareOptionsMenu(Menu menu) { MenuItem editKey = menu.findItem(R.id.menu_key_view_edit); editKey.setVisible(mIsSecret); + + MenuItem addLinked = menu.findItem(R.id.menu_key_view_add_linked_identity); + addLinked.setVisible(mIsSecret); + MenuItem certifyFingerprint = menu.findItem(R.id.menu_key_view_certify_fingerprint); certifyFingerprint.setVisible(!mIsSecret && !mIsVerified && !mIsExpired && !mIsRevoked); @@ -809,166 +829,171 @@ public class ViewKeyActivity extends BaseNfcActivity implements // Swap the new cursor in. (The framework will take care of closing the // old cursor once we return.) switch (loader.getId()) { + case LOADER_ID_UNIFIED: { - if (data.moveToFirst()) { - // get name, email, and comment from USER_ID - KeyRing.UserId mainUserId = KeyRing.splitUserId(data.getString(INDEX_USER_ID)); - if (mainUserId.name != null) { - mName.setText(mainUserId.name); + + if (!data.moveToFirst()) { + return; + } + + // get name, email, and comment from USER_ID + KeyRing.UserId mainUserId = KeyRing.splitUserId(data.getString(INDEX_USER_ID)); + if (mainUserId.name != null) { + mName.setText(mainUserId.name); + } else { + mName.setText(R.string.user_id_no_name); + } + + mMasterKeyId = data.getLong(INDEX_MASTER_KEY_ID); + mFingerprint = KeyFormattingUtils.convertFingerprintToHex(data.getBlob(INDEX_FINGERPRINT)); + + mIsSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0; + mHasEncrypt = data.getInt(INDEX_HAS_ENCRYPT) != 0; + mIsRevoked = data.getInt(INDEX_IS_REVOKED) > 0; + mIsExpired = data.getInt(INDEX_IS_EXPIRED) != 0; + mIsVerified = data.getInt(INDEX_VERIFIED) > 0; + + // if the refresh animation isn't playing + if (!mRotate.hasStarted() && !mRotateSpin.hasStarted()) { + // re-create options menu based on mIsSecret, mIsVerified + supportInvalidateOptionsMenu(); + // this is done at the end of the animation otherwise + } + + AsyncTask<Long, Void, Bitmap> photoTask = + new AsyncTask<Long, Void, Bitmap>() { + protected Bitmap doInBackground(Long... mMasterKeyId) { + return ContactHelper.loadPhotoByMasterKeyId(getContentResolver(), mMasterKeyId[0], true); + } + + protected void onPostExecute(Bitmap photo) { + mPhoto.setImageBitmap(photo); + mPhoto.setVisibility(View.VISIBLE); + } + }; + + // Note: order is important + int color; + if (mIsRevoked) { + mStatusText.setText(R.string.view_key_revoked); + mStatusImage.setVisibility(View.VISIBLE); + KeyFormattingUtils.setStatusImage(this, mStatusImage, mStatusText, + State.REVOKED, R.color.icons, true); + color = getResources().getColor(R.color.android_red_light); + + mActionEncryptFile.setVisibility(View.GONE); + mActionEncryptText.setVisibility(View.GONE); + mActionNfc.setVisibility(View.GONE); + mFab.setVisibility(View.GONE); + mQrCodeLayout.setVisibility(View.GONE); + } else if (mIsExpired) { + if (mIsSecret) { + mStatusText.setText(R.string.view_key_expired_secret); } else { - mName.setText(R.string.user_id_no_name); + mStatusText.setText(R.string.view_key_expired); } + mStatusImage.setVisibility(View.VISIBLE); + KeyFormattingUtils.setStatusImage(this, mStatusImage, mStatusText, + State.EXPIRED, R.color.icons, true); + color = getResources().getColor(R.color.android_red_light); + + mActionEncryptFile.setVisibility(View.GONE); + mActionEncryptText.setVisibility(View.GONE); + mActionNfc.setVisibility(View.GONE); + mFab.setVisibility(View.GONE); + mQrCodeLayout.setVisibility(View.GONE); + } else if (mIsSecret) { + mStatusText.setText(R.string.view_key_my_key); + mStatusImage.setVisibility(View.GONE); + color = getResources().getColor(R.color.primary); + // reload qr code only if the fingerprint changed + if (!mFingerprint.equals(mQrCodeLoaded)) { + loadQrCode(mFingerprint); + } + photoTask.execute(mMasterKeyId); + mQrCodeLayout.setVisibility(View.VISIBLE); + + // and place leftOf qr code + RelativeLayout.LayoutParams nameParams = (RelativeLayout.LayoutParams) + mName.getLayoutParams(); + // remove right margin + nameParams.setMargins(FormattingUtils.dpToPx(this, 48), 0, 0, 0); + if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + nameParams.setMarginEnd(0); + } + nameParams.addRule(RelativeLayout.LEFT_OF, R.id.view_key_qr_code_layout); + mName.setLayoutParams(nameParams); + + RelativeLayout.LayoutParams statusParams = (RelativeLayout.LayoutParams) + mStatusText.getLayoutParams(); + statusParams.setMargins(FormattingUtils.dpToPx(this, 48), 0, 0, 0); + if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + statusParams.setMarginEnd(0); + } + statusParams.addRule(RelativeLayout.LEFT_OF, R.id.view_key_qr_code_layout); + mStatusText.setLayoutParams(statusParams); - mMasterKeyId = data.getLong(INDEX_MASTER_KEY_ID); - mFingerprint = KeyFormattingUtils.convertFingerprintToHex(data.getBlob(INDEX_FINGERPRINT)); - - mIsSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0; - mHasEncrypt = data.getInt(INDEX_HAS_ENCRYPT) != 0; - mIsRevoked = data.getInt(INDEX_IS_REVOKED) > 0; - mIsExpired = data.getInt(INDEX_IS_EXPIRED) != 0; - mIsVerified = data.getInt(INDEX_VERIFIED) > 0; + mActionEncryptFile.setVisibility(View.VISIBLE); + mActionEncryptText.setVisibility(View.VISIBLE); - // if the refresh animation isn't playing - if (!mRotate.hasStarted() && !mRotateSpin.hasStarted()) { - // re-create options menu based on mIsSecret, mIsVerified - supportInvalidateOptionsMenu(); - // this is done at the end of the animation otherwise + // invokeBeam is available from API 21 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + mActionNfc.setVisibility(View.VISIBLE); + } else { + mActionNfc.setVisibility(View.GONE); } + mFab.setVisibility(View.VISIBLE); + mFab.setIconDrawable(getResources().getDrawable(R.drawable.ic_repeat_white_24dp)); + } else { + mActionEncryptFile.setVisibility(View.VISIBLE); + mActionEncryptText.setVisibility(View.VISIBLE); + mQrCodeLayout.setVisibility(View.GONE); + mActionNfc.setVisibility(View.GONE); - AsyncTask<Long, Void, Bitmap> photoTask = - new AsyncTask<Long, Void, Bitmap>() { - protected Bitmap doInBackground(Long... mMasterKeyId) { - return ContactHelper.loadPhotoByMasterKeyId(getContentResolver(), mMasterKeyId[0], true); - } - - protected void onPostExecute(Bitmap photo) { - mPhoto.setImageBitmap(photo); - mPhoto.setVisibility(View.VISIBLE); - } - }; - - // Note: order is important - int color; - if (mIsRevoked) { - mStatusText.setText(R.string.view_key_revoked); + if (mIsVerified) { + mStatusText.setText(R.string.view_key_verified); mStatusImage.setVisibility(View.VISIBLE); KeyFormattingUtils.setStatusImage(this, mStatusImage, mStatusText, - State.REVOKED, R.color.icons, true); - color = getResources().getColor(R.color.android_red_light); + State.VERIFIED, R.color.icons, true); + color = getResources().getColor(R.color.primary); + photoTask.execute(mMasterKeyId); - mActionEncryptFile.setVisibility(View.GONE); - mActionEncryptText.setVisibility(View.GONE); - mActionNfc.setVisibility(View.GONE); mFab.setVisibility(View.GONE); - mQrCodeLayout.setVisibility(View.GONE); - } else if (mIsExpired) { - if (mIsSecret) { - mStatusText.setText(R.string.view_key_expired_secret); - } else { - mStatusText.setText(R.string.view_key_expired); - } + } else { + mStatusText.setText(R.string.view_key_unverified); mStatusImage.setVisibility(View.VISIBLE); KeyFormattingUtils.setStatusImage(this, mStatusImage, mStatusText, - State.EXPIRED, R.color.icons, true); - color = getResources().getColor(R.color.android_red_light); + State.UNVERIFIED, R.color.icons, true); + color = getResources().getColor(R.color.android_orange_light); - mActionEncryptFile.setVisibility(View.GONE); - mActionEncryptText.setVisibility(View.GONE); - mActionNfc.setVisibility(View.GONE); - mFab.setVisibility(View.GONE); - mQrCodeLayout.setVisibility(View.GONE); - } else if (mIsSecret) { - mStatusText.setText(R.string.view_key_my_key); - mStatusImage.setVisibility(View.GONE); - color = getResources().getColor(R.color.primary); - // reload qr code only if the fingerprint changed - if (!mFingerprint.equals(mQrCodeLoaded)) { - loadQrCode(mFingerprint); - } - photoTask.execute(mMasterKeyId); - mQrCodeLayout.setVisibility(View.VISIBLE); - - // and place leftOf qr code - RelativeLayout.LayoutParams nameParams = (RelativeLayout.LayoutParams) - mName.getLayoutParams(); - // remove right margin - nameParams.setMargins(FormattingUtils.dpToPx(this, 48), 0, 0, 0); - if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - nameParams.setMarginEnd(0); - } - nameParams.addRule(RelativeLayout.LEFT_OF, R.id.view_key_qr_code_layout); - mName.setLayoutParams(nameParams); - - RelativeLayout.LayoutParams statusParams = (RelativeLayout.LayoutParams) - mStatusText.getLayoutParams(); - statusParams.setMargins(FormattingUtils.dpToPx(this, 48), 0, 0, 0); - if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - statusParams.setMarginEnd(0); - } - statusParams.addRule(RelativeLayout.LEFT_OF, R.id.view_key_qr_code_layout); - mStatusText.setLayoutParams(statusParams); - - mActionEncryptFile.setVisibility(View.VISIBLE); - mActionEncryptText.setVisibility(View.VISIBLE); - - // invokeBeam is available from API 21 - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - mActionNfc.setVisibility(View.VISIBLE); - } else { - mActionNfc.setVisibility(View.GONE); - } mFab.setVisibility(View.VISIBLE); - mFab.setIconDrawable(getResources().getDrawable(R.drawable.ic_repeat_white_24dp)); - } else { - mActionEncryptFile.setVisibility(View.VISIBLE); - mActionEncryptText.setVisibility(View.VISIBLE); - mQrCodeLayout.setVisibility(View.GONE); - mActionNfc.setVisibility(View.GONE); - - if (mIsVerified) { - mStatusText.setText(R.string.view_key_verified); - mStatusImage.setVisibility(View.VISIBLE); - KeyFormattingUtils.setStatusImage(this, mStatusImage, mStatusText, - State.VERIFIED, R.color.icons, true); - color = getResources().getColor(R.color.primary); - photoTask.execute(mMasterKeyId); - - mFab.setVisibility(View.GONE); - } else { - mStatusText.setText(R.string.view_key_unverified); - mStatusImage.setVisibility(View.VISIBLE); - KeyFormattingUtils.setStatusImage(this, mStatusImage, mStatusText, - State.UNVERIFIED, R.color.icons, true); - color = getResources().getColor(R.color.android_orange_light); - - mFab.setVisibility(View.VISIBLE); - } } + } - if (mPreviousColor == 0 || mPreviousColor == color) { - mStatusBar.setBackgroundColor(color); - mBigToolbar.setBackgroundColor(color); - mPreviousColor = color; - } else { - ObjectAnimator colorFade1 = - ObjectAnimator.ofObject(mStatusBar, "backgroundColor", - new ArgbEvaluator(), mPreviousColor, color); - ObjectAnimator colorFade2 = - ObjectAnimator.ofObject(mBigToolbar, "backgroundColor", - new ArgbEvaluator(), mPreviousColor, color); - - colorFade1.setDuration(1200); - colorFade2.setDuration(1200); - colorFade1.start(); - colorFade2.start(); - mPreviousColor = color; - } + if (mPreviousColor == 0 || mPreviousColor == color) { + mStatusBar.setBackgroundColor(color); + mBigToolbar.setBackgroundColor(color); + mPreviousColor = color; + } else { + ObjectAnimator colorFade1 = + ObjectAnimator.ofObject(mStatusBar, "backgroundColor", + new ArgbEvaluator(), mPreviousColor, color); + ObjectAnimator colorFade2 = + ObjectAnimator.ofObject(mBigToolbar, "backgroundColor", + new ArgbEvaluator(), mPreviousColor, color); + + colorFade1.setDuration(1200); + colorFade2.setDuration(1200); + colorFade1.start(); + colorFade2.start(); + mPreviousColor = color; + } - //noinspection deprecation - mStatusImage.setAlpha(80); + //noinspection deprecation + mStatusImage.setAlpha(80); + + break; - break; - } } } } @@ -977,4 +1002,5 @@ public class ViewKeyActivity extends BaseNfcActivity implements public void onLoaderReset(Loader<Cursor> loader) { } -}
\ No newline at end of file + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvUserIdsFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvUserIdsFragment.java index 7bfebaf62..ad437f924 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvUserIdsFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvUserIdsFragment.java @@ -133,7 +133,7 @@ public class ViewKeyAdvUserIdsFragment extends LoaderFragment implements case LOADER_ID_USER_IDS: { Uri baseUri = UserPackets.buildUserIdsUri(mDataUri); return new CursorLoader(getActivity(), baseUri, - UserIdsAdapter.USER_IDS_PROJECTION, null, null, null); + UserIdsAdapter.USER_PACKETS_PROJECTION, null, null, null); } default: diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java index b92163b59..c01a94286 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java @@ -18,29 +18,42 @@ package org.sufficientlysecure.keychain.ui; +import java.io.IOException; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.graphics.Bitmap; import android.net.Uri; +import android.os.Build; import android.os.Bundle; +import android.os.Handler; import android.provider.ContactsContract; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.support.v7.widget.CardView; +import android.transition.Fade; +import android.transition.Transition; +import android.transition.TransitionInflater; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ListView; +import android.widget.TextView; import android.widget.*; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; +import org.sufficientlysecure.keychain.ui.adapter.LinkedIdsAdapter; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter; import org.sufficientlysecure.keychain.ui.dialog.UserIdInfoDialogFragment; +import org.sufficientlysecure.keychain.ui.linked.LinkedIdViewFragment; +import org.sufficientlysecure.keychain.ui.linked.LinkedIdViewFragment.OnIdentityLoadedListener; import org.sufficientlysecure.keychain.util.ContactHelper; import org.sufficientlysecure.keychain.util.Log; @@ -64,6 +77,7 @@ public class ViewKeyFragment extends LoaderFragment implements private static final int LOADER_ID_UNIFIED = 0; private static final int LOADER_ID_USER_IDS = 1; private static final int LOADER_ID_LINKED_CONTACT = 2; + private static final int LOADER_ID_LINKED_IDS = 3; private static final String LOADER_EXTRA_LINKED_CONTACT_MASTER_KEY_ID = "loader_linked_contact_master_key_id"; @@ -71,8 +85,13 @@ public class ViewKeyFragment extends LoaderFragment implements = "loader_linked_contact_is_secret"; private UserIdsAdapter mUserIdsAdapter; + private LinkedIdsAdapter mLinkedIdsAdapter; private Uri mDataUri; + private ListView mLinkedIds; + private CardView mLinkedIdsCard; + private byte[] mFingerprint; + private TextView mLinkedIdsExpander; /** * Creates new instance of this fragment @@ -93,6 +112,11 @@ public class ViewKeyFragment extends LoaderFragment implements View view = inflater.inflate(R.layout.view_key_fragment, getContainer()); mUserIds = (ListView) view.findViewById(R.id.view_key_user_ids); + mLinkedIdsCard = (CardView) view.findViewById(R.id.card_linked_ids); + + mLinkedIds = (ListView) view.findViewById(R.id.view_key_linked_ids); + + mLinkedIdsExpander = (TextView) view.findViewById(R.id.view_key_linked_ids_expander); mUserIds.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override @@ -100,6 +124,12 @@ public class ViewKeyFragment extends LoaderFragment implements showUserIdInfo(position); } }); + mLinkedIds.setOnItemClickListener(new OnItemClickListener() { + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + showLinkedId(position); + } + }); mSystemContactCard = (CardView) view.findViewById(R.id.linked_system_contact_card); mSystemContactLayout = (LinearLayout) view.findViewById(R.id.system_contact_layout); @@ -109,6 +139,47 @@ public class ViewKeyFragment extends LoaderFragment implements return root; } + private void showLinkedId(final int position) { + final LinkedIdViewFragment frag; + try { + frag = mLinkedIdsAdapter.getLinkedIdFragment(mDataUri, position, mFingerprint); + } catch (IOException e) { + e.printStackTrace(); + return; + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + Transition trans = TransitionInflater.from(getActivity()) + .inflateTransition(R.transition.linked_id_card_trans); + // setSharedElementReturnTransition(trans); + setExitTransition(new Fade()); + frag.setSharedElementEnterTransition(trans); + } + + getFragmentManager().beginTransaction() + .add(R.id.view_key_fragment, frag) + .hide(frag) + .commit(); + + frag.setOnIdentityLoadedListener(new OnIdentityLoadedListener() { + @Override + public void onIdentityLoaded() { + new Handler().post(new Runnable() { + @Override + public void run() { + getFragmentManager().beginTransaction() + .show(frag) + .addSharedElement(mLinkedIdsCard, "card_linked_ids") + .remove(ViewKeyFragment.this) + .addToBackStack("linked_id") + .commit(); + } + }); + } + }); + + } + private void showUserIdInfo(final int position) { if (!mIsSecret) { final boolean isRevoked = mUserIdsAdapter.getIsRevoked(position); @@ -129,8 +200,6 @@ public class ViewKeyFragment extends LoaderFragment implements * Hides card if no linked system contact exists. Sets name, picture * and onClickListener for the linked system contact's layout. * In the case of a secret key, "me" (own profile) contact details are loaded. - * - * @param contactId */ private void loadLinkedSystemContact(final long contactId) { // contact doesn't exist, stop @@ -188,7 +257,6 @@ public class ViewKeyFragment extends LoaderFragment implements * ContactsContract.Contact table) * * @param contactId _ID for row in ContactsContract.Contacts table - * @param context */ private void launchContactActivity(final long contactId, Context context) { Intent intent = new Intent(Intent.ACTION_VIEW); @@ -225,12 +293,17 @@ public class ViewKeyFragment extends LoaderFragment implements }; static final int INDEX_MASTER_KEY_ID = 1; + @SuppressWarnings("unused") static final int INDEX_USER_ID = 2; + @SuppressWarnings("unused") static final int INDEX_IS_REVOKED = 3; + @SuppressWarnings("unused") static final int INDEX_IS_EXPIRED = 4; + @SuppressWarnings("unused") static final int INDEX_VERIFIED = 5; static final int INDEX_HAS_ANY_SECRET = 6; static final int INDEX_FINGERPRINT = 7; + @SuppressWarnings("unused") static final int INDEX_HAS_ENCRYPT = 8; private static final String[] RAWCONTACT_PROJECTION = { @@ -246,7 +319,6 @@ public class ViewKeyFragment extends LoaderFragment implements // Prepare the loaders. Either re-connect with an existing ones, // or start new ones. - // TODO Is this loader the same as the one in the activity? getLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this); } @@ -259,8 +331,14 @@ public class ViewKeyFragment extends LoaderFragment implements Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(mDataUri); return new CursorLoader(getActivity(), baseUri, UNIFIED_PROJECTION, null, null, null); } - case LOADER_ID_USER_IDS: + + case LOADER_ID_USER_IDS: { return UserIdsAdapter.createLoader(getActivity(), mDataUri); + } + + case LOADER_ID_LINKED_IDS: { + return LinkedIdsAdapter.createLoader(getActivity(), mDataUri); + } //we need a separate loader for linked contact to ensure refreshing on verification case LOADER_ID_LINKED_CONTACT: { @@ -310,12 +388,18 @@ public class ViewKeyFragment extends LoaderFragment implements if (data.moveToFirst()) { mIsSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0; + mFingerprint = data.getBlob(INDEX_FINGERPRINT); // load user ids after we know if it's a secret key mUserIdsAdapter = new UserIdsAdapter(getActivity(), null, 0, !mIsSecret, null); mUserIds.setAdapter(mUserIdsAdapter); getLoaderManager().initLoader(LOADER_ID_USER_IDS, null, this); + mLinkedIdsAdapter = + new LinkedIdsAdapter(getActivity(), null, 0, mIsSecret, mLinkedIdsExpander); + mLinkedIds.setAdapter(mLinkedIdsAdapter); + getLoaderManager().initLoader(LOADER_ID_LINKED_IDS, null, this); + long masterKeyId = data.getLong(INDEX_MASTER_KEY_ID); // we need to load linked contact here to prevent lag introduced by loader // for the linked contact @@ -340,6 +424,12 @@ public class ViewKeyFragment extends LoaderFragment implements break; } + case LOADER_ID_LINKED_IDS: { + mLinkedIdsAdapter.swapCursor(data); + mLinkedIdsCard.setVisibility(mLinkedIdsAdapter.getCount() > 0 ? View.VISIBLE : View.GONE); + break; + } + case LOADER_ID_LINKED_CONTACT: { if (data.moveToFirst()) {// if we have a linked contact long contactId = data.getLong(INDEX_CONTACT_ID); @@ -363,6 +453,11 @@ public class ViewKeyFragment extends LoaderFragment implements mUserIdsAdapter.swapCursor(null); break; } + case LOADER_ID_LINKED_IDS: { + mLinkedIdsCard.setVisibility(View.GONE); + mLinkedIdsAdapter.swapCursor(null); + break; + } } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java index eef44a94b..3dbae09b6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java @@ -18,15 +18,12 @@ package org.sufficientlysecure.keychain.ui.adapter; -import java.util.Calendar; import java.util.Date; -import java.util.TimeZone; import android.content.Context; import android.database.Cursor; import android.graphics.PorterDuff; import android.support.v4.widget.CursorAdapter; -import android.text.format.DateFormat; import android.text.format.DateUtils; import android.view.LayoutInflater; import android.view.View; @@ -173,11 +170,11 @@ public class KeyAdapter extends CursorAdapter { String dateTime = DateUtils.formatDateTime(context, cursor.getLong(INDEX_CREATION) * 1000, DateUtils.FORMAT_SHOW_DATE + | DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_ABBREV_MONTH); - mCreationDate.setText(context.getString(R.string.label_key_created, - dateTime)); + dateTime)); mCreationDate.setVisibility(View.VISIBLE); } else { mCreationDate.setVisibility(View.GONE); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/LinkedIdsAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/LinkedIdsAdapter.java new file mode 100644 index 000000000..d50c3fd51 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/LinkedIdsAdapter.java @@ -0,0 +1,233 @@ +/* + * Copyright (C) 2014-2015 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui.adapter; + +import android.animation.ObjectAnimator; +import android.app.Activity; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.support.v4.content.CursorLoader; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.linked.LinkedIdentity; +import org.sufficientlysecure.keychain.linked.UriAttribute; +import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; +import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; +import org.sufficientlysecure.keychain.ui.linked.LinkedIdViewFragment; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; +import org.sufficientlysecure.keychain.ui.util.SubtleAttentionSeeker; +import org.sufficientlysecure.keychain.util.FilterCursorWrapper; + +import java.io.IOException; +import java.util.WeakHashMap; + +public class LinkedIdsAdapter extends UserAttributesAdapter { + private final boolean mIsSecret; + protected LayoutInflater mInflater; + WeakHashMap<Integer,UriAttribute> mLinkedIdentityCache = new WeakHashMap<>(); + + private Cursor mUnfilteredCursor; + + private TextView mExpander; + + public LinkedIdsAdapter(Context context, Cursor c, int flags, + boolean isSecret, TextView expander) { + super(context, c, flags); + mInflater = LayoutInflater.from(context); + mIsSecret = isSecret; + + if (expander != null) { + expander.setVisibility(View.GONE); + /* don't show an expander (maybe in some sort of advanced view?) + mExpander = expander; + mExpander.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + showUnfiltered(); + } + }); + */ + } + } + + @Override + public Cursor swapCursor(Cursor cursor) { + if (cursor == null) { + mUnfilteredCursor = null; + return super.swapCursor(null); + } + mUnfilteredCursor = cursor; + FilterCursorWrapper filteredCursor = new FilterCursorWrapper(cursor) { + @Override + public boolean isVisible(Cursor cursor) { + UriAttribute id = getItemAtPosition(cursor); + return id instanceof LinkedIdentity; + } + }; + + if (mExpander != null) { + int hidden = filteredCursor.getHiddenCount(); + if (hidden == 0) { + mExpander.setVisibility(View.GONE); + } else { + mExpander.setVisibility(View.VISIBLE); + mExpander.setText(mContext.getResources().getQuantityString( + R.plurals.linked_id_expand, hidden)); + } + } + + return super.swapCursor(filteredCursor); + } + + private void showUnfiltered() { + mExpander.setVisibility(View.GONE); + super.swapCursor(mUnfilteredCursor); + } + + @Override + public void bindView(View view, Context context, Cursor cursor) { + + ViewHolder holder = (ViewHolder) view.getTag(); + + if (!mIsSecret) { + int isVerified = cursor.getInt(INDEX_VERIFIED); + switch (isVerified) { + case Certs.VERIFIED_SECRET: + KeyFormattingUtils.setStatusImage(mContext, holder.vVerified, + null, State.VERIFIED, KeyFormattingUtils.DEFAULT_COLOR); + break; + case Certs.VERIFIED_SELF: + KeyFormattingUtils.setStatusImage(mContext, holder.vVerified, + null, State.UNVERIFIED, KeyFormattingUtils.DEFAULT_COLOR); + break; + default: + KeyFormattingUtils.setStatusImage(mContext, holder.vVerified, + null, State.INVALID, KeyFormattingUtils.DEFAULT_COLOR); + break; + } + } + + UriAttribute id = getItemAtPosition(cursor); + holder.setData(mContext, id); + + } + + public UriAttribute getItemAtPosition(Cursor cursor) { + int rank = cursor.getInt(INDEX_RANK); + Log.d(Constants.TAG, "requested rank: " + rank); + + UriAttribute ret = mLinkedIdentityCache.get(rank); + if (ret != null) { + Log.d(Constants.TAG, "cached!"); + return ret; + } + Log.d(Constants.TAG, "not cached!"); + + try { + byte[] data = cursor.getBlob(INDEX_ATTRIBUTE_DATA); + ret = LinkedIdentity.fromAttributeData(data); + mLinkedIdentityCache.put(rank, ret); + return ret; + } catch (IOException e) { + Log.e(Constants.TAG, "could not read linked identity subpacket data", e); + return null; + } + } + + @Override + public UriAttribute getItem(int position) { + Cursor cursor = getCursor(); + cursor.moveToPosition(position); + return getItemAtPosition(cursor); + } + + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + View v = mInflater.inflate(R.layout.linked_id_item, null); + ViewHolder holder = new ViewHolder(v); + v.setTag(holder); + return v; + } + + // don't show revoked user ids, irrelevant for average users + public static final String LINKED_IDS_WHERE = UserPackets.IS_REVOKED + " = 0"; + + public static CursorLoader createLoader(Activity activity, Uri dataUri) { + Uri baseUri = UserPackets.buildLinkedIdsUri(dataUri); + return new CursorLoader(activity, baseUri, + UserIdsAdapter.USER_PACKETS_PROJECTION, LINKED_IDS_WHERE, null, null); + } + + public LinkedIdViewFragment getLinkedIdFragment(Uri baseUri, + int position, byte[] fingerprint) throws IOException { + Cursor c = getCursor(); + c.moveToPosition(position); + int rank = c.getInt(UserIdsAdapter.INDEX_RANK); + + Uri dataUri = UserPackets.buildLinkedIdsUri(baseUri); + return LinkedIdViewFragment.newInstance(dataUri, rank, mIsSecret, fingerprint); + } + + public static class ViewHolder { + final public ImageView vVerified; + final public ImageView vIcon; + final public TextView vTitle; + final public TextView vComment; + + public ViewHolder(View view) { + vVerified = (ImageView) view.findViewById(R.id.linked_id_certified_icon); + vIcon = (ImageView) view.findViewById(R.id.linked_id_type_icon); + vTitle = (TextView) view.findViewById(R.id.linked_id_title); + vComment = (TextView) view.findViewById(R.id.linked_id_comment); + } + + public void setData(Context context, UriAttribute id) { + + vTitle.setText(id.getDisplayTitle(context)); + + String comment = id.getDisplayComment(context); + if (comment != null) { + vComment.setVisibility(View.VISIBLE); + vComment.setText(comment); + } else { + vComment.setVisibility(View.GONE); + } + + vIcon.setImageResource(id.getDisplayIcon()); + + } + + public void seekAttention() { + ObjectAnimator anim = SubtleAttentionSeeker.tintText(vComment, 1000); + anim.setStartDelay(200); + anim.start(); + } + + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/LinkedIdsCertAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/LinkedIdsCertAdapter.java new file mode 100644 index 000000000..5ecd9f408 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/LinkedIdsCertAdapter.java @@ -0,0 +1,57 @@ +package org.sufficientlysecure.keychain.ui.adapter; + + +import android.app.Activity; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.support.v4.content.CursorLoader; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CursorAdapter; + +import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; + + +public class LinkedIdsCertAdapter extends CursorAdapter { + + public static final String[] USER_CERTS_PROJECTION = new String[]{ + UserPackets._ID, + UserPackets.TYPE, + UserPackets.USER_ID, + UserPackets.ATTRIBUTE_DATA, + UserPackets.RANK, + UserPackets.VERIFIED, + UserPackets.IS_PRIMARY, + UserPackets.IS_REVOKED + }; + protected static final int INDEX_ID = 0; + protected static final int INDEX_TYPE = 1; + protected static final int INDEX_USER_ID = 2; + protected static final int INDEX_ATTRIBUTE_DATA = 3; + protected static final int INDEX_RANK = 4; + protected static final int INDEX_VERIFIED = 5; + protected static final int INDEX_IS_PRIMARY = 6; + protected static final int INDEX_IS_REVOKED = 7; + + public LinkedIdsCertAdapter(Context context, Cursor c, int flags) { + super(context, c, flags); + } + + @Override + public void bindView(View view, Context context, Cursor cursor) { + + } + + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + return null; + } + + public static CursorLoader createLoader(Activity activity, Uri dataUri) { + Uri baseUri = UserPackets.buildLinkedIdsUri(dataUri); + return new CursorLoader(activity, baseUri, + UserIdsAdapter.USER_PACKETS_PROJECTION, null, null, null); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/MultiUserIdsAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/MultiUserIdsAdapter.java index 028f0fc9c..5218273a0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/MultiUserIdsAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/MultiUserIdsAdapter.java @@ -168,7 +168,7 @@ public class MultiUserIdsAdapter extends CursorAdapter { CertifyAction action = actions.get(keyId); if (actions.get(keyId) == null) { - actions.put(keyId, new CertifyAction(keyId, uids)); + actions.put(keyId, new CertifyAction(keyId, uids, null)); } else { action.mUserIds.addAll(uids); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java index a6cb52977..6bbf41a88 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java @@ -137,9 +137,9 @@ abstract public class SelectKeyCursorAdapter extends CursorAdapter { String dateTime = DateUtils.formatDateTime(context, cursor.getLong(mIndexCreation) * 1000, DateUtils.FORMAT_SHOW_DATE + | DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_ABBREV_MONTH); - h.creation.setText(context.getString(R.string.label_key_created, dateTime)); h.creation.setVisibility(View.VISIBLE); } else { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserAttributesAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserAttributesAdapter.java index 457083770..e0abaf4b0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserAttributesAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserAttributesAdapter.java @@ -8,22 +8,24 @@ import android.view.View; import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; public abstract class UserAttributesAdapter extends CursorAdapter { - public static final String[] USER_IDS_PROJECTION = new String[]{ + public static final String[] USER_PACKETS_PROJECTION = new String[]{ UserPackets._ID, UserPackets.TYPE, UserPackets.USER_ID, + UserPackets.ATTRIBUTE_DATA, UserPackets.RANK, UserPackets.VERIFIED, UserPackets.IS_PRIMARY, UserPackets.IS_REVOKED }; - protected static final int INDEX_ID = 0; - protected static final int INDEX_TYPE = 1; - protected static final int INDEX_USER_ID = 2; - protected static final int INDEX_RANK = 3; - protected static final int INDEX_VERIFIED = 4; - protected static final int INDEX_IS_PRIMARY = 5; - protected static final int INDEX_IS_REVOKED = 6; + public static final int INDEX_ID = 0; + public static final int INDEX_TYPE = 1; + public static final int INDEX_USER_ID = 2; + public static final int INDEX_ATTRIBUTE_DATA = 3; + public static final int INDEX_RANK = 4; + public static final int INDEX_VERIFIED = 5; + public static final int INDEX_IS_PRIMARY = 6; + public static final int INDEX_IS_REVOKED = 7; public UserAttributesAdapter(Context context, Cursor c, int flags) { super(context, c, flags); @@ -46,4 +48,5 @@ public abstract class UserAttributesAdapter extends CursorAdapter { mCursor.moveToPosition(position); return mCursor.getInt(INDEX_VERIFIED); } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java index d1103ac1f..c68c078ad 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java @@ -188,7 +188,7 @@ public class UserIdsAdapter extends UserAttributesAdapter { public static CursorLoader createLoader(Activity activity, Uri dataUri) { Uri baseUri = UserPackets.buildUserIdsUri(dataUri); return new CursorLoader(activity, baseUri, - UserIdsAdapter.USER_IDS_PROJECTION, USER_IDS_WHERE, null, null); + UserIdsAdapter.USER_PACKETS_PROJECTION, USER_IDS_WHERE, null, null); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateDnsStep1Fragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateDnsStep1Fragment.java new file mode 100644 index 000000000..8062428e3 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateDnsStep1Fragment.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui.linked; + +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.text.Editable; +import android.text.TextWatcher; +import android.util.Patterns; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.EditText; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.linked.resources.DnsResource; + +public class LinkedIdCreateDnsStep1Fragment extends Fragment { + + LinkedIdWizard mLinkedIdWizard; + + EditText mEditDns; + + /** + * Creates new instance of this fragment + */ + public static LinkedIdCreateDnsStep1Fragment newInstance() { + LinkedIdCreateDnsStep1Fragment frag = new LinkedIdCreateDnsStep1Fragment(); + + Bundle args = new Bundle(); + frag.setArguments(args); + + return frag; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + mLinkedIdWizard = (LinkedIdWizard) getActivity(); + + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + final View view = inflater.inflate(R.layout.linked_create_dns_fragment_step1, container, false); + + view.findViewById(R.id.next_button).setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + + String uri = mEditDns.getText().toString(); + + if (!checkUri(uri)) { + mEditDns.setError("Please enter a valid domain name!"); + return; + } + + String proofText = DnsResource.generateText( + mLinkedIdWizard.mFingerprint); + + LinkedIdCreateDnsStep2Fragment frag = + LinkedIdCreateDnsStep2Fragment.newInstance(uri, proofText); + + mLinkedIdWizard.loadFragment(null, frag, LinkedIdWizard.FRAG_ACTION_TO_RIGHT); + + } + }); + + view.findViewById(R.id.back_button).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mLinkedIdWizard.loadFragment(null, null, LinkedIdWizard.FRAG_ACTION_TO_LEFT); + } + }); + + mEditDns = (EditText) view.findViewById(R.id.linked_create_dns_domain); + + mEditDns.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) { + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) { + } + + @Override + public void afterTextChanged(Editable editable) { + String uri = editable.toString(); + if (uri.length() > 0) { + if (checkUri(uri)) { + mEditDns.setCompoundDrawablesWithIntrinsicBounds(0, 0, + R.drawable.uid_mail_ok, 0); + } else { + mEditDns.setCompoundDrawablesWithIntrinsicBounds(0, 0, + R.drawable.uid_mail_bad, 0); + } + } else { + // remove drawable if email is empty + mEditDns.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); + } + } + }); + + return view; + } + + private static boolean checkUri(String uri) { + return Patterns.DOMAIN_NAME.matcher(uri).matches(); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateDnsStep2Fragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateDnsStep2Fragment.java new file mode 100644 index 000000000..e0e6976ee --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateDnsStep2Fragment.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui.linked; + +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.TextView; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; +import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; +import org.sufficientlysecure.keychain.linked.LinkedTokenResource; +import org.sufficientlysecure.keychain.linked.resources.DnsResource; +import org.sufficientlysecure.keychain.ui.util.Notify; +import org.sufficientlysecure.keychain.ui.util.Notify.Style; + +import java.io.FileNotFoundException; +import java.io.PrintWriter; + +public class LinkedIdCreateDnsStep2Fragment extends LinkedIdCreateFinalFragment { + + private static final int REQUEST_CODE_OUTPUT = 0x00007007; + + public static final String DOMAIN = "domain", TEXT = "text"; + + TextView mTextView; + + String mResourceDomain; + String mResourceString; + + public static LinkedIdCreateDnsStep2Fragment newInstance + (String uri, String proofText) { + + LinkedIdCreateDnsStep2Fragment frag = new LinkedIdCreateDnsStep2Fragment(); + + Bundle args = new Bundle(); + args.putString(DOMAIN, uri); + args.putString(TEXT, proofText); + frag.setArguments(args); + + return frag; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mResourceDomain = getArguments().getString(DOMAIN); + mResourceString = getArguments().getString(TEXT); + + } + + @Override + protected View newView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + return inflater.inflate(R.layout.linked_create_dns_fragment_step2, container, false); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = super.onCreateView(inflater, container, savedInstanceState); + + if (view != null) { + + view.findViewById(R.id.button_send).setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + proofSend(); + } + }); + + view.findViewById(R.id.button_save).setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + proofToClipboard(); + } + }); + + mTextView = (TextView) view.findViewById(R.id.linked_create_dns_text); + mTextView.setText(mResourceString); + + } + + return view; + } + + @Override + LinkedTokenResource getResource(OperationLog log) { + return DnsResource.createNew(mResourceDomain); + } + + private void proofSend () { + Intent sendIntent = new Intent(); + sendIntent.setAction(Intent.ACTION_SEND); + sendIntent.putExtra(Intent.EXTRA_TEXT, mResourceString); + sendIntent.setType("text/plain"); + startActivity(sendIntent); + } + + private void proofToClipboard() { + ClipboardReflection.copyToClipboard(getActivity(), mResourceString); + Notify.create(getActivity(), R.string.linked_text_clipboard, Notify.Style.OK).show(); + } + + private void saveFile(Uri uri) { + try { + PrintWriter out = + new PrintWriter(getActivity().getContentResolver().openOutputStream(uri)); + out.print(mResourceString); + if (out.checkError()) { + Notify.create(getActivity(), "Error writing file!", Style.ERROR).show(); + } + } catch (FileNotFoundException e) { + Notify.create(getActivity(), "File could not be opened for writing!", Style.ERROR).show(); + e.printStackTrace(); + } + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + // For saving a file + case REQUEST_CODE_OUTPUT: + if (data == null) { + return; + } + Uri uri = data.getData(); + saveFile(uri); + break; + default: + super.onActivityResult(requestCode, resultCode, data); + } + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateFinalFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateFinalFragment.java new file mode 100644 index 000000000..4927064e4 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateFinalFragment.java @@ -0,0 +1,250 @@ +package org.sufficientlysecure.keychain.ui.linked; + +import android.app.ProgressDialog; +import android.content.Intent; +import android.graphics.PorterDuff; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.Message; +import android.os.Messenger; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.ViewAnimator; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.operations.results.LinkedVerifyResult; +import org.sufficientlysecure.keychain.operations.results.OperationResult; +import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; +import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute; +import org.sufficientlysecure.keychain.linked.LinkedTokenResource; +import org.sufficientlysecure.keychain.linked.LinkedIdentity; +import org.sufficientlysecure.keychain.service.KeychainIntentService; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel; +import org.sufficientlysecure.keychain.service.ServiceProgressHandler; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment; +import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment.ServiceType; +import org.sufficientlysecure.keychain.ui.util.Notify; + +public abstract class LinkedIdCreateFinalFragment extends CryptoOperationFragment { + + protected LinkedIdWizard mLinkedIdWizard; + + private ImageView mVerifyImage; + private TextView mVerifyStatus; + private ViewAnimator mVerifyAnimator; + + // This is a resource, set AFTER it has been verified + LinkedTokenResource mVerifiedResource = null; + private ViewAnimator mVerifyButtonAnimator; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mLinkedIdWizard = (LinkedIdWizard) getActivity(); + } + + protected abstract View newView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState); + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + final View view = newView(inflater, container, savedInstanceState); + + view.findViewById(R.id.next_button).setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + cryptoOperation(); + } + }); + + view.findViewById(R.id.back_button).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mLinkedIdWizard.loadFragment(null, null, LinkedIdWizard.FRAG_ACTION_TO_LEFT); + } + }); + + mVerifyAnimator = (ViewAnimator) view.findViewById(R.id.verify_progress); + mVerifyImage = (ImageView) view.findViewById(R.id.verify_image); + mVerifyStatus = (TextView) view.findViewById(R.id.verify_status); + mVerifyButtonAnimator = (ViewAnimator) view.findViewById(R.id.verify_buttons); + + view.findViewById(R.id.button_verify).setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + proofVerify(); + } + }); + + view.findViewById(R.id.button_retry).setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + proofVerify(); + } + }); + + setVerifyProgress(false, null); + mVerifyStatus.setText(R.string.linked_verify_pending); + + return view; + } + + abstract LinkedTokenResource getResource(OperationLog log); + + private void setVerifyProgress(boolean on, Boolean success) { + if (success == null) { + mVerifyStatus.setText(R.string.linked_verifying); + displayButton(on ? 2 : 0); + } else if (success) { + mVerifyStatus.setText(R.string.linked_verify_success); + mVerifyImage.setImageResource(R.drawable.status_signature_verified_cutout_24dp); + mVerifyImage.setColorFilter(getResources().getColor(R.color.android_green_dark), + PorterDuff.Mode.SRC_IN); + displayButton(2); + } else { + mVerifyStatus.setText(R.string.linked_verify_error); + mVerifyImage.setImageResource(R.drawable.status_signature_unknown_cutout_24dp); + mVerifyImage.setColorFilter(getResources().getColor(R.color.android_red_dark), + PorterDuff.Mode.SRC_IN); + displayButton(1); + } + mVerifyAnimator.setDisplayedChild(on ? 1 : 0); + } + + public void displayButton(int button) { + if (mVerifyButtonAnimator.getDisplayedChild() == button) { + return; + } + mVerifyButtonAnimator.setDisplayedChild(button); + } + + protected void proofVerify() { + setVerifyProgress(true, null); + + new AsyncTask<Void,Void,LinkedVerifyResult>() { + + @Override + protected LinkedVerifyResult doInBackground(Void... params) { + long timer = System.currentTimeMillis(); + + OperationLog log = new OperationLog(); + LinkedTokenResource resource = getResource(log); + if (resource == null) { + return new LinkedVerifyResult(LinkedVerifyResult.RESULT_ERROR, log); + } + + LinkedVerifyResult result = resource.verify(getActivity(), mLinkedIdWizard.mFingerprint); + + // ux flow: this operation should take at last a second + timer = System.currentTimeMillis() -timer; + if (timer < 1000) try { + Thread.sleep(1000 -timer); + } catch (InterruptedException e) { + // never mind + } + + if (result.success()) { + mVerifiedResource = resource; + } + return result; + } + + @Override + protected void onPostExecute(LinkedVerifyResult result) { + super.onPostExecute(result); + if (result.success()) { + setVerifyProgress(false, true); + } else { + setVerifyProgress(false, false); + // on error, show error message + result.createNotify(getActivity()).show(LinkedIdCreateFinalFragment.this); + } + } + }.execute(); + + } + + protected void cryptoOperation(CryptoInputParcel cryptoInput) { + + if (mVerifiedResource == null) { + Notify.create(getActivity(), R.string.linked_need_verify, Notify.Style.ERROR) + .show(LinkedIdCreateFinalFragment.this); + return; + } + + ServiceProgressHandler saveHandler = new ServiceProgressHandler( + getActivity(), + getString(R.string.progress_saving), + ProgressDialog.STYLE_HORIZONTAL, + true, ServiceType.KEYCHAIN_INTENT) { + + public void handleMessage(Message message) { + // handle messages by standard KeychainIntentServiceHandler first + super.handleMessage(message); + + // handle pending messages + if (handlePendingMessage(message)) { + return; + } + + if (message.arg1 == MessageStatus.OKAY.ordinal()) { + + // get returned data bundle + Bundle returnData = message.getData(); + if (returnData == null) { + return; + } + final OperationResult result = + returnData.getParcelable(OperationResult.EXTRA_RESULT); + if (result == null) { + return; + } + + // if bad -> display here! + if (!result.success()) { + result.createNotify(getActivity()).show(LinkedIdCreateFinalFragment.this); + return; + } + + getActivity().finish(); + + } + } + }; + + SaveKeyringParcel skp = + new SaveKeyringParcel(mLinkedIdWizard.mMasterKeyId, mLinkedIdWizard.mFingerprint); + + WrappedUserAttribute ua = + LinkedIdentity.fromResource(mVerifiedResource).toUserAttribute(); + + skp.mAddUserAttribute.add(ua); + + // Send all information needed to service to import key in other thread + Intent intent = new Intent(getActivity(), KeychainIntentService.class); + intent.setAction(KeychainIntentService.ACTION_EDIT_KEYRING); + + // fill values for this action + Bundle data = new Bundle(); + data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput); + data.putParcelable(KeychainIntentService.EDIT_KEYRING_PARCEL, skp); + intent.putExtra(KeychainIntentService.EXTRA_DATA, data); + + // Create a new Messenger for the communication back + Messenger messenger = new Messenger(saveHandler); + intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger); + + // show progress dialog + saveHandler.showProgressDialog(getActivity()); + + // start service with intent + getActivity().startService(intent); + + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateGithubStep1Fragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateGithubStep1Fragment.java new file mode 100644 index 000000000..b166b3e4f --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateGithubStep1Fragment.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui.linked; + +import android.os.AsyncTask; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.EditText; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.util.Notify; + + +public class LinkedIdCreateGithubStep1Fragment extends Fragment { + + LinkedIdWizard mLinkedIdWizard; + + EditText mEditHandle; + + public static LinkedIdCreateGithubStep1Fragment newInstance() { + LinkedIdCreateGithubStep1Fragment frag = new LinkedIdCreateGithubStep1Fragment(); + + Bundle args = new Bundle(); + frag.setArguments(args); + + return frag; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + mLinkedIdWizard = (LinkedIdWizard) getActivity(); + + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + final View view = inflater.inflate(R.layout.linked_create_github_fragment_step1, container, false); + + view.findViewById(R.id.next_button).setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + + final String handle = mEditHandle.getText().toString(); + + new AsyncTask<Void,Void,Boolean>() { + + @Override + protected Boolean doInBackground(Void... params) { + return true; // return checkHandle(handle); + } + + @Override + protected void onPostExecute(Boolean result) { + super.onPostExecute(result); + + if (result == null) { + Notify.create(getActivity(), + "Connection error while checking username!", Notify.Style.ERROR).show(); + return; + } + + if (!result) { + Notify.create(getActivity(), + "This handle does not exist on Github!", Notify.Style.ERROR).show(); + return; + } + + LinkedIdCreateGithubStep2Fragment frag = + LinkedIdCreateGithubStep2Fragment.newInstance(handle); + + mLinkedIdWizard.loadFragment(null, frag, LinkedIdWizard.FRAG_ACTION_TO_RIGHT); + } + }.execute(); + + } + }); + + view.findViewById(R.id.back_button).setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + mLinkedIdWizard.loadFragment(null, null, LinkedIdWizard.FRAG_ACTION_TO_LEFT); + } + }); + + mEditHandle = (EditText) view.findViewById(R.id.linked_create_github_handle); + + return view; + } + + /* not used at this point, too much hassle + private static Boolean checkHandle(String handle) { + try { + HttpURLConnection nection = + (HttpURLConnection) new URL("https://api.github.com/" + handle).openConnection(); + nection.setRequestMethod("HEAD"); + return nection.getResponseCode() == 200; + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + */ + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateGithubStep2Fragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateGithubStep2Fragment.java new file mode 100644 index 000000000..a3a8c779f --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateGithubStep2Fragment.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui.linked; + +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; +import org.sufficientlysecure.keychain.linked.LinkedTokenResource; +import org.sufficientlysecure.keychain.linked.resources.GithubResource; + + +public class LinkedIdCreateGithubStep2Fragment extends LinkedIdCreateFinalFragment { + + public static final String ARG_HANDLE = "handle"; + + String mResourceHandle; + String mResourceString; + + public static LinkedIdCreateGithubStep2Fragment newInstance + (String handle) { + + LinkedIdCreateGithubStep2Fragment frag = new LinkedIdCreateGithubStep2Fragment(); + + Bundle args = new Bundle(); + args.putString(ARG_HANDLE, handle); + frag.setArguments(args); + + return frag; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mResourceString = + GithubResource.generate(getActivity(), mLinkedIdWizard.mFingerprint); + + mResourceHandle = getArguments().getString(ARG_HANDLE); + + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = super.onCreateView(inflater, container, savedInstanceState); + + if (view != null) { + + view.findViewById(R.id.button_send).setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + proofSend(); + } + }); + + view.findViewById(R.id.button_share).setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + proofShare(); + } + }); + + } + + return view; + } + + @Override + LinkedTokenResource getResource(OperationLog log) { + return GithubResource.searchInGithubStream(getActivity(), mResourceHandle, mResourceString, log); + } + + @Override + protected View newView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + return inflater.inflate(R.layout.linked_create_github_fragment_step2, container, false); + } + + private void proofShare() { + Intent sendIntent = new Intent(); + sendIntent.setAction(Intent.ACTION_SEND); + sendIntent.putExtra(Intent.EXTRA_TEXT, mResourceString); + sendIntent.setType("text/plain"); + startActivity(sendIntent); + } + + private void proofSend() { + Uri.Builder builder = Uri.parse("https://gist.github.com/").buildUpon(); + builder.appendQueryParameter("text", mResourceString); + Uri uri = builder.build(); + + Intent intent = new Intent(Intent.ACTION_VIEW, uri); + getActivity().startActivity(intent); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateHttpsStep1Fragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateHttpsStep1Fragment.java new file mode 100644 index 000000000..5a8b4a72a --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateHttpsStep1Fragment.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui.linked; + +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.text.Editable; +import android.text.TextWatcher; +import android.util.Patterns; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.EditText; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.linked.resources.GenericHttpsResource; + +public class LinkedIdCreateHttpsStep1Fragment extends Fragment { + + LinkedIdWizard mLinkedIdWizard; + + EditText mEditUri; + + public static LinkedIdCreateHttpsStep1Fragment newInstance() { + LinkedIdCreateHttpsStep1Fragment frag = new LinkedIdCreateHttpsStep1Fragment(); + + Bundle args = new Bundle(); + frag.setArguments(args); + + return frag; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + mLinkedIdWizard = (LinkedIdWizard) getActivity(); + + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + final View view = inflater.inflate(R.layout.linked_create_https_fragment_step1, container, false); + + view.findViewById(R.id.next_button).setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + + String uri = "https://" + mEditUri.getText(); + + if (!checkUri(uri)) { + return; + } + + String proofText = GenericHttpsResource.generateText(getActivity(), + mLinkedIdWizard.mFingerprint); + + LinkedIdCreateHttpsStep2Fragment frag = + LinkedIdCreateHttpsStep2Fragment.newInstance(uri, proofText); + + mLinkedIdWizard.loadFragment(null, frag, LinkedIdWizard.FRAG_ACTION_TO_RIGHT); + + } + }); + + view.findViewById(R.id.back_button).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mLinkedIdWizard.loadFragment(null, null, LinkedIdWizard.FRAG_ACTION_TO_LEFT); + } + }); + + mEditUri = (EditText) view.findViewById(R.id.linked_create_https_uri); + + mEditUri.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) { + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) { + } + + @Override + public void afterTextChanged(Editable editable) { + String uri = "https://" + editable; + if (uri.length() > 0) { + if (checkUri(uri)) { + mEditUri.setCompoundDrawablesWithIntrinsicBounds(0, 0, + R.drawable.uid_mail_ok, 0); + } else { + mEditUri.setCompoundDrawablesWithIntrinsicBounds(0, 0, + R.drawable.uid_mail_bad, 0); + } + } else { + // remove drawable if email is empty + mEditUri.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); + } + } + }); + + mEditUri.setText("mugenguild.com/pgpkey.txt"); + + return view; + } + + private static boolean checkUri(String uri) { + return Patterns.WEB_URL.matcher(uri).matches(); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateHttpsStep2Fragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateHttpsStep2Fragment.java new file mode 100644 index 000000000..2af97fe36 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateHttpsStep2Fragment.java @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui.linked; + +import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Environment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.EditText; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; +import org.sufficientlysecure.keychain.linked.resources.GenericHttpsResource; +import org.sufficientlysecure.keychain.ui.util.Notify; +import org.sufficientlysecure.keychain.ui.util.Notify.Style; +import org.sufficientlysecure.keychain.util.FileHelper; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.PrintWriter; +import java.net.URI; +import java.net.URISyntaxException; + +public class LinkedIdCreateHttpsStep2Fragment extends LinkedIdCreateFinalFragment { + + private static final int REQUEST_CODE_OUTPUT = 0x00007007; + + public static final String ARG_URI = "uri", ARG_TEXT = "text"; + + EditText mEditUri; + + URI mResourceUri; + String mResourceString; + + public static LinkedIdCreateHttpsStep2Fragment newInstance + (String uri, String proofText) { + + LinkedIdCreateHttpsStep2Fragment frag = new LinkedIdCreateHttpsStep2Fragment(); + + Bundle args = new Bundle(); + args.putString(ARG_URI, uri); + args.putString(ARG_TEXT, proofText); + frag.setArguments(args); + + return frag; + } + + @Override + GenericHttpsResource getResource(OperationLog log) { + return GenericHttpsResource.createNew(mResourceUri); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + try { + mResourceUri = new URI(getArguments().getString(ARG_URI)); + } catch (URISyntaxException e) { + e.printStackTrace(); + getActivity().finish(); + } + + mResourceString = getArguments().getString(ARG_TEXT); + + } + + protected View newView(LayoutInflater inflater, + ViewGroup container, Bundle savedInstanceState) { + return inflater.inflate(R.layout.linked_create_https_fragment_step2, container, false); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = super.onCreateView(inflater, container, savedInstanceState); + + if (view != null) { + + view.findViewById(R.id.button_send).setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + proofSend(); + } + }); + + view.findViewById(R.id.button_save).setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + proofSave(); + } + }); + + mEditUri = (EditText) view.findViewById(R.id.linked_create_https_uri); + mEditUri.setText(mResourceUri.toString()); + } + + return view; + } + + private void proofSend () { + Intent sendIntent = new Intent(); + sendIntent.setAction(Intent.ACTION_SEND); + sendIntent.putExtra(Intent.EXTRA_TEXT, mResourceString); + sendIntent.setType("text/plain"); + startActivity(sendIntent); + } + + private void proofSave () { + String state = Environment.getExternalStorageState(); + if (!Environment.MEDIA_MOUNTED.equals(state)) { + Notify.create(getActivity(), "External storage not available!", Style.ERROR); + return; + } + + String targetName = "pgpkey.txt"; + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { + File targetFile = new File(Constants.Path.APP_DIR, targetName); + FileHelper.saveFile(this, getString(R.string.title_decrypt_to_file), + getString(R.string.specify_file_to_decrypt_to), targetFile, REQUEST_CODE_OUTPUT); + } else { + FileHelper.saveDocument(this, "text/plain", targetName, REQUEST_CODE_OUTPUT); + } + } + + private void saveFile(Uri uri) { + try { + PrintWriter out = + new PrintWriter(getActivity().getContentResolver().openOutputStream(uri)); + out.print(mResourceString); + if (out.checkError()) { + Notify.create(getActivity(), "Error writing file!", Style.ERROR).show(); + } + } catch (FileNotFoundException e) { + Notify.create(getActivity(), "File could not be opened for writing!", Style.ERROR).show(); + } + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + // For saving a file + case REQUEST_CODE_OUTPUT: + if (data == null) { + return; + } + Uri uri = data.getData(); + saveFile(uri); + break; + default: + super.onActivityResult(requestCode, resultCode, data); + } + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep1Fragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep1Fragment.java new file mode 100644 index 000000000..c25f775b0 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep1Fragment.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui.linked; + +import android.os.AsyncTask; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.EditText; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.util.Notify; + +public class LinkedIdCreateTwitterStep1Fragment extends Fragment { + + LinkedIdWizard mLinkedIdWizard; + + EditText mEditHandle; + + public static LinkedIdCreateTwitterStep1Fragment newInstance() { + LinkedIdCreateTwitterStep1Fragment frag = new LinkedIdCreateTwitterStep1Fragment(); + + Bundle args = new Bundle(); + frag.setArguments(args); + + return frag; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + mLinkedIdWizard = (LinkedIdWizard) getActivity(); + + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + final View view = inflater.inflate(R.layout.linked_create_twitter_fragment_step1, container, false); + + view.findViewById(R.id.next_button).setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + + final String handle = mEditHandle.getText().toString(); + + if ("".equals(handle)) { + mEditHandle.setError("Please input a Twitter handle!"); + return; + } + + new AsyncTask<Void,Void,Boolean>() { + + @Override + protected Boolean doInBackground(Void... params) { + return true; + // return checkHandle(handle); + } + + @Override + protected void onPostExecute(Boolean result) { + super.onPostExecute(result); + + if (result == null) { + Notify.create(getActivity(), + "Connection error while checking username!", + Notify.Style.ERROR).show(LinkedIdCreateTwitterStep1Fragment.this); + return; + } + + if (!result) { + Notify.create(getActivity(), + "This handle does not exist on Twitter!", + Notify.Style.ERROR).show(LinkedIdCreateTwitterStep1Fragment.this); + return; + } + + LinkedIdCreateTwitterStep2Fragment frag = + LinkedIdCreateTwitterStep2Fragment.newInstance(handle); + + mLinkedIdWizard.loadFragment(null, frag, LinkedIdWizard.FRAG_ACTION_TO_RIGHT); + } + }.execute(); + + } + }); + + view.findViewById(R.id.back_button).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mLinkedIdWizard.loadFragment(null, null, LinkedIdWizard.FRAG_ACTION_TO_LEFT); + } + }); + + mEditHandle = (EditText) view.findViewById(R.id.linked_create_twitter_handle); + + return view; + } + + /* not used at this point, too many problems + private static Boolean checkHandle(String handle) { + try { + HttpURLConnection nection = + (HttpURLConnection) new URL("https://twitter.com/" + handle).openConnection(); + nection.setRequestMethod("HEAD"); + nection.setRequestProperty("User-Agent", "OpenKeychain"); + return nection.getResponseCode() == 200; + } catch (IOException e) { + return null; + } + } + */ + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep2Fragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep2Fragment.java new file mode 100644 index 000000000..362798bc8 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep2Fragment.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui.linked; + +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.text.Html; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.TextView; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; +import org.sufficientlysecure.keychain.linked.LinkedTokenResource; +import org.sufficientlysecure.keychain.linked.resources.TwitterResource; + +public class LinkedIdCreateTwitterStep2Fragment extends LinkedIdCreateFinalFragment { + + public static final String ARG_HANDLE = "handle"; + + String mResourceHandle; + String mResourceString; + + public static LinkedIdCreateTwitterStep2Fragment newInstance + (String handle) { + + LinkedIdCreateTwitterStep2Fragment frag = new LinkedIdCreateTwitterStep2Fragment(); + + Bundle args = new Bundle(); + args.putString(ARG_HANDLE, handle); + frag.setArguments(args); + + return frag; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mResourceString = + TwitterResource.generate(mLinkedIdWizard.mFingerprint); + + mResourceHandle = getArguments().getString(ARG_HANDLE); + + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = super.onCreateView(inflater, container, savedInstanceState); + + if (view != null) { + view.findViewById(R.id.button_send).setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + proofSend(); + } + }); + + view.findViewById(R.id.button_share).setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + proofShare(); + } + }); + + ((TextView) view.findViewById(R.id.linked_tweet_published)).setText( + Html.fromHtml(getString(R.string.linked_create_twitter_2_3, mResourceHandle)) + ); + } + + return view; + } + + @Override + LinkedTokenResource getResource(OperationLog log) { + return TwitterResource.searchInTwitterStream(getActivity(), + mResourceHandle, mResourceString, log); + } + + @Override + protected View newView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + return inflater.inflate(R.layout.linked_create_twitter_fragment_step2, container, false); + } + + private void proofShare() { + Intent sendIntent = new Intent(); + sendIntent.setAction(Intent.ACTION_SEND); + sendIntent.putExtra(Intent.EXTRA_TEXT, mResourceString); + sendIntent.setType("text/plain"); + startActivity(sendIntent); + } + + private void proofSend() { + + Uri.Builder builder = Uri.parse("https://twitter.com/intent/tweet").buildUpon(); + builder.appendQueryParameter("text", mResourceString); + Uri uri = builder.build(); + + Intent intent = new Intent(Intent.ACTION_VIEW, uri); + getActivity().startActivity(intent); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdSelectFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdSelectFragment.java new file mode 100644 index 000000000..8249f0bb6 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdSelectFragment.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui.linked; + +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import org.sufficientlysecure.keychain.R; + +public class LinkedIdSelectFragment extends Fragment { + + LinkedIdWizard mLinkedIdWizard; + + /** + * Creates new instance of this fragment + */ + public static LinkedIdSelectFragment newInstance() { + LinkedIdSelectFragment frag = new LinkedIdSelectFragment(); + + Bundle args = new Bundle(); + frag.setArguments(args); + + return frag; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.linked_select_fragment, container, false); + + view.findViewById(R.id.linked_create_https_button) + .setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + LinkedIdCreateHttpsStep1Fragment frag = + LinkedIdCreateHttpsStep1Fragment.newInstance(); + + mLinkedIdWizard.loadFragment(null, frag, LinkedIdWizard.FRAG_ACTION_TO_RIGHT); + } + }); + + view.findViewById(R.id.linked_create_dns_button) + .setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + LinkedIdCreateDnsStep1Fragment frag = + LinkedIdCreateDnsStep1Fragment.newInstance(); + + mLinkedIdWizard.loadFragment(null, frag, LinkedIdWizard.FRAG_ACTION_TO_RIGHT); + } + }); + + view.findViewById(R.id.linked_create_twitter_button) + .setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + LinkedIdCreateTwitterStep1Fragment frag = + LinkedIdCreateTwitterStep1Fragment.newInstance(); + + mLinkedIdWizard.loadFragment(null, frag, LinkedIdWizard.FRAG_ACTION_TO_RIGHT); + } + }); + + view.findViewById(R.id.linked_create_github_button) + .setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + LinkedIdCreateGithubStep1Fragment frag = + LinkedIdCreateGithubStep1Fragment.newInstance(); + + mLinkedIdWizard.loadFragment(null, frag, LinkedIdWizard.FRAG_ACTION_TO_RIGHT); + } + }); + + + return view; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + mLinkedIdWizard = (LinkedIdWizard) getActivity(); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdViewFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdViewFragment.java new file mode 100644 index 000000000..7e9aaf25e --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdViewFragment.java @@ -0,0 +1,588 @@ +package org.sufficientlysecure.keychain.ui.linked; + +import java.io.IOException; +import java.util.Collections; + +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.graphics.PorterDuff; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.os.Messenger; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentManager.OnBackStackChangedListener; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextSwitcher; +import android.widget.TextView; +import android.widget.ViewAnimator; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.Constants.key; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.operations.results.CertifyResult; +import org.sufficientlysecure.keychain.operations.results.LinkedVerifyResult; +import org.sufficientlysecure.keychain.linked.LinkedTokenResource; +import org.sufficientlysecure.keychain.linked.LinkedIdentity; +import org.sufficientlysecure.keychain.linked.LinkedResource; +import org.sufficientlysecure.keychain.linked.UriAttribute; +import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; +import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; +import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; +import org.sufficientlysecure.keychain.service.CertifyActionsParcel; +import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction; +import org.sufficientlysecure.keychain.service.KeychainIntentService; +import org.sufficientlysecure.keychain.service.ServiceProgressHandler; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.ui.adapter.LinkedIdsAdapter; +import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter; +import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment; +import org.sufficientlysecure.keychain.ui.linked.LinkedIdViewFragment.ViewHolder.VerifyState; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; +import org.sufficientlysecure.keychain.ui.util.Notify; +import org.sufficientlysecure.keychain.ui.util.Notify.Style; +import org.sufficientlysecure.keychain.ui.util.SubtleAttentionSeeker; +import org.sufficientlysecure.keychain.ui.widget.CertListWidget; +import org.sufficientlysecure.keychain.ui.widget.CertifyKeySpinner; +import org.sufficientlysecure.keychain.util.Log; + + +public class LinkedIdViewFragment extends CryptoOperationFragment implements + LoaderManager.LoaderCallbacks<Cursor>, OnBackStackChangedListener { + + private static final String ARG_DATA_URI = "data_uri"; + private static final String ARG_LID_RANK = "rank"; + private static final String ARG_IS_SECRET = "verified"; + private static final String ARG_FINGERPRINT = "fingerprint"; + private static final int LOADER_ID_LINKED_ID = 1; + + private UriAttribute mLinkedId; + private LinkedTokenResource mLinkedResource; + private boolean mIsSecret; + + private Context mContext; + private byte[] mFingerprint; + + private AsyncTask mInProgress; + + private Uri mDataUri; + private ViewHolder mViewHolder; + private int mLidRank; + private OnIdentityLoadedListener mIdLoadedListener; + private long mCertifyKeyId; + + public static LinkedIdViewFragment newInstance(Uri dataUri, int rank, + boolean isSecret, byte[] fingerprint) throws IOException { + LinkedIdViewFragment frag = new LinkedIdViewFragment(); + + Bundle args = new Bundle(); + args.putParcelable(ARG_DATA_URI, dataUri); + args.putInt(ARG_LID_RANK, rank); + args.putBoolean(ARG_IS_SECRET, isSecret); + args.putByteArray(ARG_FINGERPRINT, fingerprint); + frag.setArguments(args); + + return frag; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Bundle args = getArguments(); + mDataUri = args.getParcelable(ARG_DATA_URI); + mLidRank = args.getInt(ARG_LID_RANK); + + mIsSecret = args.getBoolean(ARG_IS_SECRET); + mFingerprint = args.getByteArray(ARG_FINGERPRINT); + + mContext = getActivity(); + + getLoaderManager().initLoader(LOADER_ID_LINKED_ID, null, this); + + } + + @Override + public Loader<Cursor> onCreateLoader(int id, Bundle args) { + switch (id) { + case LOADER_ID_LINKED_ID: + return new CursorLoader(getActivity(), mDataUri, + UserIdsAdapter.USER_PACKETS_PROJECTION, + Tables.USER_PACKETS + "." + UserPackets.RANK + + " = " + Integer.toString(mLidRank), null, null); + default: + return null; + } + } + + @Override + public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { + switch (loader.getId()) { + case LOADER_ID_LINKED_ID: + + // Nothing to load means break if we are *expected* to load + if (!cursor.moveToFirst()) { + if (mIdLoadedListener != null) { + Notify.create(getActivity(), "Error loading identity!", + Notify.LENGTH_LONG, Style.ERROR).show(); + finishFragment(); + } + // Or just ignore, this is probably some intermediate state during certify + break; + } + + try { + int certStatus = cursor.getInt(UserIdsAdapter.INDEX_VERIFIED); + + byte[] data = cursor.getBlob(UserIdsAdapter.INDEX_ATTRIBUTE_DATA); + UriAttribute linkedId = LinkedIdentity.fromAttributeData(data); + + loadIdentity(linkedId, certStatus); + + if (mIdLoadedListener != null) { + mIdLoadedListener.onIdentityLoaded(); + mIdLoadedListener = null; + } + + } catch (IOException e) { + Log.e(Constants.TAG, "error parsing identity", e); + Notify.create(getActivity(), "Error parsing identity!", + Notify.LENGTH_LONG, Style.ERROR).show(); + finishFragment(); + } + + break; + } + } + + public void finishFragment() { + new Handler().post(new Runnable() { + @Override + public void run() { + FragmentManager manager = getFragmentManager(); + manager.removeOnBackStackChangedListener(LinkedIdViewFragment.this); + manager.popBackStack("linked_id", FragmentManager.POP_BACK_STACK_INCLUSIVE); + } + }); + } + + public interface OnIdentityLoadedListener { + void onIdentityLoaded(); + } + + public void setOnIdentityLoadedListener(OnIdentityLoadedListener listener) { + mIdLoadedListener = listener; + } + + private void loadIdentity(UriAttribute linkedId, int certStatus) { + mLinkedId = linkedId; + + if (mLinkedId instanceof LinkedIdentity) { + LinkedResource res = ((LinkedIdentity) mLinkedId).mResource; + mLinkedResource = (LinkedTokenResource) res; + } + + if (!mIsSecret) { + switch (certStatus) { + case Certs.VERIFIED_SECRET: + KeyFormattingUtils.setStatusImage(mContext, mViewHolder.mLinkedIdHolder.vVerified, + null, State.VERIFIED, KeyFormattingUtils.DEFAULT_COLOR); + break; + case Certs.VERIFIED_SELF: + KeyFormattingUtils.setStatusImage(mContext, mViewHolder.mLinkedIdHolder.vVerified, + null, State.UNVERIFIED, KeyFormattingUtils.DEFAULT_COLOR); + break; + default: + KeyFormattingUtils.setStatusImage(mContext, mViewHolder.mLinkedIdHolder.vVerified, + null, State.INVALID, KeyFormattingUtils.DEFAULT_COLOR); + break; + } + } + + mViewHolder.mLinkedIdHolder.setData(mContext, mLinkedId); + + setShowVerifying(false); + + // no resource, nothing further we can do… + if (mLinkedResource == null) { + mViewHolder.vButtonView.setVisibility(View.GONE); + mViewHolder.vButtonVerify.setVisibility(View.GONE); + return; + } + + if (mLinkedResource.isViewable()) { + mViewHolder.vButtonView.setVisibility(View.VISIBLE); + mViewHolder.vButtonView.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = mLinkedResource.getViewIntent(); + if (intent == null) { + return; + } + getActivity().startActivity(intent); + } + }); + } else { + mViewHolder.vButtonView.setVisibility(View.GONE); + } + + } + + @Override + public void onLoaderReset(Loader<Cursor> loader) { + + } + + static class ViewHolder { + private final View vButtonView; + private final ViewAnimator vVerifyingContainer; + private final ViewAnimator vItemCertified; + private final View vKeySpinnerContainer; + LinkedIdsAdapter.ViewHolder mLinkedIdHolder; + + private ViewAnimator vButtonSwitcher; + private CertListWidget vLinkedCerts; + private CertifyKeySpinner vKeySpinner; + private final View vButtonVerify; + private final View vButtonRetry; + private final View vButtonConfirm; + + private final ViewAnimator vProgress; + private final TextSwitcher vText; + + ViewHolder(View root) { + vLinkedCerts = (CertListWidget) root.findViewById(R.id.linked_id_certs); + vKeySpinner = (CertifyKeySpinner) root.findViewById(R.id.cert_key_spinner); + vKeySpinnerContainer = root.findViewById(R.id.cert_key_spincontainer); + vButtonSwitcher = (ViewAnimator) root.findViewById(R.id.button_animator); + + mLinkedIdHolder = new LinkedIdsAdapter.ViewHolder(root); + + vButtonVerify = root.findViewById(R.id.button_verify); + vButtonRetry = root.findViewById(R.id.button_retry); + vButtonConfirm = root.findViewById(R.id.button_confirm); + vButtonView = root.findViewById(R.id.button_view); + + vVerifyingContainer = (ViewAnimator) root.findViewById(R.id.linked_verify_container); + vItemCertified = (ViewAnimator) root.findViewById(R.id.linked_id_certified); + + vProgress = (ViewAnimator) root.findViewById(R.id.linked_cert_progress); + vText = (TextSwitcher) root.findViewById(R.id.linked_cert_text); + } + + enum VerifyState { + VERIFYING, VERIFY_OK, VERIFY_ERROR, CERTIFYING + } + + void setVerifyingState(Context context, VerifyState state, boolean isSecret) { + switch (state) { + case VERIFYING: + vProgress.setDisplayedChild(0); + vText.setText(context.getString(R.string.linked_text_verifying)); + vKeySpinnerContainer.setVisibility(View.GONE); + break; + + case VERIFY_OK: + vProgress.setDisplayedChild(1); + if (!isSecret) { + showButton(2); + if (!vKeySpinner.isSingleEntry()) { + vKeySpinnerContainer.setVisibility(View.VISIBLE); + } + } else { + showButton(1); + vKeySpinnerContainer.setVisibility(View.GONE); + } + break; + + case VERIFY_ERROR: + showButton(1); + vProgress.setDisplayedChild(2); + vText.setText(context.getString(R.string.linked_text_error)); + vKeySpinnerContainer.setVisibility(View.GONE); + break; + + case CERTIFYING: + vProgress.setDisplayedChild(0); + vText.setText(context.getString(R.string.linked_text_confirming)); + vKeySpinnerContainer.setVisibility(View.GONE); + break; + } + } + + void showVerifyingContainer(Context context, boolean show, boolean isSecret) { + if (vVerifyingContainer.getDisplayedChild() == (show ? 1 : 0)) { + return; + } + + vVerifyingContainer.setInAnimation(context, show ? R.anim.fade_in_up : R.anim.fade_in_down); + vVerifyingContainer.setOutAnimation(context, show ? R.anim.fade_out_up : R.anim.fade_out_down); + vVerifyingContainer.setDisplayedChild(show ? 1 : 0); + + vItemCertified.setInAnimation(context, show ? R.anim.fade_in_up : R.anim.fade_in_down); + vItemCertified.setOutAnimation(context, show ? R.anim.fade_out_up : R.anim.fade_out_down); + vItemCertified.setDisplayedChild(show || isSecret ? 1 : 0); + } + + void showButton(int which) { + if (vButtonSwitcher.getDisplayedChild() == which) { + return; + } + vButtonSwitcher.setDisplayedChild(which); + } + + } + + private boolean mVerificationState = false; + /** Switches between the 'verifying' ui bit and certificate status. This method + * must behave correctly in all states, showing or hiding the appropriate views + * and cancelling pending operations where necessary. + * + * This method also handles back button functionality in combination with + * onBackStateChanged. + */ + void setShowVerifying(boolean show) { + if (!show) { + if (mInProgress != null) { + mInProgress.cancel(false); + mInProgress = null; + } + getFragmentManager().removeOnBackStackChangedListener(this); + new Handler().post(new Runnable() { + @Override + public void run() { + getFragmentManager().popBackStack("verification", + FragmentManager.POP_BACK_STACK_INCLUSIVE); + } + }); + + if (!mVerificationState) { + return; + } + mVerificationState = false; + + mViewHolder.showButton(0); + mViewHolder.vKeySpinnerContainer.setVisibility(View.GONE); + mViewHolder.showVerifyingContainer(mContext, false, mIsSecret); + return; + } + + if (mVerificationState) { + return; + } + mVerificationState = true; + + FragmentManager manager = getFragmentManager(); + manager.beginTransaction().addToBackStack("verification").commit(); + manager.executePendingTransactions(); + manager.addOnBackStackChangedListener(this); + mViewHolder.showVerifyingContainer(mContext, true, mIsSecret); + + } + + @Override + public void onBackStackChanged() { + setShowVerifying(false); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { + View root = inflater.inflate(R.layout.linked_id_view_fragment, null); + + mViewHolder = new ViewHolder(root); + root.setTag(mViewHolder); + + ((ImageView) root.findViewById(R.id.status_icon_verified)) + .setColorFilter(mContext.getResources().getColor(R.color.android_green_light), + PorterDuff.Mode.SRC_IN); + ((ImageView) root.findViewById(R.id.status_icon_invalid)) + .setColorFilter(mContext.getResources().getColor(R.color.android_red_light), + PorterDuff.Mode.SRC_IN); + + mViewHolder.vButtonVerify.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + verifyResource(); + } + }); + mViewHolder.vButtonRetry.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + verifyResource(); + } + }); + mViewHolder.vButtonConfirm.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + initiateCertifying(); + } + }); + + { + Bundle args = new Bundle(); + args.putParcelable(CertListWidget.ARG_URI, Certs.buildLinkedIdCertsUri(mDataUri, mLidRank)); + args.putBoolean(CertListWidget.ARG_IS_SECRET, mIsSecret); + getLoaderManager().initLoader(CertListWidget.LOADER_ID_LINKED_CERTS, + args, mViewHolder.vLinkedCerts); + } + + return root; + } + + void verifyResource() { + + // only one at a time (no sync needed, mInProgress is only touched in ui thread) + if (mInProgress != null) { + return; + } + + setShowVerifying(true); + + mViewHolder.vKeySpinnerContainer.setVisibility(View.GONE); + mViewHolder.setVerifyingState(mContext, VerifyState.VERIFYING, mIsSecret); + + mInProgress = new AsyncTask<Void,Void,LinkedVerifyResult>() { + @Override + protected LinkedVerifyResult doInBackground(Void... params) { + long timer = System.currentTimeMillis(); + LinkedVerifyResult result = mLinkedResource.verify(getActivity(), mFingerprint); + + // ux flow: this operation should take at last a second + timer = System.currentTimeMillis() -timer; + if (timer < 1000) try { + Thread.sleep(1000 -timer); + } catch (InterruptedException e) { + // never mind + } + + return result; + } + + @Override + protected void onPostExecute(LinkedVerifyResult result) { + if (isCancelled()) { + return; + } + if (result.success()) { + mViewHolder.vText.setText(getString(mLinkedResource.getVerifiedText(mIsSecret))); + // hack to preserve bold text + ((TextView) mViewHolder.vText.getCurrentView()).setText( + mLinkedResource.getVerifiedText(mIsSecret)); + mViewHolder.setVerifyingState(mContext, VerifyState.VERIFY_OK, mIsSecret); + mViewHolder.mLinkedIdHolder.seekAttention(); + } else { + mViewHolder.setVerifyingState(mContext, VerifyState.VERIFY_ERROR, mIsSecret); + result.createNotify(getActivity()).show(); + } + mInProgress = null; + } + }.execute(); + + } + + private void initiateCertifying() { + + if (mIsSecret) { + return; + } + + // get the user's passphrase for this key (if required) + mCertifyKeyId = mViewHolder.vKeySpinner.getSelectedItemId(); + if (mCertifyKeyId == key.none || mCertifyKeyId == key.symmetric) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + SubtleAttentionSeeker.tintBackground(mViewHolder.vKeySpinnerContainer, 600).start(); + } else { + Notify.create(getActivity(), R.string.select_key_to_certify, Style.ERROR).show(); + } + return; + } + + cryptoOperation(); + } + + @Override + protected void onCryptoOperationCancelled() { + super.onCryptoOperationCancelled(); + + // go back to 'verified ok' + setShowVerifying(false); + + } + + @Override + protected void cryptoOperation(CryptoInputParcel cryptoInput) { + + if (mIsSecret) { + return; + } + + mViewHolder.setVerifyingState(mContext, VerifyState.CERTIFYING, false); + + Bundle data = new Bundle(); + { + + long masterKeyId = KeyFormattingUtils.convertFingerprintToKeyId(mFingerprint); + CertifyAction action = new CertifyAction(masterKeyId, null, + Collections.singletonList(mLinkedId.toUserAttribute())); + + // fill values for this action + CertifyActionsParcel parcel = new CertifyActionsParcel(mCertifyKeyId); + parcel.mCertifyActions.addAll(Collections.singletonList(action)); + data.putParcelable(KeychainIntentService.CERTIFY_PARCEL, parcel); + + data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput); + + /* if (mUploadKeyCheckbox.isChecked()) { + String keyserver = Preferences.getPreferences(getActivity()).getPreferredKeyserver(); + data.putString(KeychainIntentService.UPLOAD_KEY_SERVER, keyserver); + } */ + } + + // Send all information needed to service to sign key in other thread + Intent intent = new Intent(getActivity(), KeychainIntentService.class); + intent.setAction(KeychainIntentService.ACTION_CERTIFY_KEYRING); + intent.putExtra(KeychainIntentService.EXTRA_DATA, data); + + // Message is received after signing is done in KeychainIntentService + ServiceProgressHandler saveHandler = new ServiceProgressHandler(getActivity()) { + public void handleMessage(Message message) { + // handle messages by standard KeychainIntentServiceHandler first + super.handleMessage(message); + + // handle pending messages + if (handlePendingMessage(message)) { + return; + } + + if (message.arg1 == MessageStatus.OKAY.ordinal()) { + Bundle data = message.getData(); + CertifyResult result = data.getParcelable(CertifyResult.EXTRA_RESULT); + result.createNotify(getActivity()).show(); + // no need to do anything else, we will get a loader refresh! + } + + } + }; + + // Create a new Messenger for the communication back + Messenger messenger = new Messenger(saveHandler); + intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger); + + // start service with intent + getActivity().startService(intent); + + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdWizard.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdWizard.java new file mode 100644 index 000000000..a29f175c0 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdWizard.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui.linked; + +import android.content.Context; +import android.net.Uri; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentTransaction; +import android.view.View; +import android.view.inputmethod.InputMethodManager; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; +import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; +import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.ui.base.BaseActivity; +import org.sufficientlysecure.keychain.util.Log; + +public class LinkedIdWizard extends BaseActivity { + + public static final int FRAG_ACTION_START = 0; + public static final int FRAG_ACTION_TO_RIGHT = 1; + public static final int FRAG_ACTION_TO_LEFT = 2; + + long mMasterKeyId; + byte[] mFingerprint; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setTitle(getString(R.string.title_linked_id_create)); + + try { + Uri uri = getIntent().getData(); + uri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(uri); + CachedPublicKeyRing ring = new ProviderHelper(this).getCachedPublicKeyRing(uri); + if (!ring.hasAnySecret()) { + Log.e(Constants.TAG, "Linked Identities can only be added to secret keys!"); + finish(); + return; + } + + mMasterKeyId = ring.extractOrGetMasterKeyId(); + mFingerprint = ring.getFingerprint(); + } catch (PgpKeyNotFoundException e) { + Log.e(Constants.TAG, "Invalid uri given, key does not exist!"); + finish(); + return; + } + + // pass extras into fragment + LinkedIdSelectFragment frag = LinkedIdSelectFragment.newInstance(); + loadFragment(null, frag, FRAG_ACTION_START); + } + + @Override + protected void initLayout() { + setContentView(R.layout.create_key_activity); + } + + public void loadFragment(Bundle savedInstanceState, Fragment fragment, int action) { + // However, if we're being restored from a previous state, + // then we don't need to do anything and should return or else + // we could end up with overlapping fragments. + if (savedInstanceState != null) { + return; + } + + hideKeyboard(); + + // Add the fragment to the 'fragment_container' FrameLayout + // NOTE: We use commitAllowingStateLoss() to prevent weird crashes! + FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); + + switch (action) { + case FRAG_ACTION_START: + transaction.setCustomAnimations(0, 0); + transaction.replace(R.id.create_key_fragment_container, fragment) + .commitAllowingStateLoss(); + break; + case FRAG_ACTION_TO_LEFT: + getSupportFragmentManager().popBackStackImmediate(); + break; + case FRAG_ACTION_TO_RIGHT: + transaction.setCustomAnimations(R.anim.frag_slide_in_from_right, R.anim.frag_slide_out_to_left, + R.anim.frag_slide_in_from_left, R.anim.frag_slide_out_to_right); + transaction.addToBackStack(null); + transaction.replace(R.id.create_key_fragment_container, fragment) + .commitAllowingStateLoss(); + break; + + } + // do it immediately! + getSupportFragmentManager().executePendingTransactions(); + } + + private void hideKeyboard() { + InputMethodManager inputManager = (InputMethodManager) + getSystemService(Context.INPUT_METHOD_SERVICE); + + // check if no view has focus + View v = getCurrentFocus(); + if (v == null) + return; + + inputManager.hideSoftInputFromWindow(v.getWindowToken(), 0); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java index 2dea885a7..3d98034d2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java @@ -267,6 +267,10 @@ public class KeyFormattingUtils { return hexString; } + public static long convertFingerprintToKeyId(byte[] fingerprint) { + return ByteBuffer.wrap(fingerprint, 12, 8).getLong(); + } + /** * Makes a human-readable version of a key ID, which is usually 64 bits: lower-case, no * leading 0x, space-separated quartets (for keys whose length in hex is divisible by 4) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/SubtleAttentionSeeker.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/SubtleAttentionSeeker.java index 87444c226..4549e8993 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/SubtleAttentionSeeker.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/SubtleAttentionSeeker.java @@ -17,13 +17,19 @@ package org.sufficientlysecure.keychain.ui.util; +import android.animation.AnimatorInflater; +import android.animation.ArgbEvaluator; import android.animation.Keyframe; import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; import android.annotation.TargetApi; +import android.content.Context; import android.os.Build.VERSION_CODES; import android.view.View; +import org.sufficientlysecure.keychain.R; + + @TargetApi(VERSION_CODES.ICE_CREAM_SANDWICH) /** Simple animation helper for subtle attention seeker stuff. * @@ -36,6 +42,10 @@ public class SubtleAttentionSeeker { } public static ObjectAnimator tada(View view, float shakeFactor) { + return tada(view, shakeFactor, 1400); + } + + public static ObjectAnimator tada(View view, float shakeFactor, int duration) { PropertyValuesHolder pvhScaleX = PropertyValuesHolder.ofKeyframe(View.SCALE_X, Keyframe.ofFloat(0f, 1f), @@ -80,7 +90,19 @@ public class SubtleAttentionSeeker { ); return ObjectAnimator.ofPropertyValuesHolder(view, pvhScaleX, pvhScaleY, pvhRotate). - setDuration(1400); + setDuration(duration); + } + + @TargetApi(VERSION_CODES.LOLLIPOP) + public static ObjectAnimator tintBackground(View view, int duration) { + return ObjectAnimator.ofArgb(view, "backgroundColor", + 0x00FF0000, 0x33FF0000, 0x00FF0000).setDuration(duration); + } + + @TargetApi(VERSION_CODES.LOLLIPOP) + public static ObjectAnimator tintText(View view, int duration) { + return ObjectAnimator.ofArgb(view, "backgroundColor", + 0x00FF7F00, 0x33FF7F00, 0x00FF7F00).setDuration(duration); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertListWidget.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertListWidget.java new file mode 100644 index 000000000..c413a00be --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertListWidget.java @@ -0,0 +1,143 @@ +package org.sufficientlysecure.keychain.ui.widget; + +import java.util.Date; + +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; +import android.util.AttributeSet; +import android.view.View; +import android.widget.ListView; +import android.widget.TextView; +import android.widget.ViewAnimator; + +import org.ocpsoft.prettytime.PrettyTime; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; + +public class CertListWidget extends ViewAnimator + implements LoaderManager.LoaderCallbacks<Cursor> { + + public static final int LOADER_ID_LINKED_CERTS = 38572; + + public static final String ARG_URI = "uri"; + public static final String ARG_IS_SECRET = "is_secret"; + + + // These are the rows that we will retrieve. + static final String[] CERTS_PROJECTION = new String[]{ + KeychainContract.Certs._ID, + KeychainContract.Certs.MASTER_KEY_ID, + KeychainContract.Certs.VERIFIED, + KeychainContract.Certs.TYPE, + KeychainContract.Certs.RANK, + KeychainContract.Certs.KEY_ID_CERTIFIER, + KeychainContract.Certs.USER_ID, + KeychainContract.Certs.SIGNER_UID, + KeychainContract.Certs.CREATION + }; + public static final int INDEX_MASTER_KEY_ID = 1; + public static final int INDEX_VERIFIED = 2; + public static final int INDEX_TYPE = 3; + public static final int INDEX_RANK = 4; + public static final int INDEX_KEY_ID_CERTIFIER = 5; + public static final int INDEX_USER_ID = 6; + public static final int INDEX_SIGNER_UID = 7; + public static final int INDEX_CREATION = 8; + + private TextView vCollapsed; + private ListView vExpanded; + private View vExpandButton; + private boolean mIsSecret; + + public CertListWidget(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + + View root = getRootView(); + vCollapsed = (TextView) root.findViewById(R.id.cert_collapsed_list); + vExpanded = (ListView) root.findViewById(R.id.cert_expanded_list); + vExpandButton = root.findViewById(R.id.cert_expand_button); + + // for now + vExpandButton.setVisibility(View.GONE); + vExpandButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + toggleExpanded(); + } + }); + + // vExpanded.setAdapter(null); + + } + + void toggleExpanded() { + setDisplayedChild(getDisplayedChild() == 1 ? 0 : 1); + } + + void setExpanded(boolean expanded) { + setDisplayedChild(expanded ? 1 : 0); + } + + @Override + public Loader<Cursor> onCreateLoader(int id, Bundle args) { + Uri uri = args.getParcelable(ARG_URI); + mIsSecret = args.getBoolean(ARG_IS_SECRET, false); + return new CursorLoader(getContext(), uri, + CERTS_PROJECTION, null, null, null); + } + + @Override + public void onLoadFinished(Loader<Cursor> loader, Cursor data) { + + if (data == null || !data.moveToFirst()) { + return; + } + + // TODO support external certificates + Date userCert = null; + while (!data.isAfterLast()) { + + int verified = data.getInt(INDEX_VERIFIED); + Date creation = new Date(data.getLong(INDEX_CREATION) * 1000); + + if (verified == Certs.VERIFIED_SECRET) { + if (userCert == null || userCert.after(creation)) { + userCert = creation; + } + } + + data.moveToNext(); + } + + if (userCert != null) { + PrettyTime format = new PrettyTime(); + if (mIsSecret) { + vCollapsed.setText("You created this identity " + + format.format(userCert) + "."); + } else { + vCollapsed.setText("You verified and confirmed this identity " + + format.format(userCert) + "."); + } + } else { + vCollapsed.setText("This identity is not yet verified or confirmed."); + } + + } + + @Override + public void onLoaderReset(Loader<Cursor> loader) { + setVisibility(View.GONE); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java index 460163a47..e5b3df8c9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java @@ -21,6 +21,7 @@ import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; +import android.support.annotation.StringRes; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.util.AttributeSet; @@ -35,6 +36,7 @@ import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; public class CertifyKeySpinner extends KeySpinner { private long mHiddenMasterKeyId = Constants.key.none; + private boolean mIsSingle; public CertifyKeySpinner(Context context) { super(context); @@ -98,6 +100,7 @@ public class CertifyKeySpinner extends KeySpinner { // - there are actually keys (not just "none" entry) // Then: // - select key that is capable of certifying, but only if there is only one key capable of it + mIsSingle = false; if (mSelectedKeyId == Constants.key.none && mAdapter.getCount() > 1) { // preselect if key can certify int selection = -1; @@ -105,9 +108,11 @@ public class CertifyKeySpinner extends KeySpinner { if (!data.isNull(mIndexHasCertify)) { if (selection == -1) { selection = data.getPosition() + 1; + mIsSingle = true; } else { // if selection is already set, we have more than one certify key! // get back to "none"! + mIsSingle = false; selection = 0; } } @@ -117,6 +122,9 @@ public class CertifyKeySpinner extends KeySpinner { } } + public boolean isSingleEntry() { + return mIsSingle && getSelectedItemPosition() != 0; + } @Override boolean setStatus(Context context, Cursor cursor, ImageView statusView) { @@ -138,4 +146,9 @@ public class CertifyKeySpinner extends KeySpinner { return true; } + public @StringRes int getNoneString() { + return R.string.choice_select_cert; + } + + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java index aecc81604..61b7c718b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java @@ -20,12 +20,12 @@ package org.sufficientlysecure.keychain.ui.widget; import android.content.Context; import android.database.Cursor; import android.graphics.Color; +import android.support.annotation.StringRes; import android.support.v4.app.FragmentActivity; import android.support.v4.app.LoaderManager; import android.support.v4.content.Loader; import android.support.v4.widget.CursorAdapter; import android.support.v7.widget.AppCompatSpinner; -import android.text.format.DateFormat; import android.text.format.DateUtils; import android.util.AttributeSet; import android.view.View; @@ -42,10 +42,6 @@ import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.util.Log; -import java.util.Calendar; -import java.util.Date; -import java.util.TimeZone; - /** * Use AppCompatSpinner from AppCompat lib instead of Spinner. Fixes white dropdown icon. * Related: http://stackoverflow.com/a/27713090 @@ -133,10 +129,6 @@ public abstract class KeySpinner extends AppCompatSpinner implements LoaderManag } } - public long getSelectedKeyId() { - return mSelectedKeyId; - } - public void setSelectedKeyId(long selectedKeyId) { this.mSelectedKeyId = selectedKeyId; } @@ -171,6 +163,7 @@ public abstract class KeySpinner extends AppCompatSpinner implements LoaderManag String dateTime = DateUtils.formatDateTime(context, cursor.getLong(mIndexCreationDate) * 1000, DateUtils.FORMAT_SHOW_DATE + | DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_ABBREV_MONTH); @@ -281,7 +274,7 @@ public abstract class KeySpinner extends AppCompatSpinner implements LoaderManag TextView vKeyEmail = (TextView) view.findViewById(R.id.keyspinner_key_email); TextView vKeyDuplicate = (TextView) view.findViewById(R.id.keyspinner_duplicate); - vKeyName.setText(R.string.choice_none); + vKeyName.setText(getNoneString()); vKeyEmail.setVisibility(View.GONE); vKeyDuplicate.setVisibility(View.GONE); vKeyStatus.setVisibility(View.GONE); @@ -293,10 +286,15 @@ public abstract class KeySpinner extends AppCompatSpinner implements LoaderManag } return view; } + } boolean setStatus(Context context, Cursor cursor, ImageView statusView) { return true; } + public @StringRes int getNoneString() { + return R.string.choice_none; + } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/PrefixedEditText.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/PrefixedEditText.java new file mode 100644 index 000000000..3cbb114e8 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/PrefixedEditText.java @@ -0,0 +1,45 @@ +package org.sufficientlysecure.keychain.ui.widget; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.*; +import android.support.annotation.NonNull; +import android.util.AttributeSet; +import android.widget.EditText; + +import org.sufficientlysecure.keychain.R; + +public class PrefixedEditText extends EditText { + + private String mPrefix; + private Rect mPrefixRect = new Rect(); + + public PrefixedEditText(Context context, AttributeSet attrs) { + super(context, attrs); + TypedArray style = context.getTheme().obtainStyledAttributes( + attrs, R.styleable.PrefixedEditText, 0, 0); + mPrefix = style.getString(R.styleable.PrefixedEditText_prefix); + if (mPrefix == null) { + mPrefix = ""; + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + getPaint().getTextBounds(mPrefix, 0, mPrefix.length(), mPrefixRect); + + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + + @Override + protected void onDraw(@NonNull Canvas canvas) { + super.onDraw(canvas); + canvas.drawText(mPrefix, super.getCompoundPaddingLeft(), getBaseline(), getPaint()); + } + + @Override + public int getCompoundPaddingLeft() { + return super.getCompoundPaddingLeft() + mPrefixRect.width(); + } + +}
\ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FilterCursorWrapper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FilterCursorWrapper.java new file mode 100644 index 000000000..ab73f59b8 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FilterCursorWrapper.java @@ -0,0 +1,76 @@ +package org.sufficientlysecure.keychain.util; + + +import android.database.Cursor; +import android.database.CursorWrapper; + +public abstract class FilterCursorWrapper extends CursorWrapper { + private int[] mIndex; + private int mCount = 0; + private int mPos = 0; + + public abstract boolean isVisible(Cursor cursor); + + public FilterCursorWrapper(Cursor cursor) { + super(cursor); + mCount = super.getCount(); + mIndex = new int[mCount]; + for (int i = 0; i < mCount; i++) { + super.moveToPosition(i); + if (isVisible(cursor)) { + mIndex[mPos++] = i; + } + } + mCount = mPos; + mPos = 0; + super.moveToFirst(); + } + + @Override + public boolean move(int offset) { + return this.moveToPosition(mPos + offset); + } + + @Override + public boolean moveToNext() { + return this.moveToPosition(mPos + 1); + } + + @Override + public boolean moveToPrevious() { + return this.moveToPosition(mPos - 1); + } + + @Override + public boolean moveToFirst() { + return this.moveToPosition(0); + } + + @Override + public boolean moveToLast() { + return this.moveToPosition(mCount - 1); + } + + @Override + public boolean moveToPosition(int position) { + if (position >= mCount || position < 0) { + return false; + } + return super.moveToPosition(mIndex[position]); + } + + @Override + public int getCount() { + return mCount; + } + + public int getHiddenCount() { + return super.getCount() - mCount; + } + + @Override + public int getPosition() { + return mPos; + } + +}
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/anim/fade_in.xml b/OpenKeychain/src/main/res/anim/fade_in.xml new file mode 100644 index 000000000..5e2b8be60 --- /dev/null +++ b/OpenKeychain/src/main/res/anim/fade_in.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<set xmlns:android="http://schemas.android.com/apk/res/android"> + <alpha android:fromAlpha="0.0" android:toAlpha="1.0" + android:interpolator="@android:anim/bounce_interpolator" + android:duration="700" + /> +</set>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/anim/fade_in_down.xml b/OpenKeychain/src/main/res/anim/fade_in_down.xml new file mode 100644 index 000000000..fb9ed416e --- /dev/null +++ b/OpenKeychain/src/main/res/anim/fade_in_down.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="UTF-8"?> +<set xmlns:android="http://schemas.android.com/apk/res/android"> + <translate android:fromYDelta="-10" android:toYDelta="0" + android:interpolator="@android:anim/decelerate_interpolator" + android:duration="200" + /> + <alpha android:fromAlpha="0.0" android:toAlpha="1.0" + android:interpolator="@android:anim/accelerate_interpolator" + android:duration="200" + /> +</set>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/anim/fade_in_quick.xml b/OpenKeychain/src/main/res/anim/fade_in_quick.xml new file mode 100644 index 000000000..e0725de3d --- /dev/null +++ b/OpenKeychain/src/main/res/anim/fade_in_quick.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<set xmlns:android="http://schemas.android.com/apk/res/android"> + <alpha android:fromAlpha="0.0" android:toAlpha="1.0" + android:interpolator="@android:anim/bounce_interpolator" + android:duration="400" + /> +</set>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/anim/fade_in_up.xml b/OpenKeychain/src/main/res/anim/fade_in_up.xml new file mode 100644 index 000000000..c190c0f53 --- /dev/null +++ b/OpenKeychain/src/main/res/anim/fade_in_up.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="UTF-8"?> +<set xmlns:android="http://schemas.android.com/apk/res/android"> + <translate android:fromYDelta="10" android:toYDelta="0" + android:interpolator="@android:anim/decelerate_interpolator" + android:duration="200" + /> + <alpha android:fromAlpha="0.0" android:toAlpha="1.0" + android:interpolator="@android:anim/accelerate_interpolator" + android:duration="200" + /> +</set>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/anim/fade_out.xml b/OpenKeychain/src/main/res/anim/fade_out.xml new file mode 100644 index 000000000..f96bf3cb3 --- /dev/null +++ b/OpenKeychain/src/main/res/anim/fade_out.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<set xmlns:android="http://schemas.android.com/apk/res/android"> + <alpha android:fromAlpha="1.0" android:toAlpha="0.0" + android:interpolator="@android:anim/accelerate_interpolator" + android:duration="300" + /> +</set>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/anim/fade_out_down.xml b/OpenKeychain/src/main/res/anim/fade_out_down.xml new file mode 100644 index 000000000..523b180af --- /dev/null +++ b/OpenKeychain/src/main/res/anim/fade_out_down.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="UTF-8"?> +<set xmlns:android="http://schemas.android.com/apk/res/android"> + <translate android:fromYDelta="0.0" android:toYDelta="10" + android:interpolator="@android:anim/decelerate_interpolator" + android:duration="200" + /> + <alpha android:fromAlpha="1.0" android:toAlpha="0.0" + android:interpolator="@android:anim/decelerate_interpolator" + android:duration="150" + /> +</set>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/anim/fade_out_quick.xml b/OpenKeychain/src/main/res/anim/fade_out_quick.xml new file mode 100644 index 000000000..94fc508d7 --- /dev/null +++ b/OpenKeychain/src/main/res/anim/fade_out_quick.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<set xmlns:android="http://schemas.android.com/apk/res/android"> + <alpha android:fromAlpha="1.0" android:toAlpha="0.0" + android:interpolator="@android:anim/decelerate_interpolator" + android:duration="150" + /> +</set>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/anim/fade_out_up.xml b/OpenKeychain/src/main/res/anim/fade_out_up.xml new file mode 100644 index 000000000..65049a387 --- /dev/null +++ b/OpenKeychain/src/main/res/anim/fade_out_up.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="UTF-8"?> +<set xmlns:android="http://schemas.android.com/apk/res/android"> + <translate android:fromYDelta="0.0" android:toYDelta="-10" + android:interpolator="@android:anim/decelerate_interpolator" + android:duration="200" + /> + <alpha android:fromAlpha="1.0" android:toAlpha="0.0" + android:interpolator="@android:anim/decelerate_interpolator" + android:duration="150" + /> +</set>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/drawable-hdpi/linked_dns.png b/OpenKeychain/src/main/res/drawable-hdpi/linked_dns.png Binary files differnew file mode 100644 index 000000000..41719efd6 --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-hdpi/linked_dns.png diff --git a/OpenKeychain/src/main/res/drawable-hdpi/linked_github.png b/OpenKeychain/src/main/res/drawable-hdpi/linked_github.png Binary files differnew file mode 100644 index 000000000..2a1ae67d7 --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-hdpi/linked_github.png diff --git a/OpenKeychain/src/main/res/drawable-hdpi/linked_https.png b/OpenKeychain/src/main/res/drawable-hdpi/linked_https.png Binary files differnew file mode 100644 index 000000000..278fac4f8 --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-hdpi/linked_https.png diff --git a/OpenKeychain/src/main/res/drawable-hdpi/linked_twitter.png b/OpenKeychain/src/main/res/drawable-hdpi/linked_twitter.png Binary files differnew file mode 100644 index 000000000..dd0fce197 --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-hdpi/linked_twitter.png diff --git a/OpenKeychain/src/main/res/drawable-hdpi/status_signature_verified_inner_24dp.png b/OpenKeychain/src/main/res/drawable-hdpi/status_signature_verified_inner_24dp.png Binary files differnew file mode 100644 index 000000000..e9dfc47a4 --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-hdpi/status_signature_verified_inner_24dp.png diff --git a/OpenKeychain/src/main/res/drawable-hdpi/status_signature_verified_inner_96dp.png b/OpenKeychain/src/main/res/drawable-hdpi/status_signature_verified_inner_96dp.png Binary files differnew file mode 100644 index 000000000..4a3f2b2b5 --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-hdpi/status_signature_verified_inner_96dp.png diff --git a/OpenKeychain/src/main/res/drawable-mdpi/linked_dns.png b/OpenKeychain/src/main/res/drawable-mdpi/linked_dns.png Binary files differnew file mode 100644 index 000000000..c6f3e9a06 --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-mdpi/linked_dns.png diff --git a/OpenKeychain/src/main/res/drawable-mdpi/linked_github.png b/OpenKeychain/src/main/res/drawable-mdpi/linked_github.png Binary files differnew file mode 100644 index 000000000..ba3d2e4b7 --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-mdpi/linked_github.png diff --git a/OpenKeychain/src/main/res/drawable-mdpi/linked_https.png b/OpenKeychain/src/main/res/drawable-mdpi/linked_https.png Binary files differnew file mode 100644 index 000000000..54b085aaf --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-mdpi/linked_https.png diff --git a/OpenKeychain/src/main/res/drawable-mdpi/linked_twitter.png b/OpenKeychain/src/main/res/drawable-mdpi/linked_twitter.png Binary files differnew file mode 100644 index 000000000..86ca9d46e --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-mdpi/linked_twitter.png diff --git a/OpenKeychain/src/main/res/drawable-mdpi/status_signature_verified_inner_24dp.png b/OpenKeychain/src/main/res/drawable-mdpi/status_signature_verified_inner_24dp.png Binary files differnew file mode 100644 index 000000000..28e37d9f3 --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-mdpi/status_signature_verified_inner_24dp.png diff --git a/OpenKeychain/src/main/res/drawable-mdpi/status_signature_verified_inner_96dp.png b/OpenKeychain/src/main/res/drawable-mdpi/status_signature_verified_inner_96dp.png Binary files differnew file mode 100644 index 000000000..cc7091df0 --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-mdpi/status_signature_verified_inner_96dp.png diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/linked_dns.png b/OpenKeychain/src/main/res/drawable-xhdpi/linked_dns.png Binary files differnew file mode 100644 index 000000000..ec87cdd1c --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-xhdpi/linked_dns.png diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/linked_github.png b/OpenKeychain/src/main/res/drawable-xhdpi/linked_github.png Binary files differnew file mode 100644 index 000000000..0934f1840 --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-xhdpi/linked_github.png diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/linked_https.png b/OpenKeychain/src/main/res/drawable-xhdpi/linked_https.png Binary files differnew file mode 100644 index 000000000..f627d2b3b --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-xhdpi/linked_https.png diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/linked_twitter.png b/OpenKeychain/src/main/res/drawable-xhdpi/linked_twitter.png Binary files differnew file mode 100644 index 000000000..35bb1fd3b --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-xhdpi/linked_twitter.png diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/status_signature_verified_inner_24dp.png b/OpenKeychain/src/main/res/drawable-xhdpi/status_signature_verified_inner_24dp.png Binary files differnew file mode 100644 index 000000000..ea801c45a --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-xhdpi/status_signature_verified_inner_24dp.png diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/status_signature_verified_inner_96dp.png b/OpenKeychain/src/main/res/drawable-xhdpi/status_signature_verified_inner_96dp.png Binary files differnew file mode 100644 index 000000000..61610df19 --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-xhdpi/status_signature_verified_inner_96dp.png diff --git a/OpenKeychain/src/main/res/drawable-xxhdpi/linked_dns.png b/OpenKeychain/src/main/res/drawable-xxhdpi/linked_dns.png Binary files differnew file mode 100644 index 000000000..9b3ee8572 --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-xxhdpi/linked_dns.png diff --git a/OpenKeychain/src/main/res/drawable-xxhdpi/linked_github.png b/OpenKeychain/src/main/res/drawable-xxhdpi/linked_github.png Binary files differnew file mode 100644 index 000000000..19a3421bf --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-xxhdpi/linked_github.png diff --git a/OpenKeychain/src/main/res/drawable-xxhdpi/linked_https.png b/OpenKeychain/src/main/res/drawable-xxhdpi/linked_https.png Binary files differnew file mode 100644 index 000000000..c41ab6c1c --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-xxhdpi/linked_https.png diff --git a/OpenKeychain/src/main/res/drawable-xxhdpi/linked_twitter.png b/OpenKeychain/src/main/res/drawable-xxhdpi/linked_twitter.png Binary files differnew file mode 100644 index 000000000..0710a12f1 --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-xxhdpi/linked_twitter.png diff --git a/OpenKeychain/src/main/res/drawable-xxhdpi/status_signature_verified_inner_24dp.png b/OpenKeychain/src/main/res/drawable-xxhdpi/status_signature_verified_inner_24dp.png Binary files differnew file mode 100644 index 000000000..bcd73b09b --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-xxhdpi/status_signature_verified_inner_24dp.png diff --git a/OpenKeychain/src/main/res/drawable-xxhdpi/status_signature_verified_inner_96dp.png b/OpenKeychain/src/main/res/drawable-xxhdpi/status_signature_verified_inner_96dp.png Binary files differnew file mode 100644 index 000000000..71e563218 --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-xxhdpi/status_signature_verified_inner_96dp.png diff --git a/OpenKeychain/src/main/res/drawable-xxxhdpi/status_signature_verified_inner_24dp.png b/OpenKeychain/src/main/res/drawable-xxxhdpi/status_signature_verified_inner_24dp.png Binary files differnew file mode 100644 index 000000000..cc7091df0 --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-xxxhdpi/status_signature_verified_inner_24dp.png diff --git a/OpenKeychain/src/main/res/drawable/divider.xml b/OpenKeychain/src/main/res/drawable/divider.xml new file mode 100644 index 000000000..b0695f89a --- /dev/null +++ b/OpenKeychain/src/main/res/drawable/divider.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle" > + + <size + android:height="1px" + android:width="1000dp" /> + + <solid android:color="@color/bg_gray" /> + +</shape>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/cert_list_widget.xml b/OpenKeychain/src/main/res/layout/cert_list_widget.xml new file mode 100644 index 000000000..7e3dbcdf4 --- /dev/null +++ b/OpenKeychain/src/main/res/layout/cert_list_widget.xml @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="utf-8"?> +<org.sufficientlysecure.keychain.ui.widget.CertListWidget + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:id="@+id/linked_id_certs" + tools:showIn="@layout/linked_id_view_fragment"> + + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + tools:ignore="UseCompoundDrawables"> + + <TextView + android:id="@+id/cert_collapsed_list" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceMedium" + android:gravity="center_vertical" + android:layout_weight="1" + tools:text="The identity is not yet verified or confirmed." + /> + + <ImageView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:src="@drawable/ic_expand_more_black_24dp" + android:id="@+id/cert_expand_button" + android:padding="4dp" + /> + + </LinearLayout> + + <ListView + android:id="@+id/cert_expanded_list" + android:layout_width="fill_parent" + android:layout_height="wrap_content"> + </ListView> + +</org.sufficientlysecure.keychain.ui.widget.CertListWidget>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/decrypt_text_fragment.xml b/OpenKeychain/src/main/res/layout/decrypt_text_fragment.xml index ea6be462f..9167a2859 100644 --- a/OpenKeychain/src/main/res/layout/decrypt_text_fragment.xml +++ b/OpenKeychain/src/main/res/layout/decrypt_text_fragment.xml @@ -1,5 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <LinearLayout 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:orientation="vertical"> @@ -9,7 +10,8 @@ android:id="@+id/decrypt_content" android:layout_width="match_parent" android:layout_height="match_parent" - android:orientation="vertical"> + android:orientation="vertical" + tools:visibility="visible"> <ScrollView android:fillViewport="true" @@ -35,12 +37,12 @@ </LinearLayout> <LinearLayout - android:visibility="gone" android:id="@+id/decrypt_error_overlay" android:layout_width="wrap_content" android:layout_height="match_parent" android:orientation="vertical" - android:gravity="center_vertical"> + android:gravity="center_vertical" + android:visibility="gone"> <TextView android:layout_width="wrap_content" @@ -60,4 +62,4 @@ android:text="@string/decrypt_invalid_button" android:layout_gravity="center_horizontal" /> </LinearLayout> -</LinearLayout>
\ No newline at end of file +</LinearLayout> diff --git a/OpenKeychain/src/main/res/layout/encrypt_decrypt_overview_fragment.xml b/OpenKeychain/src/main/res/layout/encrypt_decrypt_overview_fragment.xml index bd640d9af..6f76ada72 100644 --- a/OpenKeychain/src/main/res/layout/encrypt_decrypt_overview_fragment.xml +++ b/OpenKeychain/src/main/res/layout/encrypt_decrypt_overview_fragment.xml @@ -105,14 +105,6 @@ android:layout_height="wrap_content" android:text="@string/btn_decrypt_clipboard" /> - <TextView - android:textAppearance="?android:attr/textAppearanceSmall" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:textColor="@color/tertiary_text_light" - android:text="@string/btn_decrypt_and_verify" - android:gravity="center_vertical" /> - </LinearLayout> <ImageView diff --git a/OpenKeychain/src/main/res/layout/linked_create_dns_fragment_step1.xml b/OpenKeychain/src/main/res/layout/linked_create_dns_fragment_step1.xml new file mode 100644 index 000000000..9df381ea0 --- /dev/null +++ b/OpenKeychain/src/main/res/layout/linked_create_dns_fragment_step1.xml @@ -0,0 +1,126 @@ +<?xml version="1.0" encoding="UTF-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="match_parent"> + + <ScrollView + android:layout_width="match_parent" + android:layout_height="match_parent" + android:fillViewport="false" + android:layout_above="@+id/create_key_button_divider"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="16dp" + android:orientation="vertical"> + + <TextView + android:drawableLeft="@drawable/linked_dns" + android:drawablePadding="8dp" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceMedium" + android:text="@string/linked_create_dns_1_1" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:text="@string/linked_create_dns_1_2" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:text="@string/linked_create_dns_1_3" /> + + <EditText + android:id="@+id/linked_create_dns_domain" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:imeOptions="actionNext" + android:ems="10" + android:inputType="textUri" + android:layout_gravity="center_horizontal" + /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:text="@string/linked_create_dns_1_4" /> + + </LinearLayout> + + </ScrollView> + + <View + android:id="@+id/create_key_button_divider" + android:layout_width="match_parent" + android:layout_height="1dip" + android:layout_marginLeft="16dp" + android:layout_marginRight="16dp" + android:background="?android:attr/listDivider" + android:layout_alignTop="@+id/create_key_buttons" + android:layout_alignParentLeft="true" + android:layout_alignParentStart="true" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:layout_alignParentBottom="true" + android:layout_alignParentLeft="true" + android:layout_alignParentStart="true" + android:layout_marginLeft="16dp" + android:layout_marginRight="16dp" + android:id="@+id/create_key_buttons"> + + <TextView + android:id="@+id/back_button" + android:paddingLeft="8dp" + android:paddingRight="8dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:text="@string/btn_back" + android:clickable="true" + android:minHeight="?android:attr/listPreferredItemHeight" + android:textAllCaps="true" + style="?android:attr/borderlessButtonStyle" + android:drawableLeft="@drawable/ic_chevron_left_grey_24dp" + android:drawablePadding="8dp" + android:gravity="center_vertical" + android:layout_gravity="center_vertical" /> + + <View + android:layout_width="1dp" + android:layout_height="match_parent" + android:layout_marginTop="8dp" + android:layout_marginBottom="8dp" + android:background="?android:attr/listDivider" /> + + <TextView + android:id="@+id/next_button" + android:paddingLeft="8dp" + android:paddingRight="8dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:text="@string/btn_next" + android:minHeight="?android:attr/listPreferredItemHeight" + android:textAllCaps="true" + android:drawableRight="@drawable/ic_chevron_right_grey_24dp" + android:drawablePadding="8dp" + android:gravity="center_vertical|right" + android:clickable="true" + style="?android:attr/borderlessButtonStyle" + android:layout_gravity="center_vertical" /> + </LinearLayout> +</RelativeLayout>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/linked_create_dns_fragment_step2.xml b/OpenKeychain/src/main/res/layout/linked_create_dns_fragment_step2.xml new file mode 100644 index 000000000..62e267f05 --- /dev/null +++ b/OpenKeychain/src/main/res/layout/linked_create_dns_fragment_step2.xml @@ -0,0 +1,164 @@ +<?xml version="1.0" encoding="UTF-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="match_parent"> + + <ScrollView + android:layout_width="match_parent" + android:layout_height="match_parent" + android:fillViewport="false" + android:layout_above="@+id/create_key_button_divider"> + + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:paddingLeft="16dp" + android:paddingRight="16dp" + android:orientation="vertical"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:text="@string/linked_create_dns_2_1" /> + + <TextView + android:id="@+id/linked_create_dns_text" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:layout_gravity="center_horizontal" + android:textIsSelectable="true" + android:textAppearance="?android:attr/textAppearanceSmall" + /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:text="@string/linked_create_dns_2_2" /> + + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:layout_gravity="center_horizontal" + style="?android:buttonBarStyle" + android:showDividers="middle"> + + <Button + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1" + android:layout_marginLeft="8dp" + android:layout_marginRight="8dp" + style="?android:buttonBarButtonStyle" + android:drawableLeft="@drawable/ic_content_copy_grey_24dp" + android:drawableStart="@android:drawable/ic_menu_share" + android:text="Copy" + android:id="@+id/button_save" + /> + + <Button + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1" + android:layout_marginLeft="8dp" + android:layout_marginRight="8dp" + style="?android:buttonBarButtonStyle" + android:drawableLeft="@android:drawable/ic_menu_share" + android:drawableStart="@android:drawable/ic_menu_share" + android:text="Share" + android:id="@+id/button_send" + /> + + </LinearLayout> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:text="@string/linked_create_dns_2_3" /> + + <include layout="@layout/linked_create_verify" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:text="@string/linked_create_dns_2_4" /> + + </LinearLayout> + + </ScrollView> + + <View + android:id="@+id/create_key_button_divider" + android:layout_width="match_parent" + android:layout_height="1dip" + android:layout_marginLeft="16dp" + android:layout_marginRight="16dp" + android:background="?android:attr/listDivider" + android:layout_alignTop="@+id/create_key_buttons" + android:layout_alignParentLeft="true" + android:layout_alignParentStart="true" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:layout_alignParentBottom="true" + android:layout_alignParentLeft="true" + android:layout_alignParentStart="true" + android:layout_marginLeft="16dp" + android:layout_marginRight="16dp" + android:id="@+id/create_key_buttons"> + + <TextView + android:id="@+id/back_button" + android:paddingLeft="8dp" + android:paddingRight="8dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:text="@string/btn_back" + android:minHeight="?android:attr/listPreferredItemHeight" + android:clickable="true" + android:textAllCaps="true" + style="?android:attr/borderlessButtonStyle" + android:drawableLeft="@drawable/ic_chevron_left_grey_24dp" + android:drawablePadding="8dp" + android:gravity="center_vertical" + android:layout_gravity="center_vertical" /> + + <View + android:layout_width="1dp" + android:layout_height="match_parent" + android:layout_marginTop="8dp" + android:layout_marginBottom="8dp" + android:background="?android:attr/listDivider" /> + + <TextView + android:id="@+id/next_button" + android:paddingLeft="8dp" + android:paddingRight="8dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:text="@string/btn_finish" + android:minHeight="?android:attr/listPreferredItemHeight" + android:drawableRight="@drawable/ic_person_add_grey_24dp" + android:drawablePadding="8dp" + android:textAllCaps="true" + style="?android:attr/borderlessButtonStyle" + android:gravity="center_vertical|right" + android:clickable="true" + android:layout_gravity="center_vertical" /> + </LinearLayout> +</RelativeLayout>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/linked_create_github_fragment_step1.xml b/OpenKeychain/src/main/res/layout/linked_create_github_fragment_step1.xml new file mode 100644 index 000000000..2b8a810e0 --- /dev/null +++ b/OpenKeychain/src/main/res/layout/linked_create_github_fragment_step1.xml @@ -0,0 +1,119 @@ +<?xml version="1.0" encoding="UTF-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="match_parent"> + + <ScrollView + android:layout_width="match_parent" + android:layout_height="match_parent" + android:fillViewport="false" + android:layout_above="@+id/create_key_button_divider"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="16dp" + android:orientation="vertical"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:drawableLeft="@drawable/linked_github" + android:drawablePadding="8dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:text="@string/linked_create_github_1_1" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:text="@string/linked_create_github_1_2" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:text="@string/linked_create_github_1_3" /> + + <EditText + android:id="@+id/linked_create_github_handle" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:imeOptions="actionNext" + android:layout_marginTop="16dp" + android:ems="10" + android:layout_gravity="center_horizontal" + android:hint="@string/linked_create_github_handle"/> + + </LinearLayout> + + </ScrollView> + + <View + android:id="@+id/create_key_button_divider" + android:layout_width="match_parent" + android:layout_height="1dip" + android:layout_marginLeft="16dp" + android:layout_marginRight="16dp" + android:background="?android:attr/listDivider" + android:layout_alignTop="@+id/create_key_buttons" + android:layout_alignParentLeft="true" + android:layout_alignParentStart="true" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:layout_alignParentBottom="true" + android:layout_alignParentLeft="true" + android:layout_alignParentStart="true" + android:layout_marginLeft="16dp" + android:layout_marginRight="16dp" + android:id="@+id/create_key_buttons"> + + <TextView + android:id="@+id/back_button" + android:paddingLeft="8dp" + android:paddingRight="8dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:text="@string/btn_back" + android:minHeight="?android:attr/listPreferredItemHeight" + android:textAllCaps="true" + style="?android:attr/borderlessButtonStyle" + android:drawableLeft="@drawable/ic_chevron_left_grey_24dp" + android:drawablePadding="8dp" + android:clickable="true" + android:gravity="center_vertical" + android:layout_gravity="center_vertical" /> + + <View + android:layout_width="1dp" + android:layout_height="match_parent" + android:layout_marginTop="8dp" + android:layout_marginBottom="8dp" + android:background="?android:attr/listDivider" /> + + <TextView + android:id="@+id/next_button" + android:paddingLeft="8dp" + android:paddingRight="8dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:text="@string/btn_next" + android:minHeight="?android:attr/listPreferredItemHeight" + android:textAllCaps="true" + style="?android:attr/borderlessButtonStyle" + android:drawableRight="@drawable/ic_chevron_right_grey_24dp" + android:drawablePadding="8dp" + android:gravity="center_vertical|right" + android:layout_gravity="center_vertical" /> + </LinearLayout> +</RelativeLayout>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/linked_create_github_fragment_step2.xml b/OpenKeychain/src/main/res/layout/linked_create_github_fragment_step2.xml new file mode 100644 index 000000000..81844e739 --- /dev/null +++ b/OpenKeychain/src/main/res/layout/linked_create_github_fragment_step2.xml @@ -0,0 +1,152 @@ +<?xml version="1.0" encoding="UTF-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="match_parent"> + + <ScrollView + android:layout_width="match_parent" + android:layout_height="match_parent" + android:fillViewport="false" + android:layout_above="@+id/create_key_button_divider"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingLeft="16dp" + android:paddingRight="16dp" + android:orientation="vertical"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:text="@string/linked_create_github_2_1" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:text="@string/linked_create_github_2_2" /> + + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:layout_gravity="center_horizontal" + style="?android:buttonBarStyle"> + + <Button + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1" + android:layout_marginLeft="8dp" + android:layout_marginRight="8dp" + style="?android:buttonBarButtonStyle" + android:drawableLeft="@android:drawable/ic_menu_send" + android:drawableStart="@android:drawable/ic_menu_send" + android:text="Post Gist" + android:id="@+id/button_send" + /> + + <Button + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1" + android:layout_marginLeft="8dp" + android:layout_marginRight="8dp" + style="?android:buttonBarButtonStyle" + android:drawableLeft="@android:drawable/ic_menu_share" + android:drawableStart="@android:drawable/ic_menu_share" + android:text="Share" + android:id="@+id/button_share" + /> + + </LinearLayout> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:text="@string/linked_create_github_2_3" /> + + <include layout="@layout/linked_create_verify" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:text="@string/linked_create_github_2_4" /> + + </LinearLayout> + + </ScrollView> + + <View + android:id="@+id/create_key_button_divider" + android:layout_width="match_parent" + android:layout_height="1dip" + android:layout_marginLeft="16dp" + android:layout_marginRight="16dp" + android:background="?android:attr/listDivider" + android:layout_alignTop="@+id/create_key_buttons" + android:layout_alignParentLeft="true" + android:layout_alignParentStart="true" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:layout_alignParentBottom="true" + android:layout_alignParentLeft="true" + android:layout_alignParentStart="true" + android:layout_marginLeft="16dp" + android:layout_marginRight="16dp" + android:id="@+id/create_key_buttons"> + + <TextView + android:id="@+id/back_button" + android:paddingLeft="8dp" + android:paddingRight="8dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:text="@string/btn_back" + android:minHeight="?android:attr/listPreferredItemHeight" + android:textAllCaps="true" + style="?android:attr/borderlessButtonStyle" + android:drawableLeft="@drawable/ic_chevron_left_grey_24dp" + android:drawablePadding="8dp" + android:gravity="center_vertical" + android:clickable="true" + android:layout_gravity="center_vertical" /> + + <View + android:layout_width="1dp" + android:layout_height="match_parent" + android:layout_marginTop="8dp" + android:layout_marginBottom="8dp" + android:background="?android:attr/listDivider" /> + + <TextView + android:id="@+id/next_button" + android:paddingLeft="8dp" + android:paddingRight="8dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:text="@string/btn_finish" + android:minHeight="?android:attr/listPreferredItemHeight" + android:textAllCaps="true" + android:drawableRight="@drawable/ic_person_add_grey_24dp" + android:drawablePadding="8dp" + style="?android:attr/borderlessButtonStyle" + android:gravity="center_vertical|right" + android:layout_gravity="center_vertical" /> + </LinearLayout> +</RelativeLayout>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/linked_create_https_fragment_step1.xml b/OpenKeychain/src/main/res/layout/linked_create_https_fragment_step1.xml new file mode 100644 index 000000000..8b6047e21 --- /dev/null +++ b/OpenKeychain/src/main/res/layout/linked_create_https_fragment_step1.xml @@ -0,0 +1,129 @@ +<?xml version="1.0" encoding="UTF-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:custom="http://schemas.android.com/apk/res-auto" + android:layout_width="wrap_content" + android:layout_height="match_parent"> + + <ScrollView + android:layout_width="match_parent" + android:layout_height="match_parent" + android:fillViewport="false" + android:layout_above="@+id/create_key_button_divider"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="16dp" + android:orientation="vertical"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:drawableLeft="@drawable/linked_https" + android:drawablePadding="8dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:text="@string/linked_create_https_1_1" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:text="@string/linked_create_https_1_2" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:text="@string/linked_create_https_1_3" /> + + <org.sufficientlysecure.keychain.ui.widget.PrefixedEditText + android:id="@+id/linked_create_https_uri" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:imeOptions="actionNext" + android:layout_marginTop="16dp" + android:ems="10" + android:inputType="textUri" + android:layout_gravity="center_horizontal" + custom:prefix="https://" + /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:text="@string/linked_create_https_1_4" /> + + </LinearLayout> + + </ScrollView> + + <View + android:id="@+id/create_key_button_divider" + android:layout_width="match_parent" + android:layout_height="1dip" + android:layout_marginLeft="16dp" + android:layout_marginRight="16dp" + android:background="?android:attr/listDivider" + android:layout_alignTop="@+id/create_key_buttons" + android:layout_alignParentLeft="true" + android:layout_alignParentStart="true" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:layout_alignParentBottom="true" + android:layout_alignParentLeft="true" + android:layout_alignParentStart="true" + android:layout_marginLeft="16dp" + android:layout_marginRight="16dp" + android:id="@+id/create_key_buttons"> + + <TextView + android:id="@+id/back_button" + android:paddingLeft="8dp" + android:paddingRight="8dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:text="@string/btn_back" + android:clickable="true" + android:minHeight="?android:attr/listPreferredItemHeight" + android:textAllCaps="true" + style="?android:attr/borderlessButtonStyle" + android:drawableLeft="@drawable/ic_chevron_left_grey_24dp" + android:drawablePadding="8dp" + android:gravity="center_vertical" + android:layout_gravity="center_vertical" /> + + <View + android:layout_width="1dp" + android:layout_height="match_parent" + android:layout_marginTop="8dp" + android:layout_marginBottom="8dp" + android:background="?android:attr/listDivider" /> + + <TextView + android:id="@+id/next_button" + android:paddingLeft="8dp" + android:paddingRight="8dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:text="@string/btn_next" + android:minHeight="?android:attr/listPreferredItemHeight" + android:textAllCaps="true" + android:drawableRight="@drawable/ic_chevron_right_grey_24dp" + android:drawablePadding="8dp" + android:gravity="center_vertical|right" + style="?android:attr/borderlessButtonStyle" + android:clickable="true" + android:layout_gravity="center_vertical" /> + </LinearLayout> +</RelativeLayout>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/linked_create_https_fragment_step2.xml b/OpenKeychain/src/main/res/layout/linked_create_https_fragment_step2.xml new file mode 100644 index 000000000..dc6895a54 --- /dev/null +++ b/OpenKeychain/src/main/res/layout/linked_create_https_fragment_step2.xml @@ -0,0 +1,165 @@ +<?xml version="1.0" encoding="UTF-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="match_parent"> + + <ScrollView + android:layout_width="match_parent" + android:layout_height="match_parent" + android:fillViewport="false" + android:layout_above="@+id/create_key_button_divider"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingLeft="16dp" + android:paddingRight="16dp" + android:orientation="vertical"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:text="@string/linked_create_https_2_1" /> + + <EditText + android:id="@+id/linked_create_https_uri" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:imeOptions="actionNext" + android:layout_marginTop="8dp" + android:hint="uri" + android:ems="10" + android:layout_gravity="center_horizontal" + android:inputType="textUri|none" + android:enabled="false"/> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:text="@string/linked_create_https_2_2" /> + + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:layout_gravity="center_horizontal" + style="?android:buttonBarStyle" + > + + <Button + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1" + android:layout_marginLeft="8dp" + android:layout_marginRight="8dp" + style="?android:buttonBarButtonStyle" + android:drawableLeft="@android:drawable/ic_menu_save" + android:drawableStart="@android:drawable/ic_menu_save" + android:text="Save" + android:id="@+id/button_save" + /> + + <Button + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1" + android:layout_marginLeft="8dp" + android:layout_marginRight="8dp" + style="?android:buttonBarButtonStyle" + android:drawableLeft="@android:drawable/ic_menu_share" + android:drawableStart="@android:drawable/ic_menu_share" + android:text="Share" + android:id="@+id/button_send" + /> + + </LinearLayout> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:text="@string/linked_create_https_2_3" /> + + <include layout="@layout/linked_create_verify" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:text="@string/linked_create_https_2_4" /> + + </LinearLayout> + + </ScrollView> + + <View + android:id="@+id/create_key_button_divider" + android:layout_width="match_parent" + android:layout_height="1dip" + android:layout_marginLeft="16dp" + android:layout_marginRight="16dp" + android:background="?android:attr/listDivider" + android:layout_alignTop="@+id/create_key_buttons" + android:layout_alignParentLeft="true" + android:layout_alignParentStart="true" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:layout_alignParentBottom="true" + android:layout_alignParentLeft="true" + android:layout_alignParentStart="true" + android:layout_marginLeft="16dp" + android:layout_marginRight="16dp" + android:id="@+id/create_key_buttons"> + + <TextView + android:id="@+id/back_button" + android:paddingLeft="8dp" + android:paddingRight="8dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:text="@string/btn_back" + android:minHeight="?android:attr/listPreferredItemHeight" + android:clickable="true" + android:textAllCaps="true" + style="?android:attr/borderlessButtonStyle" + android:drawableLeft="@drawable/ic_chevron_left_grey_24dp" + android:drawablePadding="8dp" + android:gravity="center_vertical" + android:layout_gravity="center_vertical" /> + + <View + android:layout_width="1dp" + android:layout_height="match_parent" + android:layout_marginTop="8dp" + android:layout_marginBottom="8dp" + android:background="?android:attr/listDivider" /> + + <TextView + android:id="@+id/next_button" + android:paddingLeft="8dp" + android:paddingRight="8dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:text="@string/btn_finish" + android:minHeight="?android:attr/listPreferredItemHeight" + android:textAllCaps="true" + android:drawableRight="@drawable/ic_person_add_grey_24dp" + android:drawablePadding="8dp" + style="?android:attr/borderlessButtonStyle" + android:gravity="center_vertical|right" + android:layout_gravity="center_vertical" /> + </LinearLayout> +</RelativeLayout>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/linked_create_twitter_fragment_step1.xml b/OpenKeychain/src/main/res/layout/linked_create_twitter_fragment_step1.xml new file mode 100644 index 000000000..848ee47f4 --- /dev/null +++ b/OpenKeychain/src/main/res/layout/linked_create_twitter_fragment_step1.xml @@ -0,0 +1,123 @@ +<?xml version="1.0" encoding="UTF-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:custom="http://schemas.android.com/apk/res-auto" + android:layout_width="wrap_content" + android:layout_height="match_parent" + > + + <ScrollView + android:layout_width="match_parent" + android:layout_height="match_parent" + android:fillViewport="false" + android:layout_above="@+id/create_key_button_divider"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="16dp" + android:orientation="vertical"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:drawableLeft="@drawable/linked_twitter" + android:drawablePadding="8dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:text="@string/linked_create_twitter_1_1" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:text="@string/linked_create_twitter_1_2" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:text="@string/linked_create_twitter_1_3" /> + + <org.sufficientlysecure.keychain.ui.widget.PrefixedEditText + android:id="@+id/linked_create_twitter_handle" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:imeOptions="actionNext" + android:layout_marginTop="16dp" + android:ems="10" + android:layout_gravity="center_horizontal" + android:inputType="text" + android:hint="@string/linked_create_twitter_handle" + custom:prefix="\@" + /> + + </LinearLayout> + + </ScrollView> + + <View + android:id="@+id/create_key_button_divider" + android:layout_width="match_parent" + android:layout_height="1dip" + android:layout_marginLeft="16dp" + android:layout_marginRight="16dp" + android:background="?android:attr/listDivider" + android:layout_alignTop="@+id/create_key_buttons" + android:layout_alignParentLeft="true" + android:layout_alignParentStart="true" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:layout_alignParentBottom="true" + android:layout_alignParentLeft="true" + android:layout_alignParentStart="true" + android:layout_marginLeft="16dp" + android:layout_marginRight="16dp" + android:id="@+id/create_key_buttons"> + + <TextView + android:id="@+id/back_button" + android:paddingLeft="8dp" + android:paddingRight="8dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:text="@string/btn_back" + android:minHeight="?android:attr/listPreferredItemHeight" + android:textAllCaps="true" + style="?android:attr/borderlessButtonStyle" + android:drawableLeft="@drawable/ic_chevron_left_grey_24dp" + android:drawablePadding="8dp" + android:clickable="true" + android:gravity="center_vertical" + android:layout_gravity="center_vertical" /> + + <View + android:layout_width="1dp" + android:layout_height="match_parent" + android:layout_marginTop="8dp" + android:layout_marginBottom="8dp" + android:background="?android:attr/listDivider" /> + + <TextView + android:id="@+id/next_button" + android:paddingLeft="8dp" + android:paddingRight="8dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:text="@string/btn_next" + android:minHeight="?android:attr/listPreferredItemHeight" + android:textAllCaps="true" + style="?android:attr/borderlessButtonStyle" + android:drawableRight="@drawable/ic_chevron_right_grey_24dp" + android:drawablePadding="8dp" + android:gravity="center_vertical|right" + android:layout_gravity="center_vertical" /> + </LinearLayout> +</RelativeLayout>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/linked_create_twitter_fragment_step2.xml b/OpenKeychain/src/main/res/layout/linked_create_twitter_fragment_step2.xml new file mode 100644 index 000000000..e66946482 --- /dev/null +++ b/OpenKeychain/src/main/res/layout/linked_create_twitter_fragment_step2.xml @@ -0,0 +1,154 @@ +<?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="wrap_content" + android:layout_height="match_parent"> + + <ScrollView + android:layout_width="match_parent" + android:layout_height="match_parent" + android:fillViewport="false" + android:layout_above="@+id/create_key_button_divider"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingLeft="16dp" + android:paddingRight="16dp" + android:orientation="vertical"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:text="@string/linked_create_twitter_2_1" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:textAppearance="?android:attr/textAppearanceSmall" + android:text="@string/linked_create_twitter_2_2" /> + + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:layout_gravity="center_horizontal" + style="?android:buttonBarStyle"> + + <Button + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1" + android:layout_marginLeft="8dp" + android:layout_marginRight="8dp" + style="?android:buttonBarButtonStyle" + android:drawableLeft="@android:drawable/ic_menu_send" + android:drawableStart="@android:drawable/ic_menu_send" + android:text="Tweet" + android:id="@+id/button_send" + /> + + <Button + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1" + android:layout_marginLeft="8dp" + android:layout_marginRight="8dp" + style="?android:buttonBarButtonStyle" + android:drawableLeft="@android:drawable/ic_menu_share" + android:drawableStart="@android:drawable/ic_menu_share" + android:text="Share" + android:id="@+id/button_share" + /> + + </LinearLayout> + + <TextView + android:id="@+id/linked_tweet_published" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:textAppearance="?android:attr/textAppearanceMedium" + tools:text="@string/linked_create_twitter_2_3" /> + + <include layout="@layout/linked_create_verify" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:text="@string/linked_create_twitter_2_4" /> + + </LinearLayout> + + </ScrollView> + + <View + android:id="@+id/create_key_button_divider" + android:layout_width="match_parent" + android:layout_height="1dip" + android:layout_marginLeft="16dp" + android:layout_marginRight="16dp" + android:background="?android:attr/listDivider" + android:layout_alignTop="@+id/create_key_buttons" + android:layout_alignParentLeft="true" + android:layout_alignParentStart="true" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:layout_alignParentBottom="true" + android:layout_alignParentLeft="true" + android:layout_alignParentStart="true" + android:layout_marginLeft="16dp" + android:layout_marginRight="16dp" + android:id="@+id/create_key_buttons"> + + <TextView + android:id="@+id/back_button" + android:paddingLeft="8dp" + android:paddingRight="8dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:text="@string/btn_back" + android:minHeight="?android:attr/listPreferredItemHeight" + android:textAllCaps="true" + style="?android:attr/borderlessButtonStyle" + android:drawableLeft="@drawable/ic_chevron_left_grey_24dp" + android:drawablePadding="8dp" + android:gravity="center_vertical" + android:clickable="true" + android:layout_gravity="center_vertical" /> + + <View + android:layout_width="1dp" + android:layout_height="match_parent" + android:layout_marginTop="8dp" + android:layout_marginBottom="8dp" + android:background="?android:attr/listDivider" /> + + <TextView + android:id="@+id/next_button" + android:paddingLeft="8dp" + android:paddingRight="8dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:text="@string/btn_finish" + android:minHeight="?android:attr/listPreferredItemHeight" + android:textAllCaps="true" + android:drawableRight="@drawable/ic_person_add_grey_24dp" + android:drawablePadding="8dp" + style="?android:attr/borderlessButtonStyle" + android:gravity="center_vertical|right" + android:layout_gravity="center_vertical" /> + </LinearLayout> +</RelativeLayout>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/linked_create_verify.xml b/OpenKeychain/src/main/res/layout/linked_create_verify.xml new file mode 100644 index 000000000..234f43334 --- /dev/null +++ b/OpenKeychain/src/main/res/layout/linked_create_verify.xml @@ -0,0 +1,79 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + tools:showIn="@layout/linked_create_https_fragment_step2"> + + <ViewAnimator + android:id="@+id/verify_progress" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginLeft="16dip" + android:layout_marginStart="16dip" + android:layout_gravity="center_vertical" + android:inAnimation="@anim/fade_in" + android:outAnimation="@anim/fade_out" + > + + <ImageView + android:layout_width="24dp" + android:layout_height="24dp" + android:id="@+id/verify_image" + android:src="@drawable/status_signature_unverified_cutout_24dp" + /> + + <ProgressBar + android:layout_width="24dp" + android:layout_height="24dp" + android:indeterminateOnly="true" /> + + </ViewAnimator> + + <TextView + android:id="@+id/verify_status" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:textAppearance="?android:attr/textAppearanceMedium" + android:text="@string/linked_verify_pending" + android:layout_marginLeft="16dip" + android:layout_marginStart="16dip" + android:layout_weight="1" + /> + + <ViewAnimator + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginRight="16dp" + android:layout_marginEnd="16dip" + android:inAnimation="@anim/fade_in" + android:outAnimation="@anim/fade_out" + android:id="@+id/verify_buttons" + > + + <Button + android:layout_width="wrap_content" + android:layout_height="wrap_content" + style="?android:buttonBarButtonStyle" + android:text="@string/linked_button_verify" + android:id="@+id/button_verify" + /> + + <Button + android:layout_width="wrap_content" + android:layout_height="wrap_content" + style="?android:buttonBarButtonStyle" + android:text="@string/linked_button_retry" + android:id="@+id/button_retry" + /> + + <Space + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + + </ViewAnimator> + +</LinearLayout>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/linked_id_item.xml b/OpenKeychain/src/main/res/layout/linked_id_item.xml new file mode 100644 index 000000000..4846bd0a4 --- /dev/null +++ b/OpenKeychain/src/main/res/layout/linked_id_item.xml @@ -0,0 +1,75 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="?android:attr/listPreferredItemHeight" + android:orientation="horizontal" + android:singleLine="true" + tools:showIn="@layout/linked_id_view_fragment"> + + <ImageView + android:layout_width="32dp" + android:layout_height="32dp" + android:id="@+id/linked_id_type_icon" + android:layout_marginLeft="14dp" + android:layout_marginStart="14dp" + tools:src="@drawable/linked_dns" + android:layout_gravity="center_vertical" + android:scaleType="fitCenter" /> + + <LinearLayout + android:orientation="vertical" + android:layout_gravity="center_vertical" + android:layout_width="0dip" + android:layout_marginLeft="8dp" + android:layout_marginTop="4dp" + android:layout_marginBottom="4dp" + android:layout_height="wrap_content" + android:layout_weight="1"> + + <TextView + android:id="@+id/linked_id_title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + tools:text="Title" + android:textAppearance="?android:attr/textAppearanceMedium" + /> + + <TextView + android:id="@+id/linked_id_comment" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textColor="@color/tertiary_text_light" + tools:text="comment" + android:textAppearance="?android:attr/textAppearanceSmall" + /> + + </LinearLayout> + + <ViewAnimator + android:id="@+id/linked_id_certified" + android:layout_width="22dp" + android:layout_height="wrap_content" + android:layout_marginLeft="16dp" + android:layout_marginRight="16dp" + android:outAnimation="@anim/fade_out_down" + android:inAnimation="@anim/fade_in_up" + android:layout_gravity="center_vertical" + android:orientation="vertical"> + + <ImageView + android:id="@+id/linked_id_certified_icon" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + tools:src="@drawable/status_signature_unknown_cutout_24dp" + /> + + <Space + android:layout_height="wrap_content" + android:layout_width="wrap_content" /> + + </ViewAnimator> + +</LinearLayout> diff --git a/OpenKeychain/src/main/res/layout/linked_id_view_fragment.xml b/OpenKeychain/src/main/res/layout/linked_id_view_fragment.xml new file mode 100644 index 000000000..377ce4fb6 --- /dev/null +++ b/OpenKeychain/src/main/res/layout/linked_id_view_fragment.xml @@ -0,0 +1,193 @@ +<?xml version="1.0" encoding="utf-8"?> +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:card_view="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:paddingTop="16dp" + android:paddingBottom="16dp" + android:paddingLeft="16dp" + android:paddingRight="16dp"> + + <android.support.v7.widget.CardView + android:id="@+id/card_linked_ids" + android:transitionName="card_linked_ids" + android:layout_width="match_parent" + android:layout_height="wrap_content" + card_view:cardBackgroundColor="@android:color/white" + card_view:cardElevation="2dp" + card_view:cardUseCompatPadding="true" + card_view:cardCornerRadius="4dp"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:animateLayoutChanges="true" + android:orientation="vertical"> + + <TextView + style="@style/CardViewHeader" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/card_linked_identity" /> + + <include layout="@layout/linked_id_item" /> + + <ViewAnimator + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:id="@+id/linked_verify_container" + android:layout_marginLeft="12dp" + android:layout_marginRight="12dp" + android:measureAllChildren="false" + > + + <include layout="@layout/cert_list_widget" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:baselineAligned="false" + android:animateLayoutChanges="true" + > + + <TextSwitcher + android:id="@+id/linked_cert_text" + android:layout_height="wrap_content" + android:layout_width="0dip" + android:layout_marginLeft="8dp" + android:layout_marginTop="4dp" + android:layout_marginBottom="4dp" + android:inAnimation="@anim/fade_in_quick" + android:outAnimation="@anim/fade_out_quick" + android:measureAllChildren="false" + android:layout_weight="1"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceSmall" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceSmall" /> + + </TextSwitcher> + + <ViewAnimator + android:layout_width="22dp" + android:layout_height="22dp" + android:layout_marginLeft="16dp" + android:layout_marginRight="4dp" + android:layout_gravity="center" + android:id="@+id/linked_cert_progress" + android:inAnimation="@anim/fade_in" + android:outAnimation="@anim/fade_out"> + + <ProgressBar + android:layout_width="22dp" + android:layout_height="22dp" + android:indeterminate="true" + /> + + <ImageView + android:id="@+id/status_icon_verified" + android:layout_width="22dp" + android:layout_height="wrap_content" + android:src="@drawable/status_signature_verified_inner_24dp" + /> + + <ImageView + android:id="@+id/status_icon_invalid" + android:layout_width="22dp" + android:layout_height="wrap_content" + android:src="@drawable/status_signature_invalid_cutout_24dp" + /> + + </ViewAnimator> + + </LinearLayout> + + </ViewAnimator> + + <!-- this layout is used for a highlight thing, so we use padding instead of margin --> + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:paddingLeft="14dp" + android:paddingRight="14dp" + android:id="@+id/cert_key_spincontainer" + android:visibility="gone" + tools:visibility="visible" + > + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:textAppearance="?android:attr/textAppearanceMedium" + android:layout_marginRight="4dp" + android:layout_marginEnd="4dp" + android:text="@string/add_keys_my_key" /> + + <org.sufficientlysecure.keychain.ui.widget.CertifyKeySpinner + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:id="@+id/cert_key_spinner"> + </org.sufficientlysecure.keychain.ui.widget.CertifyKeySpinner> + + </LinearLayout> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:gravity="right|end"> + + <Button + android:id="@+id/button_view" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/linked_button_view" + android:textColor="@color/link_text_material_light" + style="?android:attr/borderlessButtonStyle" + /> + + <ViewAnimator + android:id="@+id/button_animator" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:inAnimation="@anim/fade_in" + android:outAnimation="@anim/fade_out"> + <Button + android:id="@+id/button_verify" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/linked_button_verify" + android:textColor="@color/link_text_material_light" + style="?android:attr/borderlessButtonStyle" /> + <Button + android:id="@+id/button_retry" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/linked_button_retry" + android:textColor="@color/link_text_material_light" + style="?android:attr/borderlessButtonStyle" /> + <Button + android:id="@+id/button_confirm" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/linked_button_confirm" + android:textColor="@color/link_text_material_light" + style="?android:attr/borderlessButtonStyle" /> + </ViewAnimator> + + </LinearLayout> + + </LinearLayout> + + </android.support.v7.widget.CardView> + +</ScrollView>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/linked_select_fragment.xml b/OpenKeychain/src/main/res/layout/linked_select_fragment.xml new file mode 100644 index 000000000..25b7bd413 --- /dev/null +++ b/OpenKeychain/src/main/res/layout/linked_select_fragment.xml @@ -0,0 +1,188 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingLeft="16dp" + android:paddingRight="16dp" + android:orientation="vertical"> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:layout_marginBottom="8dp" + android:layout_marginLeft="8dp" + android:layout_marginRight="8dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:text="@string/linked_select_1" + android:id="@+id/textView" + android:layout_weight="1" /> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="8dp" + android:layout_marginLeft="8dp" + android:layout_marginRight="8dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:text="@string/linked_select_2" + android:layout_weight="1" /> + + <org.sufficientlysecure.keychain.ui.widget.FixedListView + android:id="@+id/view_key_user_ids" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + <View + android:layout_width="match_parent" + android:layout_height="1dip" + android:layout_marginBottom="4dp" + android:background="?android:attr/listDivider" /> + + <LinearLayout + android:id="@+id/linked_create_https_button" + android:layout_width="match_parent" + android:layout_height="?android:attr/listPreferredItemHeight" + android:clickable="true" + android:background="?android:selectableItemBackground" + android:orientation="horizontal"> + + <!-- separate ImageView required for recoloring --> + <ImageView + android:id="@+id/certify_key_action_certify_image" + android:layout_width="60dip" + android:layout_height="60dip" + android:padding="8dp" + android:src="@drawable/linked_https" + android:layout_gravity="center_vertical" /> + + <TextView + android:paddingLeft="8dp" + android:paddingRight="8dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:layout_width="0dip" + android:layout_height="match_parent" + android:text="@string/linked_title_https" + android:layout_weight="1" + android:gravity="center_vertical" /> + + </LinearLayout> + + <View + android:layout_width="match_parent" + android:layout_height="1dip" + android:layout_marginBottom="4dp" + android:background="?android:attr/listDivider" /> + + <LinearLayout + android:id="@+id/linked_create_dns_button" + android:layout_width="match_parent" + android:layout_height="?android:attr/listPreferredItemHeight" + android:clickable="true" + android:background="?android:selectableItemBackground" + android:orientation="horizontal"> + + <!-- separate ImageView required for recoloring --> + <ImageView + android:layout_width="60dip" + android:layout_height="60dip" + android:padding="8dp" + android:src="@drawable/linked_dns" + android:layout_gravity="center" + /> + + <TextView + android:paddingLeft="8dp" + android:paddingRight="8dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:layout_width="0dip" + android:layout_height="match_parent" + android:text="@string/linked_title_dns" + android:layout_weight="1" + android:gravity="center_vertical" /> + + </LinearLayout> + + <View + android:layout_width="match_parent" + android:layout_height="1dip" + android:layout_marginBottom="4dp" + android:background="?android:attr/listDivider" /> + + <LinearLayout + android:id="@+id/linked_create_twitter_button" + android:layout_width="match_parent" + android:layout_height="?android:attr/listPreferredItemHeight" + android:clickable="true" + android:background="?android:selectableItemBackground" + android:orientation="horizontal"> + + <!-- separate ImageView required for recoloring --> + <ImageView + android:layout_width="60dip" + android:layout_height="60dip" + android:padding="8dp" + android:src="@drawable/linked_twitter" + android:layout_gravity="center" + /> + + <TextView + android:paddingLeft="8dp" + android:paddingRight="8dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:layout_width="0dip" + android:layout_height="match_parent" + android:text="@string/linked_title_twitter" + android:layout_weight="1" + android:gravity="center_vertical" /> + + </LinearLayout> + + <View + android:layout_width="match_parent" + android:layout_height="1dip" + android:layout_marginBottom="4dp" + android:background="?android:attr/listDivider" /> + + <LinearLayout + android:id="@+id/linked_create_github_button" + android:layout_width="match_parent" + android:layout_height="?android:attr/listPreferredItemHeight" + android:clickable="true" + android:background="?android:selectableItemBackground" + android:orientation="horizontal"> + + <!-- separate ImageView required for recoloring --> + <ImageView + android:layout_width="60dip" + android:layout_height="60dip" + android:padding="8dp" + android:src="@drawable/linked_github" + android:layout_gravity="center" + /> + + <TextView + android:paddingLeft="8dp" + android:paddingRight="8dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:layout_width="0dip" + android:layout_height="match_parent" + android:text="@string/linked_title_github" + android:layout_weight="1" + android:gravity="center_vertical" /> + + </LinearLayout> + + <View + android:layout_width="match_parent" + android:layout_height="1dip" + android:layout_marginBottom="4dp" + android:background="?android:attr/listDivider" /> + + </LinearLayout> + +</ScrollView>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/view_key_fragment.xml b/OpenKeychain/src/main/res/layout/view_key_fragment.xml index a71eb5880..294ac59a7 100644 --- a/OpenKeychain/src/main/res/layout/view_key_fragment.xml +++ b/OpenKeychain/src/main/res/layout/view_key_fragment.xml @@ -1,7 +1,8 @@ <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:card_view="http://schemas.android.com/apk/res-auto" - android:layout_width="match_parent" - android:layout_height="match_parent"> + xmlns:card_view="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent"> <LinearLayout android:layout_width="match_parent" @@ -13,7 +14,7 @@ android:paddingRight="16dp"> <android.support.v7.widget.CardView - android:id="@+id/card_view" + android:id="@+id/card_identities" android:layout_gravity="center" android:layout_width="match_parent" android:layout_height="wrap_content" @@ -39,6 +40,62 @@ android:layout_height="wrap_content" android:layout_marginBottom="4dp"/> </LinearLayout> + + </android.support.v7.widget.CardView> + + <android.support.v7.widget.CardView + android:id="@+id/card_linked_ids" + android:transitionName="card_linked_ids" + android:layout_gravity="center" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:visibility="gone" + tools:visibility="visible" + card_view:cardBackgroundColor="@android:color/white" + card_view:cardElevation="2dp" + card_view:cardUseCompatPadding="true" + card_view:cardCornerRadius="4dp"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <TextView + style="@style/CardViewHeader" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/section_linked_identities" /> + + <org.sufficientlysecure.keychain.ui.widget.FixedListView + android:id="@+id/view_key_linked_ids" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="4dp" /> + + <TextView + android:id="@+id/view_key_linked_ids_expander" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginTop="4dp" + android:layout_marginBottom="4dp" + android:gravity="center_vertical" + android:drawableTop="@drawable/divider" + android:drawableRight="@drawable/ic_expand_more_black_24dp" + android:drawableEnd="@drawable/ic_expand_more_black_24dp" + android:drawablePadding="3dp" + android:clickable="true" + android:textColor="@color/tertiary_text_light" + android:text="@string/linked_ids_more_unknown" + android:paddingLeft="8dp" + android:paddingRight="8dp" + android:background="?android:selectableItemBackground" + android:visibility="gone" + tools:visibility="visible" + /> + + </LinearLayout> + </android.support.v7.widget.CardView> <android.support.v7.widget.CardView diff --git a/OpenKeychain/src/main/res/menu/decrypt_menu.xml b/OpenKeychain/src/main/res/menu/decrypt_menu.xml index 9e90fc9c7..0b81ea1db 100644 --- a/OpenKeychain/src/main/res/menu/decrypt_menu.xml +++ b/OpenKeychain/src/main/res/menu/decrypt_menu.xml @@ -14,4 +14,4 @@ android:icon="@drawable/ic_share_black_24dp" app:showAsAction="ifRoom" /> -</menu>
\ No newline at end of file +</menu> diff --git a/OpenKeychain/src/main/res/menu/key_view.xml b/OpenKeychain/src/main/res/menu/key_view.xml index 4a9fe16c7..3d1b02958 100644 --- a/OpenKeychain/src/main/res/menu/key_view.xml +++ b/OpenKeychain/src/main/res/menu/key_view.xml @@ -37,4 +37,9 @@ android:visible="false" android:title="@string/menu_certify_fingerprint" /> + <item + android:id="@+id/menu_key_view_add_linked_identity" + app:showAsAction="never" + android:title="@string/menu_linked_add_identity" /> + </menu>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/transition/linked_id_card_trans.xml b/OpenKeychain/src/main/res/transition/linked_id_card_trans.xml new file mode 100644 index 000000000..8d6e75cee --- /dev/null +++ b/OpenKeychain/src/main/res/transition/linked_id_card_trans.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"> + <changeBounds /> +</transitionSet>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/values/attr.xml b/OpenKeychain/src/main/res/values/attr.xml index 7a2f3054e..74584a1e5 100644 --- a/OpenKeychain/src/main/res/values/attr.xml +++ b/OpenKeychain/src/main/res/values/attr.xml @@ -29,4 +29,8 @@ <attr name="color_strong" format="color" /> </declare-styleable> + <declare-styleable name="PrefixedEditText"> + <attr name="prefix" format="string" /> + </declare-styleable> + </resources>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index 45b1dc26b..3de83f9d5 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -14,6 +14,7 @@ <string name="title_decrypt">"Decrypt"</string> <string name="title_add_subkey">"Add subkey"</string> <string name="title_edit_key">"Edit Key"</string> + <string name="title_linked_create">"Create a Linked Identity"</string> <string name="title_preferences">"Settings"</string> <string name="title_api_registered_apps">"Apps"</string> <string name="title_key_server_preference">"Keyservers"</string> @@ -54,8 +55,8 @@ <string name="section_share_key">"Key"</string> <string name="section_key_server">"Keyserver"</string> <string name="section_fingerprint">"Fingerprint"</string> - <string name="section_encrypt">"Encrypt"</string> - <string name="section_decrypt">"Decrypt"</string> + <string name="section_encrypt">"Encrypt and/or sign"</string> + <string name="section_decrypt">"Decrypt and verify signatures"</string> <string name="section_current_expiry">"Current expiry"</string> <string name="section_new_expiry">"New expiry"</string> @@ -73,15 +74,15 @@ <string name="btn_back">"Back"</string> <string name="btn_no">"No"</string> <string name="btn_match">"Fingerprints match"</string> - <string name="btn_share_encrypted_signed">"Encrypt and share text"</string> - <string name="btn_copy_encrypted_signed">"Encrypt and copy text"</string> + <string name="btn_share_encrypted_signed">"Encrypt/sign and share text"</string> + <string name="btn_copy_encrypted_signed">"Encrypt/sign and copy text"</string> <string name="btn_view_cert_key">"View certification key"</string> <string name="btn_create_key">"Create key"</string> <string name="btn_add_files">"Add file(s)"</string> <string name="btn_share_decrypted_text">"Share decrypted text"</string> <string name="btn_copy_decrypted_text">"Copy decrypted text"</string> <string name="btn_decrypt_clipboard">"Decrypt text from clipboard"</string> - <string name="btn_decrypt_and_verify">"and verify signatures"</string> + <string name="btn_decrypt_and_verify">""</string> <string name="btn_decrypt_files">"Decrypt files"</string> <string name="btn_encrypt_files">"Encrypt files"</string> <string name="btn_encrypt_text">"Encrypt text"</string> @@ -192,6 +193,7 @@ <string name="choice_4hours">"4 hours"</string> <string name="choice_8hours">"8 hours"</string> <string name="choice_forever">"forever"</string> + <string name="choice_select_cert">"Select a Key"</string> <string name="dsa">"DSA"</string> <string name="elgamal">"ElGamal"</string> <string name="rsa">"RSA"</string> @@ -422,11 +424,11 @@ <!-- Import result toast --> <plurals name="import_keys_added_and_updated_1"> - <item quantity="one">"Successfully imported key"</item> + <item quantity="one">"Successfully imported one key"</item> <item quantity="other">"Successfully imported %1$d keys"</item> </plurals> <plurals name="import_keys_added_and_updated_2"> - <item quantity="one">"and updated key%2$s."</item> + <item quantity="one">"and updated one key%2$s."</item> <item quantity="other">"and updated %1$d keys%2$s."</item> </plurals> <plurals name="import_keys_added"> @@ -1178,6 +1180,28 @@ <item quantity="other">"Failed to delete %d keys"</item> </plurals> + <!-- Linked Identity verification --> + <string name="msg_lv">"Verifying linked identity…"</string> + <string name="msg_lv_match">"Searching for token"</string> + <string name="msg_lv_match_error">"No token found in resource!"</string> + <string name="msg_lv_fp_ok">"Fingerprint OK."</string> + <string name="msg_lv_fp_error">"Fingerprint mismatch!"</string> + + <string name="msg_lv_error_twitter_auth">"Error obtaining Twitter auth token!"</string> + <string name="msg_lv_error_twitter_handle">"Twitter account handle mismatch in response!"</string> + <string name="msg_lv_error_twitter_response">"Unexpected response from Twitter API!"</string> + <string name="msg_lv_error_github_handle">"Github account handle mismatch in response!"</string> + <string name="msg_lv_error_github_not_found">"Gist contains no matching files!"</string> + + <string name="msg_lv_fetch">"Fetching URI '%s'"</string> + <string name="msg_lv_fetch_redir">"Following redirect to '%s'"</string> + <string name="msg_lv_fetch_ok">"Successfully fetched (HTTP %s)"</string> + <string name="msg_lv_fetch_error">"Server error (HTTP %s)"</string> + <string name="msg_lv_fetch_error_url">"URL is malformed!"</string> + <string name="msg_lv_fetch_error_io">"IO Error!"</string> + <string name="msg_lv_fetch_error_format">"Format error!"</string> + <string name="msg_lv_fetch_error_nothing">"Resource not found!"</string> + <string name="msg_acc_saved">"Account saved"</string> <string name="msg_download_success">"Downloaded successfully!"</string> @@ -1291,4 +1315,85 @@ <string name="error_pin_nodefault">Default PIN was rejected!</string> <string name="error_bluetooth_file">Error creating temporary file. Bluetooth sharing will fail.</string> + <string name="linked_create_https_1_1">"By creating a Linked Identity of this type, you can link your key to a website you control."</string> + <string name="linked_create_https_1_2">"To do this, you publish a text file on this website, then create a Linked Identity which links to it."</string> + <string name="linked_create_https_1_3">"Please enter a URL where you are able to place a text file for proof. Note that your server must support https and have a valid TLS certificate!"</string> + <string name="linked_create_https_1_4">"Example: https://example.com/pgpkey.txt"</string> + <string name="linked_create_https_created">"The proof file has been created. For the next step, you should save and upload it to the URI you indicated:"</string> + <string name="linked_create_https_2_1">"A proof file for this URI has been created:"</string> + <string name="linked_create_https_2_2">"For the next step, you should save and upload this file."</string> + <string name="linked_create_https_2_3">"Make sure the file is reachable at the correct URI, then verify your setup."</string> + <string name="linked_create_https_2_4">"After successful verification, press the Finish button to add the Linked Identity to your keyring and finish the process."</string> + + <string name="linked_create_twitter_1_1">"By creating a Linked Identity of this type, you can link your key to a Twitter account you control."</string> + <string name="linked_create_twitter_1_2">"To do this, you publish a specific Tweet on your timeline, then create a Linked Identity which links to this Tweet."</string> + <string name="linked_create_twitter_1_3">"Please enter your Twitter screen name to proceed."</string> + <string name="linked_create_twitter_handle">Twitter Handle</string> + <string name="linked_create_twitter_2_1">"Click either button to tweet the message!"</string> + <string name="linked_create_twitter_2_2">"You can edit the Tweet before posting it, so long as the text inside the brackets is unmodified."</string> + <string name="linked_create_twitter_2_3">"Once your Tweet is published as <b>@%s</b>, click the Verify button to scan your timeline for it."</string> + <string name="linked_create_twitter_2_4">"After successful verification, press the Finish button to add the Linked Identity to your keyring and finish the process."</string> + + <string name="linked_create_github_1_1">"By creating a Linked Identity of this type, you can link your key to a Github account you control."</string> + <string name="linked_create_github_1_2">"To do this, you publish a specific Gist on your timeline, then create a Linked Identity which links to this Gist."</string> + <string name="linked_create_github_1_3">"Please enter your Twitter screen name to proceed."</string> + <string name="linked_create_github_handle">Github Handle</string> + <string name="linked_create_github_2_1">"Click either button to post the gist!"</string> + <string name="linked_create_github_2_2">"You can edit the Gist before posting it, so long as the text inside the brackets is unmodified."</string> + <string name="linked_create_github_2_3">"Once your Gist is published, click the Verify button to scan your timeline for it."</string> + <string name="linked_create_github_2_4">"After successful verification, press the Finish button to add the Linked Identity to your keyring and finish the process."</string> + + <string name="linked_create_dns_1_1">"By creating a Linked Identity of this type, you can link your key to a domain name you control."</string> + <string name="linked_create_dns_1_2">"To do this, you create a specific TXT record for the domain, then create a Linked Identity which links to this record."</string> + <!-- An Identity of this type is especially appropriate if your email address is at the same domain. --> + <string name="linked_create_dns_1_3">"Please enter a fully qualified domain name to proceed."</string> + <string name="linked_create_dns_1_4">"Example: subdomain.example.com"</string> + <string name="linked_create_dns_2_1">"Your proof text:"</string> + <string name="linked_create_dns_2_2">"For the next step, bla bla"</string> + <string name="linked_create_dns_2_3">"More bla:"</string> + <string name="linked_create_dns_2_4">"After successful verification, press the Finish button to add the Linked Identity to your keyring and finish the process."</string> + + <string name="linked_create_verify">"Verify"</string> + <string name="linked_text_clipboard">Text has been copied to clipboard</string> + <string name="linked_verified_https">"The link between this Website and key was securely verified. <b>If you believe the Website is genuine</b>, confirm this verification with your key."</string> + <string name="linked_verified_github">"The link between this Github account and key was securely verified. <b>If you believe the account is genuine</b>, confirm this verification with your key."</string> + <string name="linked_verified_dns">"The link between this Domain Name and key was securely verified. <b>If you believe the Domain is genuine</b>, confirm this verification with your key."</string> + <string name="linked_verified_twitter">"The link between this Twitter account and key was securely verified. <b>If you believe the account is genuine</b>, confirm this verification with your key."</string> + <string name="linked_verified_secret_https">"The link between your Website and key was securely verified. Everything looks in order."</string> + <string name="linked_verified_secret_github">"The link between your Github account and key was securely verified. Everything looks in order."</string> + <string name="linked_verified_secret_dns">"The link between your Domain Name and key was securely verified. Everything looks in order."</string> + <string name="linked_verified_secret_twitter">"The link between this Twitter account and key was securely verified. Everything looks in order."</string> + + <plurals name="linked_id_expand"> + <item quantity="one">"There is one more unknown identity type"</item> + <item quantity="other">"There are %d more unknown identity types"</item> + </plurals> + + <string name="linked_select_1">"A \'linked identity\' connects your pgp key to a resource on the web."</string> + <string name="linked_select_2">"Please select a type:"</string> + <string name="linked_id_generic_text">"This file claims ownership of the OpenPGP key with long id %2$s.\n\nToken for proof:\n%1$s"</string> + <string name="linked_id_github_text">"This Gist confirms the Linked Identity in my OpenPGP key, and links it to this Github account.\n\nToken for proof:\n%1$s"</string> + <string name="linked_verifying">"Verifying…"</string> + <string name="linked_verify_success">"Verified!"</string> + <string name="linked_verify_error">"Verification error!"</string> + <string name="linked_verify_pending">"Not yet verified"</string> + <string name="linked_need_verify">The resource needs to be verified before you can proceed!</string> + <string name="menu_linked_add_identity">"Add Linked Identity"</string> + <string name="section_linked_identities">"Linked Identities"</string> + <string name="btn_finish">"Finish"</string> + <string name="linked_title_https">Website (HTTPS)</string> + <string name="linked_title_dns">Domain Name (DNS)</string> + <string name="linked_title_github">Github</string> + <string name="linked_title_twitter">Twitter</string> + <string name="card_linked_identity">Linked Identity</string> + <string name="linked_button_verify">Verify</string> + <string name="linked_button_retry">Retry</string> + <string name="linked_button_confirm">Confirm</string> + <string name="linked_button_view">View</string> + <string name="linked_text_verifying">Verifying…</string> + <string name="linked_text_error">Error</string> + <string name="linked_text_confirming">Confirming…</string> + <string name="linked_ids_more_unknown">%d more unknown identity types</string> + <string name="title_linked_id_create">Create Linked Identity</string> + </resources> @@ -1,3 +1,9 @@ +# Branch Description + +This is Valodim's topic branch for linked identities in OpenKeychain. +Everything here is very much work-in-progress and subject to changes. +Specification will follow. + # OpenKeychain (for Android) OpenKeychain is an OpenPGP implementation for Android. @@ -206,3 +212,6 @@ See https://github.com/open-keychain/open-keychain/blob/development/OpenKeychain * Purple color scheme http://android-holo-colors.com/ + +* DNS icon + http://www.fatcow.com/free-icons |