aboutsummaryrefslogtreecommitdiffstats
path: root/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui
diff options
context:
space:
mode:
Diffstat (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui')
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java2
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java3
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java336
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvUserIdsFragment.java2
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java166
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/LinkedIdsAdapter.java179
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserAttributesAdapter.java13
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java2
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateDnsStep1Fragment.java133
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateDnsStep2Fragment.java167
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateFinalFragment.java238
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateHttpsStep1Fragment.java132
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateHttpsStep2Fragment.java174
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep1Fragment.java134
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep2Fragment.java152
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep3Fragment.java160
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdSelectFragment.java91
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdViewFragment.java347
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdWizard.java106
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/HttpsPrefixedText.java38
20 files changed, 2334 insertions, 241 deletions
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java
index 25ca6e8fd..005b60ce0 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java
@@ -328,7 +328,7 @@ public class EditKeyFragment 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);
}
case LOADER_ID_SUBKEYS: {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java
index bb669f6b8..29621662f 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java
@@ -237,6 +237,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("");
default:
message = "This should not happen!";
break;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java
index c94b29bac..c936fb6cc 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java
@@ -72,6 +72,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
+import org.sufficientlysecure.keychain.ui.linked.LinkedIdWizard;
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State;
@@ -262,7 +263,6 @@ public class ViewKeyActivity extends BaseActivity implements
initNfc(mDataUri);
- startFragment(savedInstanceState, mDataUri);
}
@Override
@@ -270,26 +270,6 @@ public class ViewKeyActivity extends BaseActivity implements
setContentView(R.layout.view_key_activity);
}
- private void startFragment(Bundle savedInstanceState, Uri dataUri) {
- // However, if we're being restored from a previous state,
- // then we don't need to do anything and should return or else
- // we could end up with overlapping fragments.
- if (savedInstanceState != null) {
- return;
- }
-
- // Create an instance of the fragment
- ViewKeyFragment frag = ViewKeyFragment.newInstance(dataUri);
-
- // Add the fragment to the 'fragment_container' FrameLayout
- // NOTE: We use commitAllowingStateLoss() to prevent weird crashes!
- getSupportFragmentManager().beginTransaction()
- .replace(R.id.view_key_fragment, frag)
- .commitAllowingStateLoss();
- // do it immediately!
- getSupportFragmentManager().executePendingTransactions();
- }
-
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
@@ -349,6 +329,12 @@ public class ViewKeyActivity extends BaseActivity 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;
@@ -365,6 +351,10 @@ public class ViewKeyActivity extends BaseActivity implements
public boolean onPrepareOptionsMenu(Menu menu) {
MenuItem editKey = menu.findItem(R.id.menu_key_view_edit);
editKey.setVisible(mIsSecret);
+
+ MenuItem addLinked = menu.findItem(R.id.menu_key_view_add_linked_identity);
+ addLinked.setVisible(mIsSecret);
+
MenuItem certifyFingerprint = menu.findItem(R.id.menu_key_view_certify_fingerprint);
certifyFingerprint.setVisible(!mIsSecret && !mIsVerified && !mIsExpired && !mIsRevoked);
@@ -832,166 +822,172 @@ public class ViewKeyActivity extends BaseActivity implements
// old cursor once we return.)
switch (loader.getId()) {
case LOADER_ID_UNIFIED: {
- if (data.moveToFirst()) {
- // get name, email, and comment from USER_ID
- String[] mainUserId = KeyRing.splitUserId(data.getString(INDEX_USER_ID));
- if (mainUserId[0] != null) {
- mName.setText(mainUserId[0]);
+ // if there is no data, just break
+ if (!data.moveToFirst()) {
+ break;
+ }
+
+ String oldFingerprint = mFingerprint;
+ mMasterKeyId = data.getLong(INDEX_MASTER_KEY_ID);
+ byte[] fpData = data.getBlob(INDEX_FINGERPRINT);
+ mFingerprint = KeyFormattingUtils.convertFingerprintToHex(fpData);
+
+ mIsSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0;
+ mHasEncrypt = data.getInt(INDEX_HAS_ENCRYPT) != 0;
+ mIsRevoked = data.getInt(INDEX_IS_REVOKED) > 0;
+ mIsExpired = data.getInt(INDEX_IS_EXPIRED) != 0;
+ mIsVerified = data.getInt(INDEX_VERIFIED) > 0;
+
+ startFragment(mIsSecret, fpData);
+
+ // get name, email, and comment from USER_ID
+ String[] mainUserId = KeyRing.splitUserId(data.getString(INDEX_USER_ID));
+ if (mainUserId[0] != null) {
+ mName.setText(mainUserId[0]);
+ } else {
+ mName.setText(R.string.user_id_no_name);
+ }
+
+ // if the refresh animation isn't playing
+ if (!mRotate.hasStarted() && !mRotateSpin.hasStarted()) {
+ // re-create options menu based on mIsSecret, mIsVerified
+ supportInvalidateOptionsMenu();
+ // this is done at the end of the animation otherwise
+ }
+
+ AsyncTask<Long, Void, Bitmap> photoTask =
+ new AsyncTask<Long, Void, Bitmap>() {
+ protected Bitmap doInBackground(Long... mMasterKeyId) {
+ return ContactHelper.loadPhotoByMasterKeyId(getContentResolver(), mMasterKeyId[0], true);
+ }
+
+ protected void onPostExecute(Bitmap photo) {
+ mPhoto.setImageBitmap(photo);
+ mPhoto.setVisibility(View.VISIBLE);
+ }
+ };
+
+ // Note: order is important
+ int color;
+ if (mIsRevoked) {
+ mStatusText.setText(R.string.view_key_revoked);
+ mStatusImage.setVisibility(View.VISIBLE);
+ KeyFormattingUtils.setStatusImage(this, mStatusImage, mStatusText,
+ State.REVOKED, R.color.icons, true);
+ color = getResources().getColor(R.color.android_red_light);
+
+ mActionEncryptFile.setVisibility(View.GONE);
+ mActionEncryptText.setVisibility(View.GONE);
+ mActionNfc.setVisibility(View.GONE);
+ mFab.setVisibility(View.GONE);
+ mQrCodeLayout.setVisibility(View.GONE);
+ } else if (mIsExpired) {
+ if (mIsSecret) {
+ mStatusText.setText(R.string.view_key_expired_secret);
} else {
- mName.setText(R.string.user_id_no_name);
+ mStatusText.setText(R.string.view_key_expired);
}
-
- String oldFingerprint = mFingerprint;
- mMasterKeyId = data.getLong(INDEX_MASTER_KEY_ID);
- mFingerprint = KeyFormattingUtils.convertFingerprintToHex(data.getBlob(INDEX_FINGERPRINT));
-
- mIsSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0;
- mHasEncrypt = data.getInt(INDEX_HAS_ENCRYPT) != 0;
- mIsRevoked = data.getInt(INDEX_IS_REVOKED) > 0;
- mIsExpired = data.getInt(INDEX_IS_EXPIRED) != 0;
- mIsVerified = data.getInt(INDEX_VERIFIED) > 0;
-
- // if the refresh animation isn't playing
- if (!mRotate.hasStarted() && !mRotateSpin.hasStarted()) {
- // re-create options menu based on mIsSecret, mIsVerified
- supportInvalidateOptionsMenu();
- // this is done at the end of the animation otherwise
+ mStatusImage.setVisibility(View.VISIBLE);
+ KeyFormattingUtils.setStatusImage(this, mStatusImage, mStatusText,
+ State.EXPIRED, R.color.icons, true);
+ color = getResources().getColor(R.color.android_red_light);
+
+ mActionEncryptFile.setVisibility(View.GONE);
+ mActionEncryptText.setVisibility(View.GONE);
+ mActionNfc.setVisibility(View.GONE);
+ mFab.setVisibility(View.GONE);
+ mQrCodeLayout.setVisibility(View.GONE);
+ } else if (mIsSecret) {
+ mStatusText.setText(R.string.view_key_my_key);
+ mStatusImage.setVisibility(View.GONE);
+ color = getResources().getColor(R.color.primary);
+ // reload qr code only if the fingerprint changed
+ if (!mFingerprint.equals(oldFingerprint)) {
+ loadQrCode(mFingerprint);
+ }
+ photoTask.execute(mMasterKeyId);
+ mQrCodeLayout.setVisibility(View.VISIBLE);
+
+ // and place leftOf qr code
+ RelativeLayout.LayoutParams nameParams = (RelativeLayout.LayoutParams)
+ mName.getLayoutParams();
+ // remove right margin
+ nameParams.setMargins(FormattingUtils.dpToPx(this, 48), 0, 0, 0);
+ if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ nameParams.setMarginEnd(0);
+ }
+ nameParams.addRule(RelativeLayout.LEFT_OF, R.id.view_key_qr_code_layout);
+ mName.setLayoutParams(nameParams);
+
+ RelativeLayout.LayoutParams statusParams = (RelativeLayout.LayoutParams)
+ mStatusText.getLayoutParams();
+ statusParams.setMargins(FormattingUtils.dpToPx(this, 48), 0, 0, 0);
+ if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ statusParams.setMarginEnd(0);
}
+ statusParams.addRule(RelativeLayout.LEFT_OF, R.id.view_key_qr_code_layout);
+ mStatusText.setLayoutParams(statusParams);
- AsyncTask<Long, Void, Bitmap> photoTask =
- new AsyncTask<Long, Void, Bitmap>() {
- protected Bitmap doInBackground(Long... mMasterKeyId) {
- return ContactHelper.loadPhotoByMasterKeyId(getContentResolver(), mMasterKeyId[0], true);
- }
+ mActionEncryptFile.setVisibility(View.VISIBLE);
+ mActionEncryptText.setVisibility(View.VISIBLE);
- protected void onPostExecute(Bitmap photo) {
- mPhoto.setImageBitmap(photo);
- mPhoto.setVisibility(View.VISIBLE);
- }
- };
+ // invokeBeam is available from API 21
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ mActionNfc.setVisibility(View.VISIBLE);
+ } else {
+ mActionNfc.setVisibility(View.GONE);
+ }
+ mFab.setVisibility(View.VISIBLE);
+ mFab.setIconDrawable(getResources().getDrawable(R.drawable.ic_repeat_white_24dp));
+ } else {
+ mActionEncryptFile.setVisibility(View.VISIBLE);
+ mActionEncryptText.setVisibility(View.VISIBLE);
+ mQrCodeLayout.setVisibility(View.GONE);
+ mActionNfc.setVisibility(View.GONE);
- // Note: order is important
- int color;
- if (mIsRevoked) {
- mStatusText.setText(R.string.view_key_revoked);
+ if (mIsVerified) {
+ mStatusText.setText(R.string.view_key_verified);
mStatusImage.setVisibility(View.VISIBLE);
KeyFormattingUtils.setStatusImage(this, mStatusImage, mStatusText,
- State.REVOKED, R.color.icons, true);
- color = getResources().getColor(R.color.android_red_light);
+ State.VERIFIED, R.color.icons, true);
+ color = getResources().getColor(R.color.primary);
+ photoTask.execute(mMasterKeyId);
- mActionEncryptFile.setVisibility(View.GONE);
- mActionEncryptText.setVisibility(View.GONE);
- mActionNfc.setVisibility(View.GONE);
mFab.setVisibility(View.GONE);
- mQrCodeLayout.setVisibility(View.GONE);
- } else if (mIsExpired) {
- if (mIsSecret) {
- mStatusText.setText(R.string.view_key_expired_secret);
- } else {
- mStatusText.setText(R.string.view_key_expired);
- }
+ } else {
+ mStatusText.setText(R.string.view_key_unverified);
mStatusImage.setVisibility(View.VISIBLE);
KeyFormattingUtils.setStatusImage(this, mStatusImage, mStatusText,
- State.EXPIRED, R.color.icons, true);
- color = getResources().getColor(R.color.android_red_light);
+ State.UNVERIFIED, R.color.icons, true);
+ color = getResources().getColor(R.color.android_orange_light);
- mActionEncryptFile.setVisibility(View.GONE);
- mActionEncryptText.setVisibility(View.GONE);
- mActionNfc.setVisibility(View.GONE);
- mFab.setVisibility(View.GONE);
- mQrCodeLayout.setVisibility(View.GONE);
- } else if (mIsSecret) {
- mStatusText.setText(R.string.view_key_my_key);
- mStatusImage.setVisibility(View.GONE);
- color = getResources().getColor(R.color.primary);
- // reload qr code only if the fingerprint changed
- if (!mFingerprint.equals(oldFingerprint)) {
- loadQrCode(mFingerprint);
- }
- photoTask.execute(mMasterKeyId);
- mQrCodeLayout.setVisibility(View.VISIBLE);
-
- // and place leftOf qr code
- RelativeLayout.LayoutParams nameParams = (RelativeLayout.LayoutParams)
- mName.getLayoutParams();
- // remove right margin
- nameParams.setMargins(FormattingUtils.dpToPx(this, 48), 0, 0, 0);
- if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
- nameParams.setMarginEnd(0);
- }
- nameParams.addRule(RelativeLayout.LEFT_OF, R.id.view_key_qr_code_layout);
- mName.setLayoutParams(nameParams);
-
- RelativeLayout.LayoutParams statusParams = (RelativeLayout.LayoutParams)
- mStatusText.getLayoutParams();
- statusParams.setMargins(FormattingUtils.dpToPx(this, 48), 0, 0, 0);
- if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
- statusParams.setMarginEnd(0);
- }
- statusParams.addRule(RelativeLayout.LEFT_OF, R.id.view_key_qr_code_layout);
- mStatusText.setLayoutParams(statusParams);
-
- mActionEncryptFile.setVisibility(View.VISIBLE);
- mActionEncryptText.setVisibility(View.VISIBLE);
-
- // invokeBeam is available from API 21
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- mActionNfc.setVisibility(View.VISIBLE);
- } else {
- mActionNfc.setVisibility(View.GONE);
- }
mFab.setVisibility(View.VISIBLE);
- mFab.setIconDrawable(getResources().getDrawable(R.drawable.ic_repeat_white_24dp));
- } else {
- mActionEncryptFile.setVisibility(View.VISIBLE);
- mActionEncryptText.setVisibility(View.VISIBLE);
- mQrCodeLayout.setVisibility(View.GONE);
- mActionNfc.setVisibility(View.GONE);
-
- if (mIsVerified) {
- mStatusText.setText(R.string.view_key_verified);
- mStatusImage.setVisibility(View.VISIBLE);
- KeyFormattingUtils.setStatusImage(this, mStatusImage, mStatusText,
- State.VERIFIED, R.color.icons, true);
- color = getResources().getColor(R.color.primary);
- photoTask.execute(mMasterKeyId);
-
- mFab.setVisibility(View.GONE);
- } else {
- mStatusText.setText(R.string.view_key_unverified);
- mStatusImage.setVisibility(View.VISIBLE);
- KeyFormattingUtils.setStatusImage(this, mStatusImage, mStatusText,
- State.UNVERIFIED, R.color.icons, true);
- color = getResources().getColor(R.color.android_orange_light);
-
- mFab.setVisibility(View.VISIBLE);
- }
}
+ }
- if (mPreviousColor == 0 || mPreviousColor == color) {
- mStatusBar.setBackgroundColor(color);
- mBigToolbar.setBackgroundColor(color);
- mPreviousColor = color;
- } else {
- ObjectAnimator colorFade1 =
- ObjectAnimator.ofObject(mStatusBar, "backgroundColor",
- new ArgbEvaluator(), mPreviousColor, color);
- ObjectAnimator colorFade2 =
- ObjectAnimator.ofObject(mBigToolbar, "backgroundColor",
- new ArgbEvaluator(), mPreviousColor, color);
-
- colorFade1.setDuration(1200);
- colorFade2.setDuration(1200);
- colorFade1.start();
- colorFade2.start();
- mPreviousColor = color;
- }
+ if (mPreviousColor == 0 || mPreviousColor == color) {
+ mStatusBar.setBackgroundColor(color);
+ mBigToolbar.setBackgroundColor(color);
+ mPreviousColor = color;
+ } else {
+ ObjectAnimator colorFade1 =
+ ObjectAnimator.ofObject(mStatusBar, "backgroundColor",
+ new ArgbEvaluator(), mPreviousColor, color);
+ ObjectAnimator colorFade2 =
+ ObjectAnimator.ofObject(mBigToolbar, "backgroundColor",
+ new ArgbEvaluator(), mPreviousColor, color);
+
+ colorFade1.setDuration(1200);
+ colorFade2.setDuration(1200);
+ colorFade1.start();
+ colorFade2.start();
+ mPreviousColor = color;
+ }
- //noinspection deprecation
- mStatusImage.setAlpha(80);
+ //noinspection deprecation
+ mStatusImage.setAlpha(80);
- break;
- }
+ break;
}
}
}
@@ -1000,4 +996,18 @@ public class ViewKeyActivity extends BaseActivity implements
public void onLoaderReset(Loader<Cursor> loader) {
}
+
+ private void startFragment(boolean isSecret, byte[] fingerprint) {
+ // Create an instance of the fragment
+ ViewKeyFragment frag = ViewKeyFragment.newInstance(mDataUri, isSecret, fingerprint);
+
+ // Add the fragment to the 'fragment_container' FrameLayout
+ // NOTE: We use commitAllowingStateLoss() to prevent weird crashes!
+ getSupportFragmentManager().beginTransaction()
+ .replace(R.id.view_key_fragment, frag, "main")
+ .commitAllowingStateLoss();
+ // do it immediately!
+ getSupportFragmentManager().executePendingTransactions();
+ }
+
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvUserIdsFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvUserIdsFragment.java
index 7bfebaf62..ad437f924 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvUserIdsFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvUserIdsFragment.java
@@ -133,7 +133,7 @@ public class ViewKeyAdvUserIdsFragment extends LoaderFragment implements
case LOADER_ID_USER_IDS: {
Uri baseUri = UserPackets.buildUserIdsUri(mDataUri);
return new CursorLoader(getActivity(), baseUri,
- UserIdsAdapter.USER_IDS_PROJECTION, null, null, null);
+ UserIdsAdapter.USER_PACKETS_PROJECTION, null, null, null);
}
default:
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java
index 628970b27..240dd3547 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java
@@ -18,22 +18,32 @@
package org.sufficientlysecure.keychain.ui;
+import java.io.IOException;
+
import android.database.Cursor;
import android.net.Uri;
+import android.os.Build;
import android.os.Bundle;
+import android.support.v4.app.Fragment;
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 org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
import org.sufficientlysecure.keychain.provider.KeychainContract;
+import org.sufficientlysecure.keychain.ui.adapter.LinkedIdsAdapter;
import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter;
import org.sufficientlysecure.keychain.ui.dialog.UserIdInfoDialogFragment;
import org.sufficientlysecure.keychain.util.Log;
@@ -42,25 +52,33 @@ public class ViewKeyFragment extends LoaderFragment implements
LoaderManager.LoaderCallbacks<Cursor> {
public static final String ARG_DATA_URI = "uri";
+ private static final String ARG_FINGERPRINT = "fingerprint";
+ private static final String ARG_IS_SECRET = "is_secret";
private ListView mUserIds;
boolean mIsSecret = false;
- private static final int LOADER_ID_UNIFIED = 0;
- private static final int LOADER_ID_USER_IDS = 1;
+ private static final int LOADER_ID_USER_IDS = 0;
+ private static final int LOADER_ID_LINKED_IDS = 1;
private UserIdsAdapter mUserIdsAdapter;
+ private LinkedIdsAdapter mLinkedIdsAdapter;
private Uri mDataUri;
+ private ListView mLinkedIds;
+ private CardView mLinkedIdsCard;
+ private byte[] mFingerprint;
/**
* Creates new instance of this fragment
*/
- public static ViewKeyFragment newInstance(Uri dataUri) {
+ public static ViewKeyFragment newInstance(Uri dataUri, boolean isSecret, byte[] fingerprint) {
ViewKeyFragment frag = new ViewKeyFragment();
Bundle args = new Bundle();
args.putParcelable(ARG_DATA_URI, dataUri);
+ args.putBoolean(ARG_IS_SECRET, isSecret);
+ args.putByteArray(ARG_FINGERPRINT, fingerprint);
frag.setArguments(args);
@@ -68,11 +86,31 @@ public class ViewKeyFragment extends LoaderFragment implements
}
@Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ Bundle args = getArguments();
+ Uri dataUri = args.getParcelable(ARG_DATA_URI);
+ if (dataUri == null) {
+ Log.e(Constants.TAG, "Data missing. Should be Uri of key!");
+ getActivity().finish();
+ return;
+ }
+ boolean isSecret = args.getBoolean(ARG_IS_SECRET);
+ byte[] fingerprint = args.getByteArray(ARG_FINGERPRINT);
+
+ loadData(dataUri, isSecret, fingerprint);
+ }
+
+ @Override
public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) {
View root = super.onCreateView(inflater, superContainer, savedInstanceState);
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);
mUserIds.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
@@ -80,10 +118,40 @@ 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);
+ }
+ });
return root;
}
+ private void showLinkedId(final int position) {
+ Fragment frag;
+ try {
+ frag = mLinkedIdsAdapter.getLinkedIdFragment(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()
+ .replace(R.id.view_key_fragment, frag)
+ .addSharedElement(mLinkedIdsCard, "card_linked_ids")
+ .addToBackStack(null)
+ .commit();
+ }
+
private void showUserIdInfo(final int position) {
if (!mIsSecret) {
final boolean isRevoked = mUserIdsAdapter.getIsRevoked(position);
@@ -100,100 +168,53 @@ public class ViewKeyFragment extends LoaderFragment implements
}
}
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
-
- Uri dataUri = getArguments().getParcelable(ARG_DATA_URI);
- if (dataUri == null) {
- Log.e(Constants.TAG, "Data missing. Should be Uri of key!");
- getActivity().finish();
- return;
- }
-
- loadData(dataUri);
- }
-
-
- // These are the rows that we will retrieve.
- static final String[] UNIFIED_PROJECTION = new String[]{
- KeychainContract.KeyRings._ID,
- KeychainContract.KeyRings.MASTER_KEY_ID,
- KeychainContract.KeyRings.USER_ID,
- KeychainContract.KeyRings.IS_REVOKED,
- KeychainContract.KeyRings.IS_EXPIRED,
- KeychainContract.KeyRings.VERIFIED,
- KeychainContract.KeyRings.HAS_ANY_SECRET,
- KeychainContract.KeyRings.FINGERPRINT,
- KeychainContract.KeyRings.HAS_ENCRYPT
- };
-
- static final int INDEX_MASTER_KEY_ID = 1;
- static final int INDEX_USER_ID = 2;
- static final int INDEX_IS_REVOKED = 3;
- static final int INDEX_IS_EXPIRED = 4;
- static final int INDEX_VERIFIED = 5;
- static final int INDEX_HAS_ANY_SECRET = 6;
- static final int INDEX_FINGERPRINT = 7;
- static final int INDEX_HAS_ENCRYPT = 8;
-
- private void loadData(Uri dataUri) {
+ private void loadData(Uri dataUri, boolean isSecret, byte[] fingerprint) {
mDataUri = dataUri;
+ mIsSecret = isSecret;
+ mFingerprint = fingerprint;
Log.i(Constants.TAG, "mDataUri: " + mDataUri);
- // 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);
+ // 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);
+ mLinkedIds.setAdapter(mLinkedIdsAdapter);
+ getLoaderManager().initLoader(LOADER_ID_LINKED_IDS, null, this);
+
}
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
setContentShown(false);
switch (id) {
- case LOADER_ID_UNIFIED: {
- Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(mDataUri);
- return new CursorLoader(getActivity(), baseUri, UNIFIED_PROJECTION, null, null, null);
- }
case LOADER_ID_USER_IDS:
return UserIdsAdapter.createLoader(getActivity(), mDataUri);
+ case LOADER_ID_LINKED_IDS:
+ return LinkedIdsAdapter.createLoader(getActivity(), mDataUri);
+
default:
return null;
}
}
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
- /* TODO better error handling? May cause problems when a key is deleted,
- * because the notification triggers faster than the activity closes.
- */
- // Avoid NullPointerExceptions...
- if (data.getCount() == 0) {
- return;
- }
// Swap the new cursor in. (The framework will take care of closing the
// old cursor once we return.)
switch (loader.getId()) {
- case LOADER_ID_UNIFIED: {
- if (data.moveToFirst()) {
-
- mIsSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0;
-
- // 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);
-
- break;
- }
- }
-
case LOADER_ID_USER_IDS: {
mUserIdsAdapter.swapCursor(data);
break;
}
+ case LOADER_ID_LINKED_IDS: {
+ mLinkedIdsCard.setVisibility(data.getCount() > 0 ? View.VISIBLE : View.GONE);
+ mLinkedIdsAdapter.swapCursor(data);
+ break;
+ }
}
setContentShown(true);
}
@@ -208,6 +229,11 @@ public class ViewKeyFragment extends LoaderFragment implements
mUserIdsAdapter.swapCursor(null);
break;
}
+ case LOADER_ID_LINKED_IDS: {
+ mLinkedIdsCard.setVisibility(View.GONE);
+ mLinkedIdsAdapter.swapCursor(null);
+ break;
+ }
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/LinkedIdsAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/LinkedIdsAdapter.java
new file mode 100644
index 000000000..365b8d265
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/LinkedIdsAdapter.java
@@ -0,0 +1,179 @@
+/*
+ * 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.app.Activity;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.support.v4.app.Fragment;
+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.pgp.linked.LinkedIdentity;
+import org.sufficientlysecure.keychain.pgp.linked.RawLinkedIdentity;
+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 java.io.IOException;
+import java.util.WeakHashMap;
+
+
+public class LinkedIdsAdapter extends UserAttributesAdapter {
+ private final boolean mShowCertification;
+ protected LayoutInflater mInflater;
+ WeakHashMap<Integer,RawLinkedIdentity> mLinkedIdentityCache = new WeakHashMap<>();
+
+ public LinkedIdsAdapter(Context context, Cursor c, int flags, boolean showCertification) {
+ super(context, c, flags);
+ mInflater = LayoutInflater.from(context);
+ mShowCertification = showCertification;
+ }
+
+ @Override
+ public void bindView(View view, Context context, Cursor cursor) {
+
+ ViewHolder holder = (ViewHolder) view.getTag();
+
+ if (mShowCertification) {
+ holder.vVerified.setVisibility(View.VISIBLE);
+ 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;
+ }
+ } else {
+ holder.vVerified.setVisibility(View.GONE);
+ }
+
+ RawLinkedIdentity id = getItem(cursor.getPosition());
+ holder.setData(mContext, id);
+
+ }
+
+ @Override
+ public RawLinkedIdentity getItem(int position) {
+ RawLinkedIdentity ret = mLinkedIdentityCache.get(position);
+ if (ret != null) {
+ return ret;
+ }
+
+ Cursor c = getCursor();
+ c.moveToPosition(position);
+
+ byte[] data = c.getBlob(INDEX_ATTRIBUTE_DATA);
+ try {
+ ret = LinkedIdentity.fromAttributeData(data);
+ mLinkedIdentityCache.put(position, ret);
+ return ret;
+ } catch (IOException e) {
+ Log.e(Constants.TAG, "could not read linked identity subpacket data", e);
+ return null;
+ }
+ }
+
+ @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 Fragment getLinkedIdFragment(int position, byte[] fingerprint) throws IOException {
+ RawLinkedIdentity id = getItem(position);
+
+ Integer isVerified;
+ if (mShowCertification) {
+ Cursor cursor = getCursor();
+ cursor.moveToPosition(position);
+ isVerified = cursor.getInt(INDEX_VERIFIED);
+ } else {
+ isVerified = null;
+ }
+
+ return LinkedIdViewFragment.newInstance(id, isVerified, 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.user_id_item_certified);
+ 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, RawLinkedIdentity 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());
+
+ }
+ }
+
+ @Override
+ public void notifyDataSetChanged() {
+ mLinkedIdentityCache.clear();
+ super.notifyDataSetChanged();
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserAttributesAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserAttributesAdapter.java
index 457083770..345ccccc0 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserAttributesAdapter.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserAttributesAdapter.java
@@ -8,10 +8,11 @@ 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,
@@ -20,10 +21,11 @@ public abstract class UserAttributesAdapter extends CursorAdapter {
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;
+ 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 UserAttributesAdapter(Context context, Cursor c, int flags) {
super(context, c, flags);
@@ -46,4 +48,5 @@ public abstract class UserAttributesAdapter extends CursorAdapter {
mCursor.moveToPosition(position);
return mCursor.getInt(INDEX_VERIFIED);
}
+
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java
index 3486f1516..1cf3f4d38 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java
@@ -188,7 +188,7 @@ public class UserIdsAdapter extends UserAttributesAdapter {
public static CursorLoader createLoader(Activity activity, Uri dataUri) {
Uri baseUri = UserPackets.buildUserIdsUri(dataUri);
return new CursorLoader(activity, baseUri,
- UserIdsAdapter.USER_IDS_PROJECTION, USER_IDS_WHERE, null, null);
+ UserIdsAdapter.USER_PACKETS_PROJECTION, USER_IDS_WHERE, null, null);
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateDnsStep1Fragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateDnsStep1Fragment.java
new file mode 100644
index 000000000..26b0a0539
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateDnsStep1Fragment.java
@@ -0,0 +1,133 @@
+/*
+ * 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.pgp.linked.RawLinkedIdentity;
+import org.sufficientlysecure.keychain.pgp.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;
+ }
+
+ int proofNonce = RawLinkedIdentity.generateNonce();
+ String proofText = DnsResource.generateText(getActivity(),
+ mLinkedIdWizard.mFingerprint, proofNonce);
+
+ LinkedIdCreateDnsStep2Fragment frag =
+ LinkedIdCreateDnsStep2Fragment.newInstance(uri, proofNonce, proofText);
+
+ mLinkedIdWizard.loadFragment(null, frag, LinkedIdWizard.FRAG_ACTION_TO_RIGHT);
+
+ }
+ });
+
+ view.findViewById(R.id.back_button).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mLinkedIdWizard.loadFragment(null, null, LinkedIdWizard.FRAG_ACTION_TO_LEFT);
+ }
+ });
+
+ mEditDns = (EditText) view.findViewById(R.id.linked_create_dns_domain);
+
+ mEditDns.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {
+ }
+
+ @Override
+ public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {
+ }
+
+ @Override
+ public void afterTextChanged(Editable editable) {
+ String uri = editable.toString();
+ if (uri.length() > 0) {
+ if (checkUri(uri)) {
+ mEditDns.setCompoundDrawablesWithIntrinsicBounds(0, 0,
+ R.drawable.uid_mail_ok, 0);
+ } else {
+ mEditDns.setCompoundDrawablesWithIntrinsicBounds(0, 0,
+ R.drawable.uid_mail_bad, 0);
+ }
+ } else {
+ // remove drawable if email is empty
+ mEditDns.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
+ }
+ }
+ });
+
+ mEditDns.setText("test.mugenguild.com");
+
+ return view;
+ }
+
+ private static boolean checkUri(String uri) {
+ return Patterns.DOMAIN_NAME.matcher(uri).matches();
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateDnsStep2Fragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateDnsStep2Fragment.java
new file mode 100644
index 000000000..de3a63256
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateDnsStep2Fragment.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.ui.linked;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Environment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.pgp.linked.LinkedCookieResource;
+import org.sufficientlysecure.keychain.pgp.linked.resources.DnsResource;
+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;
+
+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, int proofNonce, String proofText) {
+
+ LinkedIdCreateDnsStep2Fragment frag = new LinkedIdCreateDnsStep2Fragment();
+
+ Bundle args = new Bundle();
+ args.putInt(ARG_NONCE, proofNonce);
+ 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);
+
+ 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();
+ }
+ });
+
+ mTextView = (TextView) view.findViewById(R.id.linked_create_dns_text);
+ mTextView.setText(mResourceString);
+
+ return view;
+ }
+
+ @Override
+ LinkedCookieResource getResource() {
+ 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 proofSave () {
+ String state = Environment.getExternalStorageState();
+ if (!Environment.MEDIA_MOUNTED.equals(state)) {
+ Notify.showNotify(getActivity(), "External storage not available!", Style.ERROR);
+ return;
+ }
+
+ String targetName = "pgpkey.txt";
+
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
+ File targetFile = new File(Constants.Path.APP_DIR, targetName);
+ FileHelper.saveFile(this, getString(R.string.title_decrypt_to_file),
+ getString(R.string.specify_file_to_decrypt_to), targetFile, REQUEST_CODE_OUTPUT);
+ } else {
+ FileHelper.saveDocument(this, "text/plain", targetName, REQUEST_CODE_OUTPUT);
+ }
+ }
+
+ private void saveFile(Uri uri) {
+ try {
+ PrintWriter out =
+ new PrintWriter(getActivity().getContentResolver().openOutputStream(uri));
+ out.print(mResourceString);
+ if (out.checkError()) {
+ Notify.showNotify(getActivity(), "Error writing file!", Style.ERROR);
+ }
+ } catch (FileNotFoundException e) {
+ Notify.showNotify(getActivity(), "File could not be opened for writing!", Style.ERROR);
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ switch (requestCode) {
+ // For saving a file
+ case REQUEST_CODE_OUTPUT:
+ if (data == null) {
+ return;
+ }
+ Uri uri = data.getData();
+ saveFile(uri);
+ break;
+ default:
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateFinalFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateFinalFragment.java
new file mode 100644
index 000000000..ef76bc9d2
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateFinalFragment.java
@@ -0,0 +1,238 @@
+package org.sufficientlysecure.keychain.ui.linked;
+
+import android.app.Activity;
+import android.app.ProgressDialog;
+import android.content.Intent;
+import android.graphics.PorterDuff;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Message;
+import android.os.Messenger;
+import android.support.v4.app.Fragment;
+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 org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.operations.results.LinkedVerifyResult;
+import org.sufficientlysecure.keychain.operations.results.OperationResult;
+import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute;
+import org.sufficientlysecure.keychain.pgp.linked.LinkedCookieResource;
+import org.sufficientlysecure.keychain.pgp.linked.LinkedIdentity;
+import org.sufficientlysecure.keychain.service.KeychainIntentService;
+import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
+import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
+import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity;
+import org.sufficientlysecure.keychain.ui.util.Notify;
+
+public abstract class LinkedIdCreateFinalFragment extends Fragment {
+
+ public static final String ARG_NONCE = "nonce";
+ protected static final int REQUEST_CODE_PASSPHRASE = 0x00007008;
+
+ private LinkedIdWizard mLinkedIdWizard;
+
+ private ImageView mVerifyImage;
+ private View mVerifyProgress;
+ private TextView mVerifyStatus;
+ private int mResourceNonce;
+
+ // This is a resource, set AFTER it has been verified
+ LinkedCookieResource mVerifiedResource = null;
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ mLinkedIdWizard = (LinkedIdWizard) getActivity();
+
+ mResourceNonce = getArguments().getInt(ARG_NONCE);
+ }
+
+ 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) {
+ startCertify();
+ }
+ });
+
+ 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);
+ }
+ });
+
+ mVerifyImage = (ImageView) view.findViewById(R.id.verify_image);
+ mVerifyProgress = view.findViewById(R.id.verify_progress);
+ mVerifyStatus = (TextView) view.findViewById(R.id.verify_status);
+
+ view.findViewById(R.id.button_verify).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ proofVerify();
+ }
+ });
+
+ setVerifyProgress(false, null);
+ mVerifyStatus.setText(R.string.linked_verify_pending);
+
+ return view;
+ }
+
+
+ abstract LinkedCookieResource getResource();
+
+ private void setVerifyProgress(boolean on, Boolean success) {
+ mVerifyProgress.setVisibility(on ? View.VISIBLE : View.GONE);
+ mVerifyImage.setVisibility(on ? View.GONE : View.VISIBLE);
+ if (success == null) {
+ mVerifyStatus.setText(R.string.linked_verifying);
+ mVerifyImage.setImageResource(R.drawable.status_signature_unverified_cutout_24dp);
+ mVerifyImage.setColorFilter(getResources().getColor(R.color.tertiary_text_light),
+ PorterDuff.Mode.SRC_IN);
+ } 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);
+ } 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);
+ }
+ }
+
+ private void proofVerify() {
+ setVerifyProgress(true, null);
+
+ final LinkedCookieResource resource = getResource();
+
+ new AsyncTask<Void,Void,LinkedVerifyResult>() {
+
+ @Override
+ protected LinkedVerifyResult doInBackground(Void... params) {
+ return resource.verify(mLinkedIdWizard.mFingerprint, mResourceNonce);
+ }
+
+ @Override
+ protected void onPostExecute(LinkedVerifyResult result) {
+ super.onPostExecute(result);
+ if (result.success()) {
+ setVerifyProgress(false, true);
+ mVerifiedResource = resource;
+ } else {
+ setVerifyProgress(false, false);
+ // on error, show error message
+ result.createNotify(getActivity()).show();
+ }
+ }
+ }.execute();
+
+ }
+
+ private void startCertify() {
+
+ if (mVerifiedResource == null) {
+ Notify.showNotify(getActivity(), R.string.linked_need_verify, Notify.Style.ERROR);
+ return;
+ }
+
+ Intent intent = new Intent(getActivity(), PassphraseDialogActivity.class);
+ intent.putExtra(PassphraseDialogActivity.EXTRA_SUBKEY_ID, mLinkedIdWizard.mMasterKeyId);
+ startActivityForResult(intent, REQUEST_CODE_PASSPHRASE);
+
+ }
+
+ private void certifyLinkedIdentity (String passphrase) {
+ KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(
+ getActivity(),
+ getString(R.string.progress_saving),
+ ProgressDialog.STYLE_HORIZONTAL,
+ true) {
+ public void handleMessage(Message message) {
+ // handle messages by standard KeychainIntentServiceHandler first
+ super.handleMessage(message);
+
+ if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
+
+ // get returned data bundle
+ Bundle returnData = message.getData();
+ if (returnData == null) {
+ return;
+ }
+ final OperationResult result =
+ returnData.getParcelable(OperationResult.EXTRA_RESULT);
+ if (result == null) {
+ return;
+ }
+
+ // if bad -> display here!
+ if (!result.success()) {
+ result.createNotify(getActivity()).show();
+ return;
+ }
+
+ getActivity().finish();
+
+ }
+ }
+ };
+
+ SaveKeyringParcel skp =
+ new SaveKeyringParcel(mLinkedIdWizard.mMasterKeyId, mLinkedIdWizard.mFingerprint);
+
+ WrappedUserAttribute ua =
+ LinkedIdentity.fromResource(mVerifiedResource, mResourceNonce).toUserAttribute();
+
+ skp.mAddUserAttribute.add(ua);
+
+ // Send all information needed to service to import key in other thread
+ Intent intent = new Intent(getActivity(), KeychainIntentService.class);
+ intent.setAction(KeychainIntentService.ACTION_EDIT_KEYRING);
+
+ // fill values for this action
+ Bundle data = new Bundle();
+ data.putString(KeychainIntentService.EDIT_KEYRING_PASSPHRASE, passphrase);
+ data.putParcelable(KeychainIntentService.EDIT_KEYRING_PARCEL, skp);
+ intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
+
+ // Create a new Messenger for the communication back
+ Messenger messenger = new Messenger(saveHandler);
+ intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
+
+ // show progress dialog
+ saveHandler.showProgressDialog(getActivity());
+
+ // start service with intent
+ getActivity().startService(intent);
+
+ }
+
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ switch (requestCode) {
+ case REQUEST_CODE_PASSPHRASE:
+ if (resultCode == Activity.RESULT_OK && data != null) {
+ String passphrase =
+ data.getStringExtra(PassphraseDialogActivity.MESSAGE_DATA_PASSPHRASE);
+ certifyLinkedIdentity(passphrase);
+ }
+ break;
+ default:
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateHttpsStep1Fragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateHttpsStep1Fragment.java
new file mode 100644
index 000000000..78ca4cfe9
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateHttpsStep1Fragment.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.ui.linked;
+
+import android.os.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.pgp.linked.RawLinkedIdentity;
+import org.sufficientlysecure.keychain.pgp.linked.resources.GenericHttpsResource;
+
+public class LinkedIdCreateHttpsStep1Fragment extends Fragment {
+
+ LinkedIdWizard mLinkedIdWizard;
+
+ EditText mEditUri;
+
+ /**
+ * Creates new instance of this fragment
+ */
+ 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;
+ }
+
+ int proofNonce = RawLinkedIdentity.generateNonce();
+ String proofText = GenericHttpsResource.generateText(getActivity(),
+ mLinkedIdWizard.mFingerprint, proofNonce);
+
+ LinkedIdCreateHttpsStep2Fragment frag =
+ LinkedIdCreateHttpsStep2Fragment.newInstance(uri, proofNonce, proofText);
+
+ mLinkedIdWizard.loadFragment(null, frag, LinkedIdWizard.FRAG_ACTION_TO_RIGHT);
+
+ }
+ });
+
+ view.findViewById(R.id.back_button).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mLinkedIdWizard.loadFragment(null, null, LinkedIdWizard.FRAG_ACTION_TO_LEFT);
+ }
+ });
+
+ mEditUri = (EditText) view.findViewById(R.id.linked_create_https_uri);
+
+ mEditUri.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {
+ }
+
+ @Override
+ public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {
+ }
+
+ @Override
+ public void afterTextChanged(Editable editable) {
+ String uri = "https://" + editable;
+ if (uri.length() > 0) {
+ if (checkUri(uri)) {
+ mEditUri.setCompoundDrawablesWithIntrinsicBounds(0, 0,
+ R.drawable.uid_mail_ok, 0);
+ } else {
+ mEditUri.setCompoundDrawablesWithIntrinsicBounds(0, 0,
+ R.drawable.uid_mail_bad, 0);
+ }
+ } else {
+ // remove drawable if email is empty
+ mEditUri.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
+ }
+ }
+ });
+
+ mEditUri.setText("mugenguild.com/pgpkey.txt");
+
+ return view;
+ }
+
+ private static boolean checkUri(String uri) {
+ return Patterns.WEB_URL.matcher(uri).matches();
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateHttpsStep2Fragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateHttpsStep2Fragment.java
new file mode 100644
index 000000000..adae7eaf5
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateHttpsStep2Fragment.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.ui.linked;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Environment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.EditText;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.pgp.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, int proofNonce, String proofText) {
+
+ LinkedIdCreateHttpsStep2Fragment frag = new LinkedIdCreateHttpsStep2Fragment();
+
+ Bundle args = new Bundle();
+ args.putInt(ARG_NONCE, proofNonce);
+ args.putString(ARG_URI, uri);
+ args.putString(ARG_TEXT, proofText);
+ frag.setArguments(args);
+
+ return frag;
+ }
+
+ @Override
+ GenericHttpsResource getResource() {
+ 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);
+
+ 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.showNotify(getActivity(), "External storage not available!", Style.ERROR);
+ return;
+ }
+
+ String targetName = "pgpkey.txt";
+
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
+ File targetFile = new File(Constants.Path.APP_DIR, targetName);
+ FileHelper.saveFile(this, getString(R.string.title_decrypt_to_file),
+ getString(R.string.specify_file_to_decrypt_to), targetFile, REQUEST_CODE_OUTPUT);
+ } else {
+ FileHelper.saveDocument(this, "text/plain", targetName, REQUEST_CODE_OUTPUT);
+ }
+ }
+
+ private void saveFile(Uri uri) {
+ try {
+ PrintWriter out =
+ new PrintWriter(getActivity().getContentResolver().openOutputStream(uri));
+ out.print(mResourceString);
+ if (out.checkError()) {
+ Notify.showNotify(getActivity(), "Error writing file!", Style.ERROR);
+ }
+ } catch (FileNotFoundException e) {
+ Notify.showNotify(getActivity(), "File could not be opened for writing!", Style.ERROR);
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ switch (requestCode) {
+ // For saving a file
+ case REQUEST_CODE_OUTPUT:
+ if (data == null) {
+ return;
+ }
+ Uri uri = data.getData();
+ saveFile(uri);
+ break;
+ default:
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep1Fragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep1Fragment.java
new file mode 100644
index 000000000..e966fd71f
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep1Fragment.java
@@ -0,0 +1,134 @@
+/*
+ * 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.pgp.linked.RawLinkedIdentity;
+import org.sufficientlysecure.keychain.pgp.linked.resources.TwitterResource;
+import org.sufficientlysecure.keychain.ui.util.Notify;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+public class LinkedIdCreateTwitterStep1Fragment extends Fragment {
+
+ LinkedIdWizard mLinkedIdWizard;
+
+ EditText mEditHandle;
+
+ /**
+ * Creates new instance of this fragment
+ */
+ 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();
+
+ new AsyncTask<Void,Void,Boolean>() {
+
+ @Override
+ protected Boolean doInBackground(Void... params) {
+ return true; // checkHandle(handle);
+ }
+
+ @Override
+ protected void onPostExecute(Boolean result) {
+ super.onPostExecute(result);
+
+ if (result == null) {
+ Notify.showNotify(getActivity(), "Connection error while checking username!", Notify.Style.ERROR);
+ return;
+ }
+
+ if (!result) {
+ Notify.showNotify(getActivity(), "This handle does not exist on Twitter!", Notify.Style.ERROR);
+ return;
+ }
+
+ int proofNonce = RawLinkedIdentity.generateNonce();
+ String proofText = TwitterResource.generateText(getActivity(),
+ mLinkedIdWizard.mFingerprint, proofNonce);
+
+ LinkedIdCreateTwitterStep2Fragment frag =
+ LinkedIdCreateTwitterStep2Fragment.newInstance(handle, proofNonce, proofText);
+
+ 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);
+ mEditHandle.setText("Valodim");
+
+ return view;
+ }
+
+ private static Boolean checkHandle(String handle) {
+ try {
+ HttpURLConnection nection =
+ (HttpURLConnection) new URL("https://twitter.com/" + handle).openConnection();
+ nection.setRequestMethod("HEAD");
+ return nection.getResponseCode() == 200;
+ } catch (IOException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep2Fragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep2Fragment.java
new file mode 100644
index 000000000..837b84d40
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep2Fragment.java
@@ -0,0 +1,152 @@
+/*
+ * 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.InputFilter;
+import android.text.TextWatcher;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.pgp.linked.resources.TwitterResource;
+
+public class LinkedIdCreateTwitterStep2Fragment extends Fragment {
+
+ public static final String HANDLE = "uri", NONCE = "nonce", TEXT = "text";
+
+ LinkedIdWizard mLinkedIdWizard;
+
+ EditText mEditTweetCustom, mEditTweetPreview;
+ ImageView mVerifyImage;
+ View mVerifyProgress;
+ TextView mVerifyStatus, mEditTweetTextLen;
+
+ String mResourceHandle;
+ String mResourceNonce, mResourceString;
+ String mCookiePreview;
+
+ /**
+ * Creates new instance of this fragment
+ */
+ public static LinkedIdCreateTwitterStep2Fragment newInstance
+ (String handle, int proofNonce, String proofText) {
+
+ LinkedIdCreateTwitterStep2Fragment frag = new LinkedIdCreateTwitterStep2Fragment();
+
+ Bundle args = new Bundle();
+ args.putString(HANDLE, handle);
+ args.putInt(NONCE, proofNonce);
+ args.putString(TEXT, proofText);
+ frag.setArguments(args);
+
+ return frag;
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ final View view = inflater.inflate(R.layout.linked_create_twitter_fragment_step2, container, false);
+
+ mCookiePreview = TwitterResource.generatePreview();
+
+ mResourceHandle = getArguments().getString(HANDLE);
+ mResourceNonce = getArguments().getString(NONCE);
+ mResourceString = getArguments().getString(TEXT);
+
+ view.findViewById(R.id.next_button).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+
+ LinkedIdCreateTwitterStep3Fragment frag =
+ LinkedIdCreateTwitterStep3Fragment.newInstance(mResourceHandle,
+ mResourceNonce, mResourceString,
+ mEditTweetCustom.getText().toString());
+
+ 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);
+ }
+ });
+
+ mVerifyImage = (ImageView) view.findViewById(R.id.verify_image);
+ mVerifyProgress = view.findViewById(R.id.verify_progress);
+ mVerifyStatus = (TextView) view.findViewById(R.id.verify_status);
+
+ mEditTweetPreview = (EditText) view.findViewById(R.id.linked_create_twitter_preview);
+ mEditTweetPreview.setText(mCookiePreview);
+
+ mEditTweetCustom = (EditText) view.findViewById(R.id.linked_create_twitter_custom);
+ mEditTweetCustom.setFilters(new InputFilter[] {
+ new InputFilter.LengthFilter(139 - mResourceString.length())
+ });
+
+ mEditTweetTextLen = (TextView) view.findViewById(R.id.linked_create_twitter_textlen);
+ mEditTweetTextLen.setText(mResourceString.length() + "/140");
+
+ mEditTweetCustom.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) {
+ if (editable != null && editable.length() > 0) {
+ String str = editable + " " + mCookiePreview;
+ mEditTweetPreview.setText(str);
+
+ mEditTweetTextLen.setText(
+ (editable.length() + mResourceString.length() + 1) + "/140");
+ mEditTweetTextLen.setTextColor(getResources().getColor(str.length() == 140
+ ? R.color.android_red_dark
+ : R.color.primary_dark_material_light));
+
+
+ } else {
+ mEditTweetPreview.setText(mCookiePreview);
+ mEditTweetTextLen.setText(mResourceString.length() + "/140");
+ }
+ }
+ });
+
+ return view;
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ mLinkedIdWizard = (LinkedIdWizard) getActivity();
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep3Fragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep3Fragment.java
new file mode 100644
index 000000000..0c317004b
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep3Fragment.java
@@ -0,0 +1,160 @@
+/*
+ * 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.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.graphics.PorterDuff;
+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 android.widget.ImageView;
+import android.widget.TextView;
+
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.pgp.linked.LinkedCookieResource;
+import org.sufficientlysecure.keychain.ui.util.Notify;
+
+import java.util.List;
+
+public class LinkedIdCreateTwitterStep3Fragment extends LinkedIdCreateFinalFragment {
+
+ public static final String ARG_HANDLE = "uri", ARG_TEXT = "text", ARG_CUSTOM = "custom";
+
+ EditText mEditTweetPreview;
+
+ String mResourceHandle, mCustom, mFullString;
+ String mResourceString;
+
+ public static LinkedIdCreateTwitterStep3Fragment newInstance
+ (String handle, String proofNonce, String proofText, String customText) {
+
+ LinkedIdCreateTwitterStep3Fragment frag = new LinkedIdCreateTwitterStep3Fragment();
+
+ Bundle args = new Bundle();
+ args.putString(ARG_HANDLE, handle);
+ args.putString(ARG_NONCE, proofNonce);
+ args.putString(ARG_TEXT, proofText);
+ args.putString(ARG_CUSTOM, customText);
+ frag.setArguments(args);
+
+ return frag;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mResourceHandle = getArguments().getString(ARG_HANDLE);
+ mResourceString = getArguments().getString(ARG_TEXT);
+ mCustom = getArguments().getString(ARG_CUSTOM);
+
+ mFullString = mCustom.isEmpty() ? mResourceString : (mCustom + " " + mResourceString);
+
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View view = super.onCreateView(inflater, container, savedInstanceState);
+
+ mEditTweetPreview = (EditText) view.findViewById(R.id.linked_create_twitter_preview);
+ mEditTweetPreview.setText(mFullString);
+
+ 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();
+ }
+ });
+
+ view.findViewById(R.id.next_button).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+
+ // AffirmationCreateHttpsStep2Fragment frag =
+ // AffirmationCreateHttpsStep2Fragment.newInstance();
+
+ // mAffirmationWizard.loadFragment(null, frag, AffirmationWizard.FRAG_ACTION_TO_RIGHT);
+ }
+ });
+
+ return view;
+ }
+
+ @Override
+ LinkedCookieResource getResource() {
+ return null;
+ }
+
+ @Override
+ protected View newView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.linked_create_twitter_fragment_step3, container, false);
+ }
+
+ private void proofShare() {
+ Intent sendIntent = new Intent();
+ sendIntent.setAction(Intent.ACTION_SEND);
+ sendIntent.putExtra(Intent.EXTRA_TEXT, mFullString);
+ sendIntent.setType("text/plain");
+ startActivity(sendIntent);
+ }
+
+ private void proofSend() {
+
+ Intent tweetIntent = new Intent(Intent.ACTION_SEND);
+ tweetIntent.putExtra(Intent.EXTRA_TEXT, mFullString);
+ tweetIntent.setType("text/plain");
+
+ PackageManager packManager = getActivity().getPackageManager();
+ List<ResolveInfo> resolvedInfoList = packManager.queryIntentActivities(tweetIntent,
+ PackageManager.MATCH_DEFAULT_ONLY);
+
+ boolean resolved = false;
+ for(ResolveInfo resolveInfo : resolvedInfoList){
+ if(resolveInfo.activityInfo.packageName.startsWith("com.twitter.android")) {
+ tweetIntent.setClassName(
+ resolveInfo.activityInfo.packageName,
+ resolveInfo.activityInfo.name );
+ resolved = true;
+ break;
+ }
+ }
+
+ if (resolved) {
+ startActivity(tweetIntent);
+ } else {
+ Notify.showNotify(getActivity(),
+ "Twitter app is not installed, please use the send intent!",
+ Notify.Style.ERROR);
+ }
+
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdSelectFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdSelectFragment.java
new file mode 100644
index 000000000..abe7dbaf1
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdSelectFragment.java
@@ -0,0 +1,91 @@
+/*
+ * 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);
+ }
+ });
+
+ return view;
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ mLinkedIdWizard = (LinkedIdWizard) getActivity();
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdViewFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdViewFragment.java
new file mode 100644
index 000000000..7f8f4da04
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdViewFragment.java
@@ -0,0 +1,347 @@
+package org.sufficientlysecure.keychain.ui.linked;
+
+import java.io.IOException;
+
+import android.app.Activity;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Message;
+import android.os.Messenger;
+import android.support.v4.app.Fragment;
+import android.support.v7.widget.CardView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.ViewAnimator;
+
+import org.spongycastle.util.encoders.Hex;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.operations.results.CertifyResult;
+import org.sufficientlysecure.keychain.operations.results.LinkedVerifyResult;
+import org.sufficientlysecure.keychain.pgp.linked.LinkedCookieResource;
+import org.sufficientlysecure.keychain.pgp.linked.LinkedIdentity;
+import org.sufficientlysecure.keychain.pgp.linked.LinkedResource;
+import org.sufficientlysecure.keychain.pgp.linked.RawLinkedIdentity;
+import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
+import org.sufficientlysecure.keychain.service.CertifyActionsParcel;
+import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction;
+import org.sufficientlysecure.keychain.service.KeychainIntentService;
+import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
+import org.sufficientlysecure.keychain.ui.adapter.LinkedIdsAdapter.ViewHolder;
+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.widget.CertifyKeySpinner;
+import org.sufficientlysecure.keychain.util.Preferences;
+
+
+public class LinkedIdViewFragment extends Fragment {
+
+ private static final String ARG_ENCODED_LID = "encoded_lid";
+ private static final String ARG_VERIFIED = "verified";
+ private static final String ARG_FINGERPRINT = "fingerprint";
+ private static final String ARG_MASTER_KEY_ID = "fingerprint";
+
+ private RawLinkedIdentity mLinkedId;
+ private LinkedCookieResource mLinkedResource;
+ private Integer mVerified;
+
+ private CardView vLinkedIdsCard;
+ private Context mContext;
+ private byte[] mFingerprint;
+ private LayoutInflater mInflater;
+ private LinearLayout vLinkedCerts;
+
+ private View mCurrentCert;
+ private boolean mInProgress;
+ private ViewAnimator mButtonSwitcher;
+ private CertifyKeySpinner vKeySpinner;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Bundle args = getArguments();
+ byte[] data = args.getByteArray(ARG_ENCODED_LID);
+
+ try {
+ mLinkedId = LinkedIdentity.fromAttributeData(data);
+ } catch (IOException e) {
+ // TODO um…
+ e.printStackTrace();
+ throw new AssertionError("reconstruction of user attribute must succeed!");
+ }
+
+ if (mLinkedId instanceof LinkedIdentity) {
+ LinkedResource res = ((LinkedIdentity) mLinkedId).mResource;
+ mLinkedResource = (LinkedCookieResource) res;
+ }
+
+ mVerified = args.containsKey(ARG_VERIFIED) ? args.getInt(ARG_VERIFIED) : null;
+ mFingerprint = args.getByteArray(ARG_FINGERPRINT);
+
+ mContext = getActivity();
+ mInflater = getLayoutInflater(savedInstanceState);
+
+ }
+
+ public static Fragment newInstance(RawLinkedIdentity id,
+ Integer isVerified, byte[] fingerprint) throws IOException {
+ LinkedIdViewFragment frag = new LinkedIdViewFragment();
+
+ Bundle args = new Bundle();
+ args.putByteArray(ARG_ENCODED_LID, id.toUserAttribute().getEncoded());
+ if (isVerified != null) {
+ args.putInt(ARG_VERIFIED, isVerified);
+ }
+ args.putByteArray(ARG_FINGERPRINT, fingerprint);
+ frag.setArguments(args);
+
+ return frag;
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) {
+ View root = inflater.inflate(R.layout.linked_id_view_fragment, null);
+
+ vLinkedIdsCard = (CardView) root.findViewById(R.id.card_linked_ids);
+ vLinkedCerts = (LinearLayout) root.findViewById(R.id.linked_id_certs);
+ vKeySpinner = (CertifyKeySpinner) root.findViewById(R.id.cert_key_spinner);
+
+ View back = root.findViewById(R.id.back_button);
+ back.setClickable(true);
+ back.findViewById(R.id.back_button).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ getFragmentManager().popBackStack();
+ }
+ });
+
+ ViewHolder holder = new ViewHolder(root);
+
+ if (mVerified != null) {
+ holder.vVerified.setVisibility(View.VISIBLE);
+ switch (mVerified) {
+ 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;
+ }
+ } else {
+ holder.vVerified.setVisibility(View.GONE);
+ }
+
+ holder.setData(mContext, mLinkedId);
+
+ // no resource, nothing further we can do…
+ if (mLinkedResource == null) {
+ root.findViewById(R.id.button_view).setVisibility(View.GONE);
+ root.findViewById(R.id.button_verify).setVisibility(View.GONE);
+ return root;
+ }
+
+ View button_view = root.findViewById(R.id.button_view);
+ if (mLinkedResource.isViewable()) {
+ button_view.setVisibility(View.VISIBLE);
+ button_view.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Intent intent = mLinkedResource.getViewIntent();
+ if (intent == null) {
+ return;
+ }
+ getActivity().startActivity(intent);
+ }
+ });
+ } else {
+ button_view.setVisibility(View.GONE);
+ }
+
+ mButtonSwitcher = (ViewAnimator) root.findViewById(R.id.button_animator);
+
+ View button_verify = root.findViewById(R.id.button_verify);
+ button_verify.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ verifyResource();
+ }
+ });
+
+ View button_retry = root.findViewById(R.id.button_retry);
+ button_retry.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ verifyResource();
+ }
+ });
+
+ View button_confirm = root.findViewById(R.id.button_confirm);
+ button_confirm.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ certifyResource();
+ }
+ });
+
+ return root;
+ }
+
+ static class ViewHolderCert {
+ final ViewAnimator vProgress;
+ final ImageView vIcon;
+ final TextView vText;
+
+ ViewHolderCert(View view) {
+ vProgress = (ViewAnimator) view.findViewById(R.id.linked_cert_progress);
+ vIcon = (ImageView) view.findViewById(R.id.linked_cert_icon);
+ vText = (TextView) view.findViewById(R.id.linked_cert_text);
+ }
+ void setShowProgress(boolean show) {
+ vProgress.setDisplayedChild(show ? 0 : 1);
+ }
+ }
+
+ void showButton(int which) {
+ if (mButtonSwitcher.getDisplayedChild() == which) {
+ return;
+ }
+ mButtonSwitcher.setDisplayedChild(which);
+ }
+
+ void verifyResource() {
+
+ // only one at a time
+ synchronized (this) {
+ if (mInProgress) {
+ return;
+ }
+ mInProgress = true;
+ }
+
+ // is there a current certification? if not create a new one
+ final ViewHolderCert holder;
+ if (mCurrentCert == null) {
+ mCurrentCert = mInflater.inflate(R.layout.linked_id_cert, null);
+ holder = new ViewHolderCert(mCurrentCert);
+ mCurrentCert.setTag(holder);
+ vLinkedCerts.addView(mCurrentCert);
+ } else {
+ holder = (ViewHolderCert) mCurrentCert.getTag();
+ }
+
+ vKeySpinner.setVisibility(View.GONE);
+ holder.setShowProgress(true);
+ holder.vText.setText("Verifying…");
+
+ new AsyncTask<Void,Void,LinkedVerifyResult>() {
+ @Override
+ protected LinkedVerifyResult doInBackground(Void... params) {
+ long timer = System.currentTimeMillis();
+ LinkedVerifyResult result = mLinkedResource.verify(mFingerprint, mLinkedId.mNonce);
+
+ // 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) {
+ holder.setShowProgress(false);
+ if (result.success()) {
+ holder.vText.setText("Ok");
+ setupForConfirmation();
+ } else {
+ showButton(1);
+ holder.vText.setText("Error");
+ }
+ mInProgress = false;
+ }
+ }.execute();
+
+ }
+
+ void setupForConfirmation() {
+
+ // button is 'confirm'
+ showButton(2);
+
+ vKeySpinner.setVisibility(View.VISIBLE);
+
+ }
+
+ private void certifyResource() {
+
+ Bundle data = new Bundle();
+ {
+ CertifyAction action = new CertifyAction();
+
+ // fill values for this action
+ CertifyActionsParcel parcel = new CertifyActionsParcel(vKeySpinner.getSelectedKeyId());
+ parcel.mCertifyActions.addAll(certifyActions);
+
+ data.putParcelable(KeychainIntentService.CERTIFY_PARCEL, parcel);
+ /* if (mUploadKeyCheckbox.isChecked()) {
+ String keyserver = Preferences.getPreferences(getActivity()).getPreferredKeyserver();
+ data.putString(KeychainIntentService.UPLOAD_KEY_SERVER, keyserver);
+ } */
+ }
+
+ // Send all information needed to service to sign key in other thread
+ Intent intent = new Intent(getActivity(), KeychainIntentService.class);
+ intent.setAction(KeychainIntentService.ACTION_CERTIFY_KEYRING);
+ intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
+
+ // Message is received after signing is done in KeychainIntentService
+ KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(getActivity(),
+ getString(R.string.progress_certifying), ProgressDialog.STYLE_SPINNER, true) {
+ public void handleMessage(Message message) {
+ // handle messages by standard KeychainIntentServiceHandler first
+ super.handleMessage(message);
+
+ if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
+ Bundle data = message.getData();
+ CertifyResult result = data.getParcelable(CertifyResult.EXTRA_RESULT);
+
+ Intent intent = new Intent();
+ intent.putExtra(CertifyResult.EXTRA_RESULT, result);
+ getActivity().setResult(Activity.RESULT_OK, intent);
+ getActivity().finish();
+ }
+ }
+ };
+
+ // Create a new Messenger for the communication back
+ Messenger messenger = new Messenger(saveHandler);
+ intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
+
+ // show progress dialog
+ saveHandler.showProgressDialog(getActivity());
+
+ // start service with intent
+ getActivity().startService(intent);
+
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdWizard.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdWizard.java
new file mode 100644
index 000000000..161efc8fb
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdWizard.java
@@ -0,0 +1,106 @@
+/*
+ * 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.net.Uri;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentTransaction;
+import android.support.v7.app.ActionBarActivity;
+
+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.util.Log;
+
+public class LinkedIdWizard extends ActionBarActivity {
+
+ 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);
+
+ setContentView(R.layout.create_key_activity);
+
+ 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);
+ }
+
+ 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;
+ }
+
+ // 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();
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/HttpsPrefixedText.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/HttpsPrefixedText.java
new file mode 100644
index 000000000..292343eb7
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/HttpsPrefixedText.java
@@ -0,0 +1,38 @@
+package org.sufficientlysecure.keychain.ui.widget;
+
+import android.content.Context;
+import android.graphics.*;
+import android.support.annotation.NonNull;
+import android.util.AttributeSet;
+import android.widget.EditText;
+
+/** */
+public class HttpsPrefixedText extends EditText {
+
+ private String mPrefix; // can be hardcoded for demo purposes
+ private Rect mPrefixRect = new Rect();
+
+ public HttpsPrefixedText(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mPrefix = "https://";
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ getPaint().getTextBounds(mPrefix, 0, mPrefix.length(), mPrefixRect);
+
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+
+ @Override
+ protected void onDraw(@NonNull Canvas canvas) {
+ super.onDraw(canvas);
+ canvas.drawText(mPrefix, super.getCompoundPaddingLeft(), getBaseline(), getPaint());
+ }
+
+ @Override
+ public int getCompoundPaddingLeft() {
+ return super.getCompoundPaddingLeft() + mPrefixRect.width();
+ }
+
+} \ No newline at end of file