aboutsummaryrefslogtreecommitdiffstats
path: root/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdViewFragment.java
diff options
context:
space:
mode:
Diffstat (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdViewFragment.java')
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdViewFragment.java585
1 files changed, 585 insertions, 0 deletions
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..c39e40761
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdViewFragment.java
@@ -0,0 +1,585 @@
+package org.sufficientlysecure.keychain.ui.linked;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Messenger;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentManager.OnBackStackChangedListener;
+import android.support.v4.app.LoaderManager;
+import android.support.v4.content.CursorLoader;
+import android.support.v4.content.Loader;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.TextView;
+import android.widget.ViewAnimator;
+
+import org.sufficientlysecure.keychain.Constants;
+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.provider.KeychainContract.UserPackets;
+import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
+import org.sufficientlysecure.keychain.service.CertifyActionsParcel;
+import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction;
+import org.sufficientlysecure.keychain.service.KeychainIntentService;
+import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
+import org.sufficientlysecure.keychain.service.PassphraseCacheService;
+import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity;
+import org.sufficientlysecure.keychain.ui.adapter.LinkedIdsAdapter;
+import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter;
+import org.sufficientlysecure.keychain.ui.linked.LinkedIdViewFragment.ViewHolder.VerifyState;
+import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
+import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State;
+import org.sufficientlysecure.keychain.ui.util.Notify;
+import org.sufficientlysecure.keychain.ui.util.Notify.Style;
+import org.sufficientlysecure.keychain.ui.widget.CertListWidget;
+import org.sufficientlysecure.keychain.ui.widget.CertifyKeySpinner;
+import org.sufficientlysecure.keychain.util.Log;
+
+
+public class LinkedIdViewFragment extends Fragment implements
+ LoaderManager.LoaderCallbacks<Cursor>, OnBackStackChangedListener {
+
+ public static final int REQUEST_CODE_PASSPHRASE = 0x00008001;
+
+ private static final String ARG_DATA_URI = "data_uri";
+ private static final String ARG_LID_RANK = "rank";
+ private static final String ARG_IS_SECRET = "verified";
+ private static final String ARG_FINGERPRINT = "fingerprint";
+ private static final int LOADER_ID_LINKED_ID = 1;
+
+ private RawLinkedIdentity mLinkedId;
+ private LinkedCookieResource mLinkedResource;
+ private boolean mIsSecret;
+
+ private Context mContext;
+ private byte[] mFingerprint;
+
+ private AsyncTask mInProgress;
+
+ private Uri mDataUri;
+ private ViewHolder mViewHolder;
+ private int mLidRank;
+ private OnIdentityLoadedListener mIdLoadedListener;
+
+ public static LinkedIdViewFragment newInstance(Uri dataUri, int rank,
+ boolean isSecret, byte[] fingerprint) throws IOException {
+ LinkedIdViewFragment frag = new LinkedIdViewFragment();
+
+ Bundle args = new Bundle();
+ args.putParcelable(ARG_DATA_URI, dataUri);
+ args.putInt(ARG_LID_RANK, rank);
+ args.putBoolean(ARG_IS_SECRET, isSecret);
+ args.putByteArray(ARG_FINGERPRINT, fingerprint);
+ frag.setArguments(args);
+
+ return frag;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Bundle args = getArguments();
+ mDataUri = args.getParcelable(ARG_DATA_URI);
+ mLidRank = args.getInt(ARG_LID_RANK);
+
+ mIsSecret = args.getBoolean(ARG_IS_SECRET);
+ mFingerprint = args.getByteArray(ARG_FINGERPRINT);
+
+ mContext = getActivity();
+
+ getLoaderManager().initLoader(LOADER_ID_LINKED_ID, null, this);
+
+ }
+
+ @Override
+ public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+ switch (id) {
+ case LOADER_ID_LINKED_ID:
+ return new CursorLoader(getActivity(), mDataUri,
+ UserIdsAdapter.USER_PACKETS_PROJECTION,
+ Tables.USER_PACKETS + "." + UserPackets.RANK
+ + " = " + Integer.toString(mLidRank), null, null);
+ default:
+ return null;
+ }
+ }
+
+ @Override
+ public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
+ switch (loader.getId()) {
+ case LOADER_ID_LINKED_ID:
+
+ if (!cursor.moveToFirst()) {
+ Notify.createNotify(getActivity(), "Error loading identity!",
+ Notify.LENGTH_LONG, Style.ERROR).show();
+ finishFragment();
+ break;
+ }
+
+ try {
+ int certStatus = cursor.getInt(UserIdsAdapter.INDEX_VERIFIED);
+
+ byte[] data = cursor.getBlob(UserIdsAdapter.INDEX_ATTRIBUTE_DATA);
+ RawLinkedIdentity linkedId = LinkedIdentity.fromAttributeData(data);
+
+ loadIdentity(linkedId, certStatus);
+
+ if (mIdLoadedListener != null) {
+ mIdLoadedListener.onIdentityLoaded();
+ mIdLoadedListener = null;
+ }
+
+ } catch (IOException e) {
+ Log.e(Constants.TAG, "error parsing identity", e);
+ Notify.createNotify(getActivity(), "Error parsing identity!",
+ Notify.LENGTH_LONG, Style.ERROR).show();
+ finishFragment();
+ }
+
+ break;
+ }
+ }
+
+ public void finishFragment() {
+ FragmentManager manager = getFragmentManager();
+ manager.removeOnBackStackChangedListener(this);
+ manager.popBackStack("linked_id", FragmentManager.POP_BACK_STACK_INCLUSIVE);
+ }
+
+ public interface OnIdentityLoadedListener {
+ public void onIdentityLoaded();
+ }
+
+ public void setOnIdentityLoadedListener(OnIdentityLoadedListener listener) {
+ mIdLoadedListener = listener;
+ }
+
+ private void loadIdentity(RawLinkedIdentity linkedId, int certStatus) {
+ mLinkedId = linkedId;
+
+ setShowVerifying(false);
+
+ if (mLinkedId instanceof LinkedIdentity) {
+ LinkedResource res = ((LinkedIdentity) mLinkedId).mResource;
+ mLinkedResource = (LinkedCookieResource) res;
+ }
+
+ if (!mIsSecret) {
+ mViewHolder.mLinkedIdHolder.vVerified.setVisibility(View.VISIBLE);
+
+ switch (certStatus) {
+ case Certs.VERIFIED_SECRET:
+ KeyFormattingUtils.setStatusImage(mContext, mViewHolder.mLinkedIdHolder.vVerified,
+ null, State.VERIFIED, KeyFormattingUtils.DEFAULT_COLOR);
+ break;
+ case Certs.VERIFIED_SELF:
+ KeyFormattingUtils.setStatusImage(mContext, mViewHolder.mLinkedIdHolder.vVerified,
+ null, State.UNVERIFIED, KeyFormattingUtils.DEFAULT_COLOR);
+ break;
+ default:
+ KeyFormattingUtils.setStatusImage(mContext, mViewHolder.mLinkedIdHolder.vVerified,
+ null, State.INVALID, KeyFormattingUtils.DEFAULT_COLOR);
+ break;
+ }
+ } else {
+ mViewHolder.mLinkedIdHolder.vVerified.setVisibility(View.GONE);
+ }
+
+ mViewHolder.mLinkedIdHolder.setData(mContext, mLinkedId);
+
+ // no resource, nothing further we can do…
+ if (mLinkedResource == null) {
+ mViewHolder.vButtonView.setVisibility(View.GONE);
+ mViewHolder.vButtonVerify.setVisibility(View.GONE);
+ return;
+ }
+
+ if (mLinkedResource.isViewable()) {
+ mViewHolder.vButtonView.setVisibility(View.VISIBLE);
+ mViewHolder.vButtonView.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Intent intent = mLinkedResource.getViewIntent();
+ if (intent == null) {
+ return;
+ }
+ getActivity().startActivity(intent);
+ }
+ });
+ } else {
+ mViewHolder.vButtonView.setVisibility(View.GONE);
+ }
+
+ }
+
+ @Override
+ public void onLoaderReset(Loader<Cursor> loader) {
+
+ }
+
+ static class ViewHolder {
+ private final View vButtonView;
+ private final ViewAnimator vVerifyingContainer;
+ LinkedIdsAdapter.ViewHolder mLinkedIdHolder;
+
+ private ViewAnimator vButtonSwitcher;
+ private CertListWidget vLinkedCerts;
+ private CertifyKeySpinner vKeySpinner;
+ private final View vButtonVerify;
+ private final View vButtonRetry;
+ private final View vButtonConfirm;
+ private final View vButtonBack;
+
+ private final ViewAnimator vProgress;
+ private final TextView vText;
+
+ ViewHolder(View root) {
+ vLinkedCerts = (CertListWidget) root.findViewById(R.id.linked_id_certs);
+ vKeySpinner = (CertifyKeySpinner) root.findViewById(R.id.cert_key_spinner);
+ vButtonSwitcher = (ViewAnimator) root.findViewById(R.id.button_animator);
+
+ mLinkedIdHolder = new LinkedIdsAdapter.ViewHolder(root);
+
+ vButtonBack = root.findViewById(R.id.back_button);
+ vButtonVerify = root.findViewById(R.id.button_verify);
+ vButtonRetry = root.findViewById(R.id.button_retry);
+ vButtonConfirm = root.findViewById(R.id.button_confirm);
+ vButtonView = root.findViewById(R.id.button_view);
+
+ vVerifyingContainer = (ViewAnimator) root.findViewById(R.id.linked_verify_container);
+
+ vProgress = (ViewAnimator) root.findViewById(R.id.linked_cert_progress);
+ vText = (TextView) root.findViewById(R.id.linked_cert_text);
+ }
+
+ enum VerifyState {
+ VERIFYING, VERIFY_OK, VERIFY_ERROR, CERTIFYING
+ }
+
+ void setVerifyingState(VerifyState state, boolean isSecret) {
+ switch (state) {
+ case VERIFYING:
+ vProgress.setDisplayedChild(0);
+ vText.setText("Verifying…");
+ vKeySpinner.setVisibility(View.GONE);
+ break;
+
+ case VERIFY_OK:
+ vText.setText("Ok");
+ vProgress.setDisplayedChild(1);
+ if (!isSecret) {
+ showButton(2);
+ vKeySpinner.setVisibility(View.VISIBLE);
+ } else {
+ showButton(1);
+ vKeySpinner.setVisibility(View.GONE);
+ }
+ break;
+
+ case VERIFY_ERROR:
+ showButton(1);
+ vProgress.setDisplayedChild(2);
+ vText.setText("Error");
+ vKeySpinner.setVisibility(View.GONE);
+ break;
+
+ case CERTIFYING:
+ vProgress.setDisplayedChild(0);
+ vText.setText("Confirming…");
+ vKeySpinner.setVisibility(View.GONE);
+ break;
+ }
+ }
+
+ void showVerifyingContainer(boolean show) {
+ if (vVerifyingContainer.getDisplayedChild() == (show ? 1 : 0)) {
+ return;
+ }
+ vVerifyingContainer.setDisplayedChild(show ? 1 : 0);
+ }
+
+ void showButton(int which) {
+ if (vButtonSwitcher.getDisplayedChild() == which) {
+ return;
+ }
+ vButtonSwitcher.setDisplayedChild(which);
+ }
+
+ }
+
+ private boolean mVerificationState = false;
+ /** Switches between the 'verifying' ui bit and certificate status. This method
+ * must behave correctly in all states, showing or hiding the appropriate views
+ * and cancelling pending operations where necessary.
+ *
+ * This method also handles back button functionality in combination with
+ * onBackStateChanged.
+ */
+ void setShowVerifying(boolean show) {
+ if (!show) {
+ if (mInProgress != null) {
+ mInProgress.cancel(false);
+ mInProgress = null;
+ }
+ getFragmentManager().removeOnBackStackChangedListener(this);
+ new Handler().post(new Runnable() {
+ @Override
+ public void run() {
+ getFragmentManager().popBackStack("verification",
+ FragmentManager.POP_BACK_STACK_INCLUSIVE);
+ }
+ });
+
+ if (!mVerificationState) {
+ return;
+ }
+ mVerificationState = false;
+
+ mViewHolder.showButton(0);
+ mViewHolder.vKeySpinner.setVisibility(View.GONE);
+ mViewHolder.showVerifyingContainer(false);
+ return;
+ }
+
+ if (mVerificationState) {
+ return;
+ }
+ mVerificationState = true;
+
+ FragmentManager manager = getFragmentManager();
+ manager.beginTransaction().addToBackStack("verification").commit();
+ manager.executePendingTransactions();
+ manager.addOnBackStackChangedListener(this);
+ mViewHolder.showVerifyingContainer(true);
+
+ }
+
+ @Override
+ public void onBackStackChanged() {
+ setShowVerifying(false);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) {
+ View root = inflater.inflate(R.layout.linked_id_view_fragment, null);
+
+ mViewHolder = new ViewHolder(root);
+ root.setTag(mViewHolder);
+
+ mViewHolder.vButtonBack.setClickable(true);
+ mViewHolder.vButtonBack.findViewById(R.id.back_button).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ getFragmentManager().popBackStack();
+ }
+ });
+
+ mViewHolder.vButtonVerify.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ verifyResource();
+ }
+ });
+ mViewHolder.vButtonRetry.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ verifyResource();
+ }
+ });
+ mViewHolder.vButtonConfirm.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ initiateCertifying();
+ }
+ });
+
+ {
+ Bundle args = new Bundle();
+ args.putParcelable(CertListWidget.ARG_URI, Certs.buildLinkedIdCertsUri(mDataUri, mLidRank));
+ args.putBoolean(CertListWidget.ARG_IS_SECRET, mIsSecret);
+ getLoaderManager().initLoader(CertListWidget.LOADER_ID_LINKED_CERTS,
+ args, mViewHolder.vLinkedCerts);
+ }
+
+ return root;
+ }
+
+ void verifyResource() {
+
+ // only one at a time (no sync needed, mInProgress is only touched in ui thread)
+ if (mInProgress != null) {
+ return;
+ }
+
+ setShowVerifying(true);
+
+ mViewHolder.vKeySpinner.setVisibility(View.GONE);
+ mViewHolder.setVerifyingState(VerifyState.VERIFYING, mIsSecret);
+
+ mInProgress = new AsyncTask<Void,Void,LinkedVerifyResult>() {
+ @Override
+ protected LinkedVerifyResult doInBackground(Void... params) {
+ long timer = System.currentTimeMillis();
+ LinkedVerifyResult result = mLinkedResource.verify(mFingerprint);
+
+ // ux flow: this operation should take at last a second
+ timer = System.currentTimeMillis() -timer;
+ if (timer < 1000) try {
+ Thread.sleep(1000 -timer);
+ } catch (InterruptedException e) {
+ // never mind
+ }
+
+ return result;
+ }
+
+ @Override
+ protected void onPostExecute(LinkedVerifyResult result) {
+ if (isCancelled()) {
+ return;
+ }
+ if (result.success()) {
+ mViewHolder.setVerifyingState(VerifyState.VERIFY_OK, mIsSecret);
+ } else {
+ mViewHolder.setVerifyingState(VerifyState.VERIFY_ERROR, mIsSecret);
+ }
+ mInProgress = null;
+ }
+ }.execute();
+
+ }
+
+ private void initiateCertifying() {
+
+ if (mIsSecret) {
+ return;
+ }
+
+ // get the user's passphrase for this key (if required)
+ String passphrase;
+ long certifyKeyId = mViewHolder.vKeySpinner.getSelectedItemId();
+ try {
+ passphrase = PassphraseCacheService.getCachedPassphrase(
+ getActivity(), certifyKeyId, certifyKeyId);
+ } catch (PassphraseCacheService.KeyNotFoundException e) {
+ Log.e(Constants.TAG, "Key not found!", e);
+ getActivity().finish();
+ return;
+ }
+ if (passphrase == null) {
+ Intent intent = new Intent(getActivity(), PassphraseDialogActivity.class);
+ intent.putExtra(PassphraseDialogActivity.EXTRA_SUBKEY_ID, certifyKeyId);
+ startActivityForResult(intent, REQUEST_CODE_PASSPHRASE);
+ // bail out; need to wait until the user has entered the passphrase before trying again
+ } else {
+ certifyResource(certifyKeyId, "");
+ }
+ }
+
+ @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);
+ long certifyKeyId = data.getLongExtra(PassphraseDialogActivity.EXTRA_KEY_ID, 0L);
+ if (certifyKeyId == 0L) {
+ throw new AssertionError("key id must not be 0");
+ }
+ certifyResource(certifyKeyId, passphrase);
+ }
+ return;
+ }
+
+ default: {
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+ }
+ }
+
+ private void certifyResource(long certifyKeyId, String passphrase) {
+
+ if (mIsSecret) {
+ return;
+ }
+
+ mViewHolder.setVerifyingState(VerifyState.CERTIFYING, mIsSecret);
+
+ Bundle data = new Bundle();
+ {
+
+ long masterKeyId = KeyFormattingUtils.convertFingerprintToKeyId(mFingerprint);
+ CertifyAction action = new CertifyAction(masterKeyId, null,
+ Arrays.asList(mLinkedId.toUserAttribute()));
+
+ // fill values for this action
+ CertifyActionsParcel parcel = new CertifyActionsParcel(certifyKeyId);
+ parcel.mCertifyActions.addAll(Arrays.asList(action));
+
+ 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()) {
+ public void handleMessage(Message message) {
+ // handle messages by standard KeychainIntentServiceHandler first
+ super.handleMessage(message);
+
+ Bundle data = message.getData();
+
+ if (message.arg1 == MessageStatus.UPDATE_PROGRESS.ordinal()) {
+ if (data.containsKey(DATA_MESSAGE)) {
+ mViewHolder.vText.setText(data.getString(DATA_MESSAGE));
+ } else if (data.containsKey(DATA_MESSAGE_ID)) {
+ mViewHolder.vText.setText(data.getString(DATA_MESSAGE_ID));
+ }
+ return;
+ }
+
+ if (message.arg1 == MessageStatus.OKAY.ordinal()) {
+ CertifyResult result = data.getParcelable(CertifyResult.EXTRA_RESULT);
+ result.createNotify(getActivity()).show();
+ }
+ }
+ };
+
+ // Create a new Messenger for the communication back
+ Messenger messenger = new Messenger(saveHandler);
+ intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
+
+ // start service with intent
+ getActivity().startService(intent);
+
+ }
+
+}