Merge branch 'linked-identities' (and fix OperationHelper ids)
Merge Linked Identities. Also includes an important fix for OperationHelper ids, which had an error in the bit mask logic. Conflicts: Graphics/update-drawables.sh OpenKeychain/build.gradle OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyImportFragment.java OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java OpenKeychain/src/main/res/anim/fade_in.xml OpenKeychain/src/main/res/anim/fade_out.xml OpenKeychain/src/main/res/layout/decrypt_text_fragment.xml OpenKeychain/src/main/res/layout/encrypt_decrypt_overview_fragment.xml OpenKeychain/src/main/res/layout/view_key_fragment.xml OpenKeychain/src/main/res/menu/key_view.xml OpenKeychain/src/main/res/values/strings.xml OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/CertifyOperationTest.java README.md
4
Graphics/drawables/linked_dns.svg
Normal file
|
@ -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>
|
After Width: | Height: | Size: 401 B |
4
Graphics/drawables/linked_github.svg
Normal file
|
@ -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>
|
After Width: | Height: | Size: 852 B |
4
Graphics/drawables/linked_https.svg
Normal file
|
@ -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>
|
After Width: | Height: | Size: 1 KiB |
4
Graphics/drawables/linked_twitter.svg
Normal file
|
@ -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>
|
After Width: | Height: | Size: 763 B |
77
Graphics/drawables/status_signature_verified_inner.svg
Normal file
|
@ -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>
|
After Width: | Height: | Size: 2.3 KiB |
|
@ -22,7 +22,7 @@ SRC_DIR=./drawables/
|
|||
#inkscape -w 512 -h 512 -e "$PLAY_DIR/$NAME.png" $NAME.svg
|
||||
|
||||
|
||||
for NAME in "ic_cloud_search" "ic_action_encrypt_file" "ic_action_encrypt_text" "ic_action_verified_cutout" "ic_action_encrypt_copy" "ic_action_encrypt_save" "ic_action_encrypt_share" "status_lock_closed" "status_lock_error" "status_lock_open" "status_signature_expired_cutout" "status_signature_invalid_cutout" "status_signature_revoked_cutout" "status_signature_unknown_cutout" "status_signature_unverified_cutout" "status_signature_verified_cutout" "key_flag_authenticate" "key_flag_certify" "key_flag_encrypt" "key_flag_sign" "yubi_icon" "ic_stat_notify"
|
||||
for NAME in "ic_cloud_search" "ic_action_encrypt_file" "ic_action_encrypt_text" "ic_action_verified_cutout" "ic_action_encrypt_copy" "ic_action_encrypt_save" "ic_action_encrypt_share" "status_lock_closed" "status_lock_error" "status_lock_open" "status_signature_expired_cutout" "status_signature_invalid_cutout" "status_signature_revoked_cutout" "status_signature_unknown_cutout" "status_signature_unverified_cutout" "status_signature_verified_cutout" "key_flag_authenticate" "key_flag_certify" "key_flag_encrypt" "key_flag_sign" "yubi_icon" "ic_stat_notify" "status_signature_verified_inner"
|
||||
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"
|
||||
|
|
|
@ -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)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -46,7 +46,8 @@ 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 'com.splitwise:tokenautocomplete:1.3.3@aar'
|
||||
compile 'org.ocpsoft.prettytime:prettytime:3.2.7.Final'
|
||||
compile "com.splitwise:tokenautocomplete:1.3.3@aar"
|
||||
compile 'se.emilsjolander:stickylistheaders:2.6.0'
|
||||
compile 'org.sufficientlysecure:html-textview:1.2'
|
||||
compile 'com.mikepenz:materialdrawer:3.0.9@aar'
|
||||
|
@ -56,6 +57,7 @@ dependencies {
|
|||
compile 'com.mikepenz.iconics:community-material-typeface:1.0.0@aar'
|
||||
compile 'com.nispok:snackbar:2.11.0'
|
||||
compile 'com.squareup.okhttp:okhttp:2.4.0'
|
||||
compile 'org.thoughtcrime.ssl.pinning:AndroidPinning:1.0.0'
|
||||
|
||||
// libs as submodules
|
||||
compile project(':extern:openpgp-api-lib:openpgp-api')
|
||||
|
@ -206,6 +208,7 @@ android {
|
|||
// Disable preDexing, causes com.android.dx.cf.iface.ParseException: bad class file magic (cafebabe) or version (0034.0000) on some systems
|
||||
dexOptions {
|
||||
preDexLibraries = false
|
||||
javaMaxHeapSize "2g"
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
|
|
|
@ -110,6 +110,10 @@
|
|||
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
||||
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" />
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
package org.sufficientlysecure.keychain.linked;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.DrawableRes;
|
||||
|
||||
public class LinkedAttribute extends UriAttribute {
|
||||
|
||||
public final LinkedResource mResource;
|
||||
|
||||
protected LinkedAttribute(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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,300 @@
|
|||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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 LinkedAttribute(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 Identity";
|
||||
}
|
||||
|
||||
public String getDisplayComment(Context context) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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];
|
||||
}
|
||||
};
|
||||
|
||||
}
|
|
@ -782,12 +782,33 @@ public abstract class OperationResult implements Parcelable {
|
|||
R.string.msg_keybase_error_msg_payload_mismatch),
|
||||
|
||||
// export log
|
||||
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;
|
||||
|
|
|
@ -368,4 +368,5 @@ public class UncachedPublicKey {
|
|||
|
||||
return calendar.getTime();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -113,6 +113,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";
|
||||
|
||||
|
@ -279,6 +280,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 {
|
||||
|
@ -367,7 +373,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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
@ -63,6 +64,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;
|
||||
|
@ -131,6 +134,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
|
||||
|
@ -146,6 +152,13 @@ public class KeychainProvider extends ContentProvider {
|
|||
matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/"
|
||||
+ 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);
|
||||
|
@ -492,7 +505,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);
|
||||
|
@ -517,13 +531,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));
|
||||
|
@ -574,7 +590,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);
|
||||
|
@ -595,10 +612,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
|
||||
|
@ -618,6 +631,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;
|
||||
}
|
||||
|
||||
|
|
|
@ -32,10 +32,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.ImportOperation;
|
||||
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;
|
||||
|
@ -50,7 +55,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;
|
||||
|
@ -62,12 +66,9 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
|
|||
import org.sufficientlysecure.keychain.provider.KeychainContract.UpdatedKeys;
|
||||
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;
|
||||
|
|
|
@ -23,6 +23,9 @@ import android.os.Parcelable;
|
|||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute;
|
||||
import org.sufficientlysecure.keychain.util.ParcelableProxy;
|
||||
|
@ -86,17 +89,10 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -83,7 +83,9 @@ public class CertifyKeyFragment
|
|||
};
|
||||
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;
|
||||
|
|
|
@ -280,7 +280,7 @@ public class EditKeyFragment extends QueueingCryptoOperationFragment<SaveKeyring
|
|||
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: {
|
||||
|
|
|
@ -63,7 +63,6 @@ import org.sufficientlysecure.keychain.provider.KeychainDatabase;
|
|||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.service.ConsolidateInputParcel;
|
||||
import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.service.KeyserverSyncAdapterService;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
|
||||
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
|
||||
|
|
|
@ -71,6 +71,7 @@ import org.sufficientlysecure.keychain.util.Preferences;
|
|||
* internally and is NOT meant to be used by signing operations before adding a signature time
|
||||
*/
|
||||
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)");
|
||||
}
|
||||
|
|
|
@ -68,6 +68,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.ImportKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.ui.linked.LinkedIdWizard;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity;
|
||||
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
|
||||
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
|
||||
|
@ -357,6 +358,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;
|
||||
|
@ -377,8 +384,13 @@ 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 exportKey = menu.findItem(R.id.menu_key_view_export_file);
|
||||
exportKey.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);
|
||||
MenuItem certifyFingerprintWord = menu.findItem(R.id.menu_key_view_certify_fingerprint_word);
|
||||
|
@ -459,13 +471,13 @@ public class ViewKeyActivity extends BaseNfcActivity implements
|
|||
return;
|
||||
}
|
||||
|
||||
if (resultCode != Activity.RESULT_OK) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (requestCode) {
|
||||
case REQUEST_QR_FINGERPRINT: {
|
||||
|
||||
if (resultCode != Activity.RESULT_OK) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If there is an EXTRA_RESULT, that's an error. Just show it.
|
||||
if (data.hasExtra(OperationResult.EXTRA_RESULT)) {
|
||||
OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT);
|
||||
|
@ -487,11 +499,19 @@ public class ViewKeyActivity extends BaseNfcActivity implements
|
|||
}
|
||||
|
||||
case REQUEST_BACKUP: {
|
||||
if (resultCode != Activity.RESULT_OK) {
|
||||
return;
|
||||
}
|
||||
|
||||
backupToFile();
|
||||
return;
|
||||
}
|
||||
|
||||
case REQUEST_CERTIFY: {
|
||||
if (resultCode != Activity.RESULT_OK) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.hasExtra(OperationResult.EXTRA_RESULT)) {
|
||||
OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT);
|
||||
result.createNotify(this).show();
|
||||
|
@ -500,6 +520,10 @@ public class ViewKeyActivity extends BaseNfcActivity implements
|
|||
}
|
||||
|
||||
case REQUEST_DELETE: {
|
||||
if (resultCode != Activity.RESULT_OK) {
|
||||
return;
|
||||
}
|
||||
|
||||
setResult(RESULT_OK, data);
|
||||
finish();
|
||||
return;
|
||||
|
@ -552,7 +576,6 @@ public class ViewKeyActivity extends BaseNfcActivity implements
|
|||
finish();
|
||||
}
|
||||
}, R.string.snack_yubikey_view).show();
|
||||
|
||||
// and if it's not found, offer import
|
||||
} catch (PgpKeyNotFoundException e) {
|
||||
Notify.create(this, R.string.snack_yubi_other, Notify.LENGTH_LONG,
|
||||
|
@ -986,4 +1009,6 @@ public class ViewKeyActivity extends BaseNfcActivity implements
|
|||
public boolean onCryptoSetProgress(String msg, int progress, int max) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ import java.util.ArrayList;
|
|||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Date;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
|
@ -193,9 +194,9 @@ public class KeyAdapter extends CursorAdapter {
|
|||
String dateTime = DateUtils.formatDateTime(context,
|
||||
item.mCreation.getTime(),
|
||||
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));
|
||||
mCreationDate.setTextColor(textColor);
|
||||
|
|
|
@ -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.LinkedAttribute;
|
||||
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 LinkedAttribute;
|
||||
}
|
||||
};
|
||||
|
||||
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 = LinkedAttribute.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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -171,7 +171,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);
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -18,9 +18,9 @@
|
|||
|
||||
package org.sufficientlysecure.keychain.ui.base;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.Fragment;
|
||||
|
@ -50,17 +50,21 @@ import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
|||
* @see KeychainService
|
||||
*
|
||||
*/
|
||||
abstract class CryptoOperationFragment<T extends Parcelable, S extends OperationResult>
|
||||
public abstract class CryptoOperationFragment<T extends Parcelable, S extends OperationResult>
|
||||
extends Fragment implements CryptoOperationHelper.Callback<T, S> {
|
||||
|
||||
final private CryptoOperationHelper<T, S> mOperationHelper;
|
||||
|
||||
public CryptoOperationFragment() {
|
||||
mOperationHelper = new CryptoOperationHelper<>(1, this, this, R.string.progress_processing);
|
||||
}
|
||||
|
||||
public CryptoOperationFragment(Integer initialProgressMsg) {
|
||||
mOperationHelper = new CryptoOperationHelper<>(1, this, this, initialProgressMsg);
|
||||
}
|
||||
|
||||
public CryptoOperationFragment() {
|
||||
mOperationHelper = new CryptoOperationHelper<>(1, this, this, R.string.progress_processing);
|
||||
public CryptoOperationFragment(int id, Integer initialProgressMsg) {
|
||||
mOperationHelper = new CryptoOperationHelper<>(id, this, this, initialProgressMsg);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -70,9 +70,11 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
|
|||
// particular helper. a request code looks as follows:
|
||||
// (id << 9) + (1<<8) + REQUEST_CODE_X
|
||||
// that is, starting from LSB, there are 8 bits request code, 1
|
||||
// fixed bit set, then 7 bit operator-id code. the first two
|
||||
// summands are stored in the mId for easy operation.
|
||||
private final int mId;
|
||||
// fixed bit set, then 7 bit helper-id code. the first two
|
||||
// summands are stored in the mHelperId for easy operation.
|
||||
private final int mHelperId;
|
||||
// bitmask for helperId is everything except the least 8 bits
|
||||
public static final int HELPER_ID_BITMASK = ~0xff;
|
||||
|
||||
public static final int REQUEST_CODE_PASSPHRASE = 1;
|
||||
public static final int REQUEST_CODE_NFC = 2;
|
||||
|
@ -92,7 +94,7 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
|
|||
*/
|
||||
public CryptoOperationHelper(int id, FragmentActivity activity, Callback<T, S> callback,
|
||||
Integer progressMessageString) {
|
||||
mId = (id << 9) + (1<<8);
|
||||
mHelperId = (id << 9) + (1<<8);
|
||||
mActivity = activity;
|
||||
mUseFragment = false;
|
||||
mCallback = callback;
|
||||
|
@ -103,7 +105,7 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
|
|||
* if OperationHelper is being integrated into a fragment
|
||||
*/
|
||||
public CryptoOperationHelper(int id, Fragment fragment, Callback<T, S> callback, Integer progressMessageString) {
|
||||
mId = (id << 9) + (1<<8);
|
||||
mHelperId = (id << 9) + (1<<8);
|
||||
mFragment = fragment;
|
||||
mUseFragment = true;
|
||||
mProgressMessageResource = progressMessageString;
|
||||
|
@ -162,9 +164,9 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
|
|||
|
||||
protected void startActivityForResult(Intent intent, int requestCode) {
|
||||
if (mUseFragment) {
|
||||
mFragment.startActivityForResult(intent, mId + requestCode);
|
||||
mFragment.startActivityForResult(intent, mHelperId + requestCode);
|
||||
} else {
|
||||
mActivity.startActivityForResult(intent, mId + requestCode);
|
||||
mActivity.startActivityForResult(intent, mHelperId + requestCode);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -176,13 +178,13 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
|
|||
public boolean handleActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
Log.d(Constants.TAG, "received activity result in OperationHelper");
|
||||
|
||||
if ((requestCode & mId) != mId) {
|
||||
if ((requestCode & HELPER_ID_BITMASK) != mHelperId) {
|
||||
// this wasn't meant for us to handle
|
||||
return false;
|
||||
}
|
||||
Log.d(Constants.TAG, "handling activity result in OperationHelper");
|
||||
// filter out mId from requestCode
|
||||
requestCode ^= mId;
|
||||
// filter out mHelperId from requestCode
|
||||
requestCode ^= mHelperId;
|
||||
|
||||
if (resultCode == Activity.RESULT_CANCELED) {
|
||||
mCallback.onCryptoOperationCancelled();
|
||||
|
|
|
@ -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.ic_stat_retyped_ok, 0);
|
||||
} else {
|
||||
mEditDns.setCompoundDrawablesWithIntrinsicBounds(0, 0,
|
||||
R.drawable.ic_stat_retyped_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();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,172 @@
|
|||
/*
|
||||
* 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 java.io.FileNotFoundException;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
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.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.linked.LinkedTokenResource;
|
||||
import org.sufficientlysecure.keychain.linked.resources.DnsResource;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||
|
||||
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() {
|
||||
Activity activity = getActivity();
|
||||
if (activity == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ClipboardManager clipMan = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
if (clipMan == null) {
|
||||
Notify.create(activity, R.string.error_clipboard_copy, Style.ERROR).show();
|
||||
return;
|
||||
}
|
||||
|
||||
ClipData clip = ClipData.newPlainText(Constants.CLIPBOARD_LABEL, mResourceString);
|
||||
clipMan.setPrimaryClip(clip);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,221 @@
|
|||
package org.sufficientlysecure.keychain.ui.linked;
|
||||
|
||||
|
||||
import android.graphics.PorterDuff;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.support.annotation.Nullable;
|
||||
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.linked.LinkedAttribute;
|
||||
import org.sufficientlysecure.keychain.linked.LinkedTokenResource;
|
||||
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.service.SaveKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment;
|
||||
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();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cryptoOperation() {
|
||||
if (mVerifiedResource == null) {
|
||||
Notify.create(getActivity(), R.string.linked_need_verify, Notify.Style.ERROR)
|
||||
.show(LinkedIdCreateFinalFragment.this);
|
||||
return;
|
||||
}
|
||||
|
||||
super.cryptoOperation();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cryptoOperation(CryptoInputParcel cryptoInput) {
|
||||
if (mVerifiedResource == null) {
|
||||
Notify.create(getActivity(), R.string.linked_need_verify, Notify.Style.ERROR)
|
||||
.show(LinkedIdCreateFinalFragment.this);
|
||||
return;
|
||||
}
|
||||
|
||||
super.cryptoOperation(cryptoInput);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Parcelable createOperationInput() {
|
||||
SaveKeyringParcel skp =
|
||||
new SaveKeyringParcel(mLinkedIdWizard.mMasterKeyId, mLinkedIdWizard.mFingerprint);
|
||||
|
||||
WrappedUserAttribute ua =
|
||||
LinkedAttribute.fromResource(mVerifiedResource).toUserAttribute();
|
||||
|
||||
skp.mAddUserAttribute.add(ua);
|
||||
|
||||
return skp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCryptoOperationSuccess(OperationResult result) {
|
||||
// if bad -> display here!
|
||||
if (!result.success()) {
|
||||
result.createNotify(getActivity()).show(LinkedIdCreateFinalFragment.this);
|
||||
return;
|
||||
}
|
||||
|
||||
getActivity().finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCryptoOperationError(OperationResult result) {
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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.ic_stat_retyped_ok, 0);
|
||||
} else {
|
||||
mEditUri.setCompoundDrawablesWithIntrinsicBounds(0, 0,
|
||||
R.drawable.ic_stat_retyped_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();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,172 @@
|
|||
/*
|
||||
* 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.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";
|
||||
|
||||
FileHelper.saveDocument(this,
|
||||
targetName, Uri.fromFile(new File(Constants.Path.APP_DIR, targetName)),
|
||||
"text/plain", R.string.title_decrypt_to_file, R.string.specify_file_to_decrypt_to,
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,558 @@
|
|||
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.Parcelable;
|
||||
import android.support.annotation.Nullable;
|
||||
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.LinkedVerifyResult;
|
||||
import org.sufficientlysecure.keychain.linked.LinkedTokenResource;
|
||||
import org.sufficientlysecure.keychain.linked.LinkedAttribute;
|
||||
import org.sufficientlysecure.keychain.linked.LinkedResource;
|
||||
import org.sufficientlysecure.keychain.linked.UriAttribute;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
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.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;
|
||||
}
|
||||
|
||||
public LinkedIdViewFragment() {
|
||||
// IMPORTANT: the id must be unique in the ViewKeyActivity CryptoOperationHelper id namespace!
|
||||
// no initial progress message -> we handle progress ourselves!
|
||||
super(5, null);
|
||||
}
|
||||
|
||||
@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 = LinkedAttribute.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 LinkedAttribute) {
|
||||
LinkedResource res = ((LinkedAttribute) 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.getSelectedKeyId();
|
||||
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;
|
||||
}
|
||||
|
||||
mViewHolder.setVerifyingState(mContext, VerifyState.CERTIFYING, false);
|
||||
cryptoOperation();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCryptoOperationCancelled() {
|
||||
super.onCryptoOperationCancelled();
|
||||
|
||||
// go back to 'verified ok'
|
||||
setShowVerifying(false);
|
||||
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Parcelable createOperationInput() {
|
||||
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));
|
||||
|
||||
return parcel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCryptoOperationSuccess(OperationResult result) {
|
||||
result.createNotify(getActivity()).show();
|
||||
// no need to do anything else, we will get a loader refresh!
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCryptoOperationError(OperationResult result) {
|
||||
result.createNotify(getActivity()).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCryptoSetProgress(String msg, int progress, int max) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -269,6 +269,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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -17,25 +17,24 @@
|
|||
|
||||
package org.sufficientlysecure.keychain.ui.widget;
|
||||
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
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;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter;
|
||||
|
||||
public class CertifyKeySpinner extends KeySpinner {
|
||||
private long mHiddenMasterKeyId = Constants.key.none;
|
||||
private boolean mIsSingle;
|
||||
|
||||
public CertifyKeySpinner(Context context) {
|
||||
super(context);
|
||||
|
@ -94,9 +93,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;
|
||||
}
|
||||
}
|
||||
|
@ -106,6 +107,9 @@ public class CertifyKeySpinner extends KeySpinner {
|
|||
}
|
||||
}
|
||||
|
||||
public boolean isSingleEntry() {
|
||||
return mIsSingle && getSelectedItemPosition() != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isItemEnabled(Cursor cursor) {
|
||||
|
@ -128,4 +132,10 @@ public class CertifyKeySpinner extends KeySpinner {
|
|||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @StringRes int getNoneString() {
|
||||
return R.string.choice_select_cert;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import android.database.Cursor;
|
|||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.Loader;
|
||||
|
@ -34,6 +35,7 @@ import android.view.ViewGroup;
|
|||
import android.widget.AdapterView;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.SpinnerAdapter;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
|
@ -226,9 +228,11 @@ public abstract class KeySpinner extends AppCompatSpinner implements
|
|||
return inner.getView(position -1, convertView, parent);
|
||||
}
|
||||
|
||||
return convertView != null ? convertView :
|
||||
View view = convertView != null ? convertView :
|
||||
LayoutInflater.from(getContext()).inflate(
|
||||
R.layout.keyspinner_item_none, parent, false);
|
||||
((TextView) view.findViewById(R.id.keyspinner_key_name)).setText(getNoneString());
|
||||
return view;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -259,4 +263,9 @@ public abstract class KeySpinner extends AppCompatSpinner implements
|
|||
bundle.putLong(ARG_KEY_ID, getSelectedKeyId());
|
||||
return bundle;
|
||||
}
|
||||
|
||||
public @StringRes int getNoneString() {
|
||||
return R.string.cert_none;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
11
OpenKeychain/src/main/res/anim/fade_in_down.xml
Normal file
|
@ -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>
|
7
OpenKeychain/src/main/res/anim/fade_in_quick.xml
Normal file
|
@ -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>
|
11
OpenKeychain/src/main/res/anim/fade_in_up.xml
Normal file
|
@ -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>
|
11
OpenKeychain/src/main/res/anim/fade_out_down.xml
Normal file
|
@ -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>
|
7
OpenKeychain/src/main/res/anim/fade_out_quick.xml
Normal file
|
@ -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>
|
11
OpenKeychain/src/main/res/anim/fade_out_up.xml
Normal file
|
@ -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>
|
BIN
OpenKeychain/src/main/res/drawable-hdpi/linked_dns.png
Normal file
After Width: | Height: | Size: 667 B |
BIN
OpenKeychain/src/main/res/drawable-hdpi/linked_github.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
OpenKeychain/src/main/res/drawable-hdpi/linked_https.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
OpenKeychain/src/main/res/drawable-hdpi/linked_twitter.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 544 B |
After Width: | Height: | Size: 2 KiB |
BIN
OpenKeychain/src/main/res/drawable-mdpi/linked_dns.png
Normal file
After Width: | Height: | Size: 371 B |
BIN
OpenKeychain/src/main/res/drawable-mdpi/linked_github.png
Normal file
After Width: | Height: | Size: 999 B |
BIN
OpenKeychain/src/main/res/drawable-mdpi/linked_https.png
Normal file
After Width: | Height: | Size: 1,010 B |
BIN
OpenKeychain/src/main/res/drawable-mdpi/linked_twitter.png
Normal file
After Width: | Height: | Size: 880 B |
After Width: | Height: | Size: 429 B |
After Width: | Height: | Size: 1.4 KiB |
BIN
OpenKeychain/src/main/res/drawable-xhdpi/linked_dns.png
Normal file
After Width: | Height: | Size: 736 B |
BIN
OpenKeychain/src/main/res/drawable-xhdpi/linked_github.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
OpenKeychain/src/main/res/drawable-xhdpi/linked_https.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
OpenKeychain/src/main/res/drawable-xhdpi/linked_twitter.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 717 B |
After Width: | Height: | Size: 3 KiB |
BIN
OpenKeychain/src/main/res/drawable-xxhdpi/linked_dns.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
OpenKeychain/src/main/res/drawable-xxhdpi/linked_github.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
OpenKeychain/src/main/res/drawable-xxhdpi/linked_https.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
OpenKeychain/src/main/res/drawable-xxhdpi/linked_twitter.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 1.4 KiB |
11
OpenKeychain/src/main/res/drawable/divider.xml
Normal file
|
@ -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="#eeeeee" />
|
||||
|
||||
</shape>
|
44
OpenKeychain/src/main/res/layout/cert_list_widget.xml
Normal file
|
@ -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>
|
|
@ -8,11 +8,11 @@
|
|||
|
||||
<LinearLayout
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible"
|
||||
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"
|
||||
|
@ -39,12 +39,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"
|
||||
|
@ -65,4 +65,4 @@
|
|||
android:layout_gravity="center_horizontal" />
|
||||
</LinearLayout>
|
||||
|
||||
</org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator>
|
||||
</org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator>
|
||||
|
|
|
@ -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="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
|
@ -13,7 +14,8 @@
|
|||
android:id="@+id/keyspinner_key_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/choice_none"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium" />
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
tools:text="@string/choice_none"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|